@edx/frontend-app-alacarte-checkout 1.22.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/PaymentForm/Messages.d.ts +10 -0
- package/dist/components/PaymentForm/Messages.js +10 -0
- package/dist/components/PaymentForm/Messages.js.map +1 -1
- package/dist/components/PaymentForm/PaymentForm.js +39 -11
- package/dist/components/PaymentForm/PaymentForm.js.map +1 -1
- package/dist/data/hooks/placedOrderPoll.d.ts +38 -0
- package/dist/data/hooks/placedOrderPoll.js +81 -0
- package/dist/data/hooks/placedOrderPoll.js.map +1 -0
- package/dist/data/hooks/useAlacarteCheckout.d.ts +4 -0
- package/dist/data/hooks/useAlacarteCheckout.js +49 -23
- package/dist/data/hooks/useAlacarteCheckout.js.map +1 -1
- package/dist/data/hooks/useAlacarteQueries.d.ts +178 -5
- package/dist/data/hooks/useAlacarteQueries.js +50 -20
- package/dist/data/hooks/useAlacarteQueries.js.map +1 -1
- package/dist/data/hooks/useCheckoutQueries.js +11 -0
- package/dist/data/hooks/useCheckoutQueries.js.map +1 -1
- package/dist/types/alacarte.d.ts +1 -1
- package/dist/types/alacarte.js.map +1 -1
- package/dist/utils/alacarteApi.d.ts +2 -2
- package/dist/utils/alacarteApi.js +8 -8
- package/dist/utils/alacarteApi.js.map +1 -1
- package/dist/utils/alacartePayment.d.ts +1 -1
- package/dist/utils/alacartePayment.js.map +1 -1
- package/dist/utils/alacarteQueries.d.ts +5 -5
- package/dist/utils/alacarteQueries.js +5 -3
- package/dist/utils/alacarteQueries.js.map +1 -1
- package/dist/utils/billingAddress.d.ts +15 -1
- package/dist/utils/billingAddress.js +64 -3
- package/dist/utils/billingAddress.js.map +1 -1
- package/package.json +1 -1
|
@@ -104,5 +104,15 @@ declare const messages: {
|
|
|
104
104
|
defaultMessage: string;
|
|
105
105
|
description: string;
|
|
106
106
|
};
|
|
107
|
+
zipCodeInvalid: {
|
|
108
|
+
id: string;
|
|
109
|
+
defaultMessage: string;
|
|
110
|
+
description: string;
|
|
111
|
+
};
|
|
112
|
+
postalCodeInvalid: {
|
|
113
|
+
id: string;
|
|
114
|
+
defaultMessage: string;
|
|
115
|
+
description: string;
|
|
116
|
+
};
|
|
107
117
|
};
|
|
108
118
|
export default messages;
|
|
@@ -108,6 +108,16 @@ const messages = defineMessages({
|
|
|
108
108
|
defaultMessage: 'This field is required',
|
|
109
109
|
description: 'Error text shown below a required field that is empty',
|
|
110
110
|
},
|
|
111
|
+
zipCodeInvalid: {
|
|
112
|
+
id: 'checkout.paymentForm.zipCodeInvalid',
|
|
113
|
+
defaultMessage: 'Enter a valid ZIP code',
|
|
114
|
+
description: 'Error text shown when a US ZIP code has an invalid format',
|
|
115
|
+
},
|
|
116
|
+
postalCodeInvalid: {
|
|
117
|
+
id: 'checkout.paymentForm.postalCodeInvalid',
|
|
118
|
+
defaultMessage: 'Enter a valid postal code',
|
|
119
|
+
description: 'Error text shown when a Canadian postal code has an invalid format',
|
|
120
|
+
},
|
|
111
121
|
});
|
|
112
122
|
export default messages;
|
|
113
123
|
//# sourceMappingURL=Messages.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Messages.js","sourceRoot":"","sources":["../../../src/components/PaymentForm/Messages.ts"],"names":[],"mappings":"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","sourcesContent":["import { defineMessages } from '@openedx/frontend-base';\n\nconst messages = defineMessages({\n // Section headings\n selectPaymentMethod: {\n id: 'checkout.paymentForm.selectPaymentMethod',\n defaultMessage: 'Select Payment Method',\n description: 'Heading for the payment method selection section',\n },\n needAssistance: {\n id: 'checkout.paymentForm.needAssistance',\n defaultMessage: 'Need assistance?',\n description: 'Help text heading',\n },\n needAssistanceBody: {\n id: 'checkout.paymentForm.needAssistanceBody',\n defaultMessage: 'Visit the {link} to review purchase and refund policies.',\n description: 'Help text with learner help center link',\n },\n learnerHelpCenter: {\n id: 'checkout.paymentForm.learnerHelpCenter',\n defaultMessage: 'Learner Help Center',\n description: 'Learner help center link text',\n },\n\n payWithCard: {\n id: 'checkout.paymentForm.payWithCard',\n defaultMessage: 'Pay with card',\n description: 'Heading for the card payment fields',\n },\n\n // Order total\n orderDisclaimer: {\n id: 'checkout.paymentForm.orderDisclaimer',\n defaultMessage: 'After you complete your order you will be able to select course dates from your dashboard.',\n description: 'Disclaimer shown above the order total explaining taxes and enrollment',\n },\n total: {\n id: 'checkout.paymentForm.total',\n defaultMessage: 'Total:',\n description: 'Label for the order total amount',\n },\n totalAmount: {\n id: 'checkout.paymentForm.totalAmount',\n defaultMessage: '${amount} {currency}',\n description: 'Formatted order total with currency code e.g. $219.00 USD',\n },\n\n // Submit button\n payNow: {\n id: 'checkout.paymentForm.payNow',\n defaultMessage: 'Pay now',\n description: 'Submit button label',\n },\n processing: {\n id: 'checkout.paymentForm.processing',\n defaultMessage: 'Processing...',\n description: 'Submit button label while the order is being placed',\n },\n country: {\n id: 'checkout.paymentForm.country',\n defaultMessage: 'Country',\n description: 'Country selector label',\n },\n countryPlaceholder: {\n id: 'checkout.paymentForm.countryPlaceholder',\n defaultMessage: 'Select your country',\n description: 'Placeholder option shown before a country is selected',\n },\n state: {\n id: 'checkout.paymentForm.state',\n defaultMessage: 'State',\n description: 'State selector label (India)',\n },\n statePlaceholder: {\n id: 'checkout.paymentForm.statePlaceholder',\n defaultMessage: 'Select your state',\n description: 'Placeholder option shown before a state is selected',\n },\n province: {\n id: 'checkout.paymentForm.province',\n defaultMessage: 'Province',\n description: 'Province selector label (Canada)',\n },\n provincePlaceholder: {\n id: 'checkout.paymentForm.provincePlaceholder',\n defaultMessage: 'Select your province',\n description: 'Placeholder option shown before a province is selected',\n },\n zipCode: {\n id: 'checkout.paymentForm.zipCode',\n defaultMessage: 'ZIP Code',\n description: 'ZIP code label (United States)',\n },\n zipCodePlaceholder: {\n id: 'checkout.paymentForm.zipCodePlaceholder',\n defaultMessage: '12345',\n description: 'ZIP code input placeholder (United States)',\n },\n postalCode: {\n id: 'checkout.paymentForm.postalCode',\n defaultMessage: 'Postal code',\n description: 'Postal code label (Canada)',\n },\n postalCodePlaceholder: {\n id: 'checkout.paymentForm.postalCodePlaceholder',\n defaultMessage: 'A1A 1A1',\n description: 'Postal code input placeholder (Canada)',\n },\n fieldRequired: {\n id: 'checkout.paymentForm.fieldRequired',\n defaultMessage: 'This field is required',\n description: 'Error text shown below a required field that is empty',\n },\n});\n\nexport default messages;\n"]}
|
|
1
|
+
{"version":3,"file":"Messages.js","sourceRoot":"","sources":["../../../src/components/PaymentForm/Messages.ts"],"names":[],"mappings":"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","sourcesContent":["import { defineMessages } from '@openedx/frontend-base';\n\nconst messages = defineMessages({\n // Section headings\n selectPaymentMethod: {\n id: 'checkout.paymentForm.selectPaymentMethod',\n defaultMessage: 'Select Payment Method',\n description: 'Heading for the payment method selection section',\n },\n needAssistance: {\n id: 'checkout.paymentForm.needAssistance',\n defaultMessage: 'Need assistance?',\n description: 'Help text heading',\n },\n needAssistanceBody: {\n id: 'checkout.paymentForm.needAssistanceBody',\n defaultMessage: 'Visit the {link} to review purchase and refund policies.',\n description: 'Help text with learner help center link',\n },\n learnerHelpCenter: {\n id: 'checkout.paymentForm.learnerHelpCenter',\n defaultMessage: 'Learner Help Center',\n description: 'Learner help center link text',\n },\n\n payWithCard: {\n id: 'checkout.paymentForm.payWithCard',\n defaultMessage: 'Pay with card',\n description: 'Heading for the card payment fields',\n },\n\n // Order total\n orderDisclaimer: {\n id: 'checkout.paymentForm.orderDisclaimer',\n defaultMessage: 'After you complete your order you will be able to select course dates from your dashboard.',\n description: 'Disclaimer shown above the order total explaining taxes and enrollment',\n },\n total: {\n id: 'checkout.paymentForm.total',\n defaultMessage: 'Total:',\n description: 'Label for the order total amount',\n },\n totalAmount: {\n id: 'checkout.paymentForm.totalAmount',\n defaultMessage: '${amount} {currency}',\n description: 'Formatted order total with currency code e.g. $219.00 USD',\n },\n\n // Submit button\n payNow: {\n id: 'checkout.paymentForm.payNow',\n defaultMessage: 'Pay now',\n description: 'Submit button label',\n },\n processing: {\n id: 'checkout.paymentForm.processing',\n defaultMessage: 'Processing...',\n description: 'Submit button label while the order is being placed',\n },\n country: {\n id: 'checkout.paymentForm.country',\n defaultMessage: 'Country',\n description: 'Country selector label',\n },\n countryPlaceholder: {\n id: 'checkout.paymentForm.countryPlaceholder',\n defaultMessage: 'Select your country',\n description: 'Placeholder option shown before a country is selected',\n },\n state: {\n id: 'checkout.paymentForm.state',\n defaultMessage: 'State',\n description: 'State selector label (India)',\n },\n statePlaceholder: {\n id: 'checkout.paymentForm.statePlaceholder',\n defaultMessage: 'Select your state',\n description: 'Placeholder option shown before a state is selected',\n },\n province: {\n id: 'checkout.paymentForm.province',\n defaultMessage: 'Province',\n description: 'Province selector label (Canada)',\n },\n provincePlaceholder: {\n id: 'checkout.paymentForm.provincePlaceholder',\n defaultMessage: 'Select your province',\n description: 'Placeholder option shown before a province is selected',\n },\n zipCode: {\n id: 'checkout.paymentForm.zipCode',\n defaultMessage: 'ZIP Code',\n description: 'ZIP code label (United States)',\n },\n zipCodePlaceholder: {\n id: 'checkout.paymentForm.zipCodePlaceholder',\n defaultMessage: '12345',\n description: 'ZIP code input placeholder (United States)',\n },\n postalCode: {\n id: 'checkout.paymentForm.postalCode',\n defaultMessage: 'Postal code',\n description: 'Postal code label (Canada)',\n },\n postalCodePlaceholder: {\n id: 'checkout.paymentForm.postalCodePlaceholder',\n defaultMessage: 'A1A 1A1',\n description: 'Postal code input placeholder (Canada)',\n },\n fieldRequired: {\n id: 'checkout.paymentForm.fieldRequired',\n defaultMessage: 'This field is required',\n description: 'Error text shown below a required field that is empty',\n },\n zipCodeInvalid: {\n id: 'checkout.paymentForm.zipCodeInvalid',\n defaultMessage: 'Enter a valid ZIP code',\n description: 'Error text shown when a US ZIP code has an invalid format',\n },\n postalCodeInvalid: {\n id: 'checkout.paymentForm.postalCodeInvalid',\n defaultMessage: 'Enter a valid postal code',\n description: 'Error text shown when a Canadian postal code has an invalid format',\n },\n});\n\nexport default messages;\n"]}
|
|
@@ -14,12 +14,14 @@ import { useIntl } from '@openedx/frontend-base';
|
|
|
14
14
|
import PaymentFields from '../PaymentFields/PaymentFields';
|
|
15
15
|
import ExpressCheckout from '../ExpressCheckout/ExpressCheckout';
|
|
16
16
|
import { BILLING_COUNTRIES, LEARNER_HELP_CENTER_URL } from '../../constants';
|
|
17
|
-
import { getBillingCountryFields, hasRequiredBillingFields } from '../../utils/billingAddress';
|
|
17
|
+
import { getBillingCountryFields, getBillingPostalMaxLength, hasRequiredBillingFields, isValidBillingPostal, isValidBillingSubdivision, sanitizeBillingPostalInput, } from '../../utils/billingAddress';
|
|
18
18
|
import messages from './Messages';
|
|
19
19
|
const PaymentForm = ({ billing, setField, numberMountRef, expiryMountRef, cvcMountRef, stripeReady, sessionReady, submitting, onPlaceOrder, alacarteLine = null, stripe = null, expressCountry = 'US', onExpressPayment, cardFieldStates, onValidationError, }) => {
|
|
20
|
-
var _a, _b;
|
|
20
|
+
var _a, _b, _c;
|
|
21
21
|
const { formatMessage } = useIntl();
|
|
22
22
|
const [showValidationErrors, setShowValidationErrors] = useState(false);
|
|
23
|
+
/** Postal format errors only after blur — cleared again while the user types. */
|
|
24
|
+
const [postalBlurred, setPostalBlurred] = useState(false);
|
|
23
25
|
// No fallback amount — an unresolved à la carte line means we don't know
|
|
24
26
|
// the price yet, so this is left blank rather than showing a made-up number.
|
|
25
27
|
const totalText = (alacarteLine === null || alacarteLine === void 0 ? void 0 : alacarteLine.amountDue)
|
|
@@ -67,17 +69,39 @@ const PaymentForm = ({ billing, setField, numberMountRef, expiryMountRef, cvcMou
|
|
|
67
69
|
}
|
|
68
70
|
: undefined;
|
|
69
71
|
const countryError = showValidationErrors && !billing.country ? requiredMsg : undefined;
|
|
70
|
-
const stateError =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
const stateError = (() => {
|
|
73
|
+
if (!countryFields.needsState)
|
|
74
|
+
return undefined;
|
|
75
|
+
if (!billing.state.trim()) {
|
|
76
|
+
return showValidationErrors ? requiredMsg : undefined;
|
|
77
|
+
}
|
|
78
|
+
// Subdivision is a select of known values — only surface if somehow invalid on submit.
|
|
79
|
+
if (showValidationErrors && !isValidBillingSubdivision(countryCode, billing.state)) {
|
|
80
|
+
return requiredMsg;
|
|
81
|
+
}
|
|
82
|
+
return undefined;
|
|
83
|
+
})();
|
|
84
|
+
const postalError = (() => {
|
|
85
|
+
if (!countryFields.needsPostal)
|
|
86
|
+
return undefined;
|
|
87
|
+
const zip = billing.zip.trim();
|
|
88
|
+
if (!zip) {
|
|
89
|
+
return showValidationErrors ? requiredMsg : undefined;
|
|
90
|
+
}
|
|
91
|
+
// Format check only after blur (or when Pay now forces validation).
|
|
92
|
+
if (!(postalBlurred || showValidationErrors))
|
|
93
|
+
return undefined;
|
|
94
|
+
if (!isValidBillingPostal(countryCode, zip)) {
|
|
95
|
+
return formatMessage(countryCode === 'US' ? messages.zipCodeInvalid : messages.postalCodeInvalid);
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
})();
|
|
76
99
|
const handleCountryChange = useCallback((country) => {
|
|
77
100
|
setField('country', country);
|
|
78
101
|
// Subdivision / postal values are country-specific — clear them on change.
|
|
79
102
|
setField('state', '');
|
|
80
103
|
setField('zip', '');
|
|
104
|
+
setPostalBlurred(false);
|
|
81
105
|
}, [setField]);
|
|
82
106
|
// This design has no terms/consent checkboxes, so there's nothing for the
|
|
83
107
|
// learner to opt into on screen — send a default consent record instead of
|
|
@@ -112,13 +136,17 @@ const PaymentForm = ({ billing, setField, numberMountRef, expiryMountRef, cvcMou
|
|
|
112
136
|
setField('state', e.target.value);
|
|
113
137
|
}, "aria-label": formatMessage(countryCode === 'CA' ? messages.province : messages.state), children: [_jsx("option", { value: "", disabled: true, hidden: true, children: formatMessage(countryCode === 'CA' ? messages.provincePlaceholder : messages.statePlaceholder) }), countryFields.subdivisions.map((subdivision) => (_jsx("option", { value: subdivision[0], children: subdivision[1] }, subdivision[0])))] }), stateError && (_jsxs("div", { className: "payment--field__error", role: "alert", children: [_jsx("span", { className: "payment--field__error-icon", "aria-hidden": "true" }), stateError] }))] })) : null;
|
|
114
138
|
const postalControl = countryFields.needsPostal ? (_jsxs(Form.Group, { controlId: "postal", className: `mb-0 payment__field--postal${postalError ? ' payment__field--postal-error' : ''}`, children: [_jsx(Form.Label, { className: "payment--field__label text-gray-900", children: formatMessage(countryCode === 'US' ? messages.zipCode : messages.postalCode) }), _jsx(Form.Control, { value: billing.zip, onChange: (e) => {
|
|
115
|
-
setField('zip', e.target.value);
|
|
116
|
-
|
|
139
|
+
setField('zip', sanitizeBillingPostalInput(countryCode, e.target.value));
|
|
140
|
+
// Hide format errors while typing; re-validate on the next blur.
|
|
141
|
+
setPostalBlurred(false);
|
|
142
|
+
}, onBlur: () => {
|
|
143
|
+
setPostalBlurred(true);
|
|
144
|
+
}, maxLength: (_a = getBillingPostalMaxLength(countryCode)) !== null && _a !== void 0 ? _a : undefined, placeholder: formatMessage(countryCode === 'US' ? messages.zipCodePlaceholder : messages.postalCodePlaceholder), "aria-label": formatMessage(countryCode === 'US' ? messages.zipCode : messages.postalCode), autoComplete: "postal-code" }), postalError && (_jsxs("div", { className: "payment--field__error", role: "alert", children: [_jsx("span", { className: "payment--field__error-icon", "aria-hidden": "true" }), postalError] }))] })) : null;
|
|
117
145
|
return (_jsxs("div", { className: "payment-form bg-white", children: [_jsxs("div", { children: [_jsx("h2", { className: "payment-form__heading font-weight-bold mb-3 text-primary-500", children: formatMessage(messages.selectPaymentMethod) }), _jsxs("p", { className: "payment-form__help small mb-0", children: [_jsx("strong", { className: "d-block text-primary-500", children: formatMessage(messages.needAssistance) }), ' ', _jsx("span", { className: "text-gray-700 gap-y-2", children: formatMessage(messages.needAssistanceBody, {
|
|
118
146
|
link: (_jsx("a", { href: LEARNER_HELP_CENTER_URL, target: "_blank", rel: "noreferrer", className: "text-gray-700", children: formatMessage(messages.learnerHelpCenter) })),
|
|
119
147
|
}) })] })] }), _jsx(ExpressCheckout, { stripe: stripe, totalAmountCents: (alacarteLine === null || alacarteLine === void 0 ? void 0 : alacarteLine.amountDue)
|
|
120
148
|
? Math.round(alacarteLine.amountDue.amount * 100)
|
|
121
|
-
: null, currency: (
|
|
149
|
+
: null, currency: (_c = (_b = alacarteLine === null || alacarteLine === void 0 ? void 0 : alacarteLine.amountDue) === null || _b === void 0 ? void 0 : _b.currency) !== null && _c !== void 0 ? _c : null, country: expressCountry, onPaymentAuthorized: onExpressPayment !== null && onExpressPayment !== void 0 ? onExpressPayment : (() => __awaiter(void 0, void 0, void 0, function* () { })),
|
|
122
150
|
// Wallets need a real place-order handler (à la carte ready) — a missing
|
|
123
151
|
// handler used to no-op after wallet auth and mark the sheet successful.
|
|
124
152
|
disabled: isDisabled || !onExpressPayment }), _jsx("div", { className: "payment-form__methods", children: _jsx("div", { className: "payment-form__method payment-form__method--selected", children: _jsxs("div", { className: "payment-form__card-panel", children: [_jsx("h3", { className: "payment-form__card-heading font-weight-bold text-center text-primary-500 mb-3", children: formatMessage(messages.payWithCard) }), _jsx(PaymentFields, { numberMountRef: numberMountRef, expiryMountRef: expiryMountRef, cvcMountRef: cvcMountRef, fieldErrors: fieldErrors }), _jsx("div", { className: "payment-form__billing mt-3", children: countryFields.countryFullWidth ? (_jsxs(_Fragment, { children: [countryControl, (stateControl || postalControl) && (_jsxs("div", { className: "payment-form__billing-row", children: [stateControl, postalControl] }))] })) : (_jsxs("div", { className: `payment-form__billing-row${!stateControl && !postalControl ? ' payment-form__billing-row--single' : ''}`, children: [countryControl, stateControl, postalControl] })) })] }) }) }), _jsxs("div", { className: "payment-form__total", children: [_jsx("p", { className: "payment-form__total-disclaimer text-gray-700 mb-3", children: formatMessage(messages.orderDisclaimer) }), _jsxs("div", { className: "payment-form__total-row", children: [_jsxs("p", { className: "mb-0 text-gray-900", children: [_jsx("strong", { className: "payment-form__total-label", children: formatMessage(messages.total) }), ' ', _jsx("span", { className: "payment-form__total-value", children: totalText })] }), _jsx(Button, { variant: "brand", className: "payment-form__submit", onClick: handlePayNow, disabled: payNowDisabled, children: formatMessage(submitting ? messages.processing : messages.payNow) })] })] })] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PaymentForm.js","sourceRoot":"","sources":["../../../src/components/PaymentForm/PaymentForm.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useState, useCallback } from 'react';\nimport type { Ref } from 'react';\nimport { Button, Form } from '@openedx/paragon';\nimport { useIntl } from '@openedx/frontend-base';\nimport PaymentFields from '../PaymentFields/PaymentFields';\nimport type { PaymentFieldErrors } from '../PaymentFields/PaymentFields';\nimport ExpressCheckout from '../ExpressCheckout/ExpressCheckout';\nimport type { BillingInfo, CardFieldStates, CheckoutConsent, StripeLike, SupportedPaymentMethod } from '../../types';\nimport type { AlacarteLine } from '../../types/alacarte';\nimport { BILLING_COUNTRIES, LEARNER_HELP_CENTER_URL } from '../../constants';\nimport { getBillingCountryFields, hasRequiredBillingFields } from '../../utils/billingAddress';\nimport messages from './Messages';\n\nexport interface PaymentFormProps {\n billing: BillingInfo,\n setField: (field: keyof BillingInfo, value: string) => void,\n numberMountRef: Ref<HTMLDivElement>,\n expiryMountRef: Ref<HTMLDivElement>,\n cvcMountRef: Ref<HTMLDivElement>,\n cardError: string,\n stripeReady: boolean,\n sessionReady: boolean,\n submitting: boolean,\n onPlaceOrder: (consent: CheckoutConsent, paymentMethod: SupportedPaymentMethod) => void,\n /** @deprecated Subscription plan price — unused for à la carte (see alacarteLine). */\n priceToShow?: { label?: string, amount?: string } | null,\n cycleToShow?: string,\n trialEnd?: string,\n userCountry?: string | null,\n /** Promotional monthly price e.g. \"39\" — monthly plans only. */\n promoAmount?: string,\n /** Length of the promotional period in months. */\n promoMonths?: number,\n /** Regular monthly price after the promo period e.g. \"59\". */\n monthlyRegularAmount?: string,\n geolocationUrl?: string,\n /** À la carte checkout line — when present, drives the order total shown. */\n alacarteLine?: AlacarteLine | null,\n /** Stripe.js instance for express checkout (Apple Pay / Google Pay). */\n stripe?: StripeLike | null,\n /** Two-letter country code for express checkout initialisation. */\n expressCountry?: string,\n /** Called when the user authorises a wallet payment. */\n onExpressPayment?: (paymentMethodId: string, walletCountry?: string) => Promise<void>,\n /** Tracks empty/complete state of each Stripe card element for validation. */\n cardFieldStates?: CardFieldStates,\n /** Called when Pay Now validation fails or succeeds — lets the parent show/hide a banner. */\n onValidationError?: (hasErrors: boolean) => void,\n}\n\nconst PaymentForm = ({\n billing,\n setField,\n numberMountRef,\n expiryMountRef,\n cvcMountRef,\n stripeReady,\n sessionReady,\n submitting,\n onPlaceOrder,\n alacarteLine = null,\n stripe = null,\n expressCountry = 'US',\n onExpressPayment,\n cardFieldStates,\n onValidationError,\n}: PaymentFormProps) => {\n const { formatMessage } = useIntl();\n const [showValidationErrors, setShowValidationErrors] = useState(false);\n\n // No fallback amount — an unresolved à la carte line means we don't know\n // the price yet, so this is left blank rather than showing a made-up number.\n const totalText = alacarteLine?.amountDue\n ? formatMessage(messages.totalAmount, {\n amount: alacarteLine.amountDue.amount.toFixed(2), currency: alacarteLine.amountDue.currency,\n })\n : '';\n\n const sessionBusy = submitting || !stripeReady || !sessionReady;\n const countryCode = billing.country.trim().toUpperCase();\n const countryFields = getBillingCountryFields(countryCode);\n\n const cardFieldsValid = !!cardFieldStates\n && cardFieldStates.number.complete\n && !cardFieldStates.number.empty\n && !cardFieldStates.number.error\n && cardFieldStates.expiry.complete\n && !cardFieldStates.expiry.empty\n && !cardFieldStates.expiry.error\n && cardFieldStates.cvc.complete\n && !cardFieldStates.cvc.empty\n && !cardFieldStates.cvc.error;\n const billingValid = hasRequiredBillingFields(billing);\n // Pay now stays disabled until Stripe Elements report complete, valid fields\n // and required billing fields are filled. Express checkout only needs session readiness.\n const isDisabled = sessionBusy;\n const payNowDisabled = sessionBusy || !cardFieldsValid || !billingValid;\n\n const requiredMsg = formatMessage(messages.fieldRequired);\n\n // Per-field error: show Stripe's inline error on blur, or \"required\" on submit.\n const fieldErrorFor = (field: 'number' | 'expiry' | 'cvc'): string | undefined => {\n const state = cardFieldStates?.[field];\n if (!state) return undefined;\n // Submit-time: empty fields show \"This field is required\"\n if (showValidationErrors && state.empty) return requiredMsg;\n // Blur-time: show Stripe's validation error once the user has left the field\n if (state.blurred && state.error) return state.error;\n return undefined;\n };\n\n const fieldErrors: PaymentFieldErrors | undefined = cardFieldStates\n ? {\n number: fieldErrorFor('number'),\n expiry: fieldErrorFor('expiry'),\n cvc: fieldErrorFor('cvc'),\n }\n : undefined;\n\n const countryError = showValidationErrors && !billing.country ? requiredMsg : undefined;\n const stateError = showValidationErrors && countryFields.needsState && !billing.state.trim()\n ? requiredMsg\n : undefined;\n const postalError = showValidationErrors && countryFields.needsPostal && !billing.zip.trim()\n ? requiredMsg\n : undefined;\n\n const handleCountryChange = useCallback((country: string) => {\n setField('country', country);\n // Subdivision / postal values are country-specific — clear them on change.\n setField('state', '');\n setField('zip', '');\n }, [setField]);\n\n // This design has no terms/consent checkboxes, so there's nothing for the\n // learner to opt into on screen — send a default consent record instead of\n // building it from UI state that doesn't exist here.\n const handlePayNow = useCallback(() => {\n // Check if any required field is empty.\n const numberEmpty = cardFieldStates?.number.empty ?? true;\n const expiryEmpty = cardFieldStates?.expiry.empty ?? true;\n const cvcEmpty = cardFieldStates?.cvc.empty ?? true;\n const billingMissing = !hasRequiredBillingFields(billing);\n\n if (numberEmpty || expiryEmpty || cvcEmpty || billingMissing) {\n setShowValidationErrors(true);\n onValidationError?.(true);\n return;\n }\n\n setShowValidationErrors(false);\n onValidationError?.(false);\n onPlaceOrder({\n tConsentRecurring: true,\n tConsentUkCa: null,\n tConsentAt: new Date().toISOString(),\n tConsentText: '',\n }, 'card');\n }, [cardFieldStates, billing, onPlaceOrder, onValidationError]);\n\n const countryControl = (\n <Form.Group\n controlId=\"country\"\n className={`mb-0 payment__field--country${countryError ? ' payment__field--country-error' : ''}`}\n >\n <Form.Label className=\"payment--field__label text-gray-900\">\n {formatMessage(messages.country)}\n </Form.Label>\n <Form.Control\n as=\"select\"\n value={billing.country}\n onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {\n handleCountryChange(e.target.value);\n }}\n aria-label={formatMessage(messages.country)}\n >\n <option value=\"\" disabled hidden>\n {formatMessage(messages.countryPlaceholder)}\n </option>\n {BILLING_COUNTRIES.map((country) => {\n return (\n <option key={country[0]} value={country[0]}>\n {country[1]}\n </option>\n );\n })}\n </Form.Control>\n {countryError && (\n <div className=\"payment--field__error\" role=\"alert\">\n <span className=\"payment--field__error-icon\" aria-hidden=\"true\" />\n {countryError}\n </div>\n )}\n </Form.Group>\n );\n\n const stateControl = countryFields.needsState && countryFields.subdivisions ? (\n <Form.Group\n controlId=\"state\"\n className={`mb-0 payment__field--state${stateError ? ' payment__field--state-error' : ''}`}\n >\n <Form.Label className=\"payment--field__label text-gray-900\">\n {formatMessage(countryCode === 'CA' ? messages.province : messages.state)}\n </Form.Label>\n <Form.Control\n as=\"select\"\n value={billing.state}\n onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {\n setField('state', e.target.value);\n }}\n aria-label={formatMessage(countryCode === 'CA' ? messages.province : messages.state)}\n >\n <option value=\"\" disabled hidden>\n {formatMessage(\n countryCode === 'CA' ? messages.provincePlaceholder : messages.statePlaceholder,\n )}\n </option>\n {countryFields.subdivisions.map((subdivision) => (\n <option key={subdivision[0]} value={subdivision[0]}>\n {subdivision[1]}\n </option>\n ))}\n </Form.Control>\n {stateError && (\n <div className=\"payment--field__error\" role=\"alert\">\n <span className=\"payment--field__error-icon\" aria-hidden=\"true\" />\n {stateError}\n </div>\n )}\n </Form.Group>\n ) : null;\n\n const postalControl = countryFields.needsPostal ? (\n <Form.Group\n controlId=\"postal\"\n className={`mb-0 payment__field--postal${postalError ? ' payment__field--postal-error' : ''}`}\n >\n <Form.Label className=\"payment--field__label text-gray-900\">\n {formatMessage(countryCode === 'US' ? messages.zipCode : messages.postalCode)}\n </Form.Label>\n <Form.Control\n value={billing.zip}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => {\n setField('zip', e.target.value);\n }}\n placeholder={formatMessage(\n countryCode === 'US' ? messages.zipCodePlaceholder : messages.postalCodePlaceholder,\n )}\n aria-label={formatMessage(countryCode === 'US' ? messages.zipCode : messages.postalCode)}\n autoComplete=\"postal-code\"\n />\n {postalError && (\n <div className=\"payment--field__error\" role=\"alert\">\n <span className=\"payment--field__error-icon\" aria-hidden=\"true\" />\n {postalError}\n </div>\n )}\n </Form.Group>\n ) : null;\n\n return (\n <div className=\"payment-form bg-white\">\n <div>\n <h2 className=\"payment-form__heading font-weight-bold mb-3 text-primary-500\">\n {formatMessage(messages.selectPaymentMethod)}\n </h2>\n\n <p className=\"payment-form__help small mb-0\">\n <strong className=\"d-block text-primary-500\">\n {formatMessage(messages.needAssistance)}\n </strong>{' '}\n <span className=\"text-gray-700 gap-y-2\">\n {formatMessage(messages.needAssistanceBody, {\n link: (\n <a\n href={LEARNER_HELP_CENTER_URL}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-gray-700\"\n >\n {formatMessage(messages.learnerHelpCenter)}\n </a>\n ),\n })}\n </span>\n </p>\n </div>\n\n <ExpressCheckout\n stripe={stripe}\n totalAmountCents={\n alacarteLine?.amountDue\n ? Math.round(alacarteLine.amountDue.amount * 100)\n : null\n }\n currency={alacarteLine?.amountDue?.currency ?? null}\n country={expressCountry}\n onPaymentAuthorized={onExpressPayment ?? (async () => {})}\n // Wallets need a real place-order handler (à la carte ready) — a missing\n // handler used to no-op after wallet auth and mark the sheet successful.\n disabled={isDisabled || !onExpressPayment}\n />\n\n {/* ── Payment method ── */}\n <div className=\"payment-form__methods\">\n <div className=\"payment-form__method payment-form__method--selected\">\n <div className=\"payment-form__card-panel\">\n <h3 className=\"payment-form__card-heading font-weight-bold text-center text-primary-500 mb-3\">\n {formatMessage(messages.payWithCard)}\n </h3>\n <PaymentFields\n numberMountRef={numberMountRef}\n expiryMountRef={expiryMountRef}\n cvcMountRef={cvcMountRef}\n fieldErrors={fieldErrors}\n />\n <div className=\"payment-form__billing mt-3\">\n {countryFields.countryFullWidth ? (\n <>\n {countryControl}\n {(stateControl || postalControl) && (\n <div className=\"payment-form__billing-row\">\n {stateControl}\n {postalControl}\n </div>\n )}\n </>\n ) : (\n <div className={`payment-form__billing-row${\n !stateControl && !postalControl ? ' payment-form__billing-row--single' : ''\n }`}\n >\n {countryControl}\n {stateControl}\n {postalControl}\n </div>\n )}\n </div>\n </div>\n </div>\n </div>\n\n {/* ── Order total ── */}\n <div className=\"payment-form__total\">\n <p className=\"payment-form__total-disclaimer text-gray-700 mb-3\">\n {formatMessage(messages.orderDisclaimer)}\n </p>\n <div className=\"payment-form__total-row\">\n <p className=\"mb-0 text-gray-900\">\n <strong className=\"payment-form__total-label\">{formatMessage(messages.total)}</strong>{' '}\n <span className=\"payment-form__total-value\">\n {totalText}\n </span>\n </p>\n <Button\n variant=\"brand\"\n className=\"payment-form__submit\"\n onClick={handlePayNow}\n disabled={payNowDisabled}\n >\n {formatMessage(submitting ? messages.processing : messages.payNow)}\n </Button>\n </div>\n </div>\n </div>\n );\n};\n\nexport default PaymentForm;\n"]}
|
|
1
|
+
{"version":3,"file":"PaymentForm.js","sourceRoot":"","sources":["../../../src/components/PaymentForm/PaymentForm.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useState, useCallback } from 'react';\nimport type { Ref } from 'react';\nimport { Button, Form } from '@openedx/paragon';\nimport { useIntl } from '@openedx/frontend-base';\nimport PaymentFields from '../PaymentFields/PaymentFields';\nimport type { PaymentFieldErrors } from '../PaymentFields/PaymentFields';\nimport ExpressCheckout from '../ExpressCheckout/ExpressCheckout';\nimport type { BillingInfo, CardFieldStates, CheckoutConsent, StripeLike, SupportedPaymentMethod } from '../../types';\nimport type { AlacarteLine } from '../../types/alacarte';\nimport { BILLING_COUNTRIES, LEARNER_HELP_CENTER_URL } from '../../constants';\nimport {\n getBillingCountryFields,\n getBillingPostalMaxLength,\n hasRequiredBillingFields,\n isValidBillingPostal,\n isValidBillingSubdivision,\n sanitizeBillingPostalInput,\n} from '../../utils/billingAddress';\nimport messages from './Messages';\n\nexport interface PaymentFormProps {\n billing: BillingInfo,\n setField: (field: keyof BillingInfo, value: string) => void,\n numberMountRef: Ref<HTMLDivElement>,\n expiryMountRef: Ref<HTMLDivElement>,\n cvcMountRef: Ref<HTMLDivElement>,\n cardError: string,\n stripeReady: boolean,\n sessionReady: boolean,\n submitting: boolean,\n onPlaceOrder: (consent: CheckoutConsent, paymentMethod: SupportedPaymentMethod) => void,\n /** @deprecated Subscription plan price — unused for à la carte (see alacarteLine). */\n priceToShow?: { label?: string, amount?: string } | null,\n cycleToShow?: string,\n trialEnd?: string,\n userCountry?: string | null,\n /** Promotional monthly price e.g. \"39\" — monthly plans only. */\n promoAmount?: string,\n /** Length of the promotional period in months. */\n promoMonths?: number,\n /** Regular monthly price after the promo period e.g. \"59\". */\n monthlyRegularAmount?: string,\n geolocationUrl?: string,\n /** À la carte checkout line — when present, drives the order total shown. */\n alacarteLine?: AlacarteLine | null,\n /** Stripe.js instance for express checkout (Apple Pay / Google Pay). */\n stripe?: StripeLike | null,\n /** Two-letter country code for express checkout initialisation. */\n expressCountry?: string,\n /** Called when the user authorises a wallet payment. */\n onExpressPayment?: (paymentMethodId: string, walletCountry?: string) => Promise<void>,\n /** Tracks empty/complete state of each Stripe card element for validation. */\n cardFieldStates?: CardFieldStates,\n /** Called when Pay Now validation fails or succeeds — lets the parent show/hide a banner. */\n onValidationError?: (hasErrors: boolean) => void,\n}\n\nconst PaymentForm = ({\n billing,\n setField,\n numberMountRef,\n expiryMountRef,\n cvcMountRef,\n stripeReady,\n sessionReady,\n submitting,\n onPlaceOrder,\n alacarteLine = null,\n stripe = null,\n expressCountry = 'US',\n onExpressPayment,\n cardFieldStates,\n onValidationError,\n}: PaymentFormProps) => {\n const { formatMessage } = useIntl();\n const [showValidationErrors, setShowValidationErrors] = useState(false);\n /** Postal format errors only after blur — cleared again while the user types. */\n const [postalBlurred, setPostalBlurred] = useState(false);\n\n // No fallback amount — an unresolved à la carte line means we don't know\n // the price yet, so this is left blank rather than showing a made-up number.\n const totalText = alacarteLine?.amountDue\n ? formatMessage(messages.totalAmount, {\n amount: alacarteLine.amountDue.amount.toFixed(2), currency: alacarteLine.amountDue.currency,\n })\n : '';\n\n const sessionBusy = submitting || !stripeReady || !sessionReady;\n const countryCode = billing.country.trim().toUpperCase();\n const countryFields = getBillingCountryFields(countryCode);\n\n const cardFieldsValid = !!cardFieldStates\n && cardFieldStates.number.complete\n && !cardFieldStates.number.empty\n && !cardFieldStates.number.error\n && cardFieldStates.expiry.complete\n && !cardFieldStates.expiry.empty\n && !cardFieldStates.expiry.error\n && cardFieldStates.cvc.complete\n && !cardFieldStates.cvc.empty\n && !cardFieldStates.cvc.error;\n const billingValid = hasRequiredBillingFields(billing);\n // Pay now stays disabled until Stripe Elements report complete, valid fields\n // and required billing fields are filled. Express checkout only needs session readiness.\n const isDisabled = sessionBusy;\n const payNowDisabled = sessionBusy || !cardFieldsValid || !billingValid;\n\n const requiredMsg = formatMessage(messages.fieldRequired);\n\n // Per-field error: show Stripe's inline error on blur, or \"required\" on submit.\n const fieldErrorFor = (field: 'number' | 'expiry' | 'cvc'): string | undefined => {\n const state = cardFieldStates?.[field];\n if (!state) return undefined;\n // Submit-time: empty fields show \"This field is required\"\n if (showValidationErrors && state.empty) return requiredMsg;\n // Blur-time: show Stripe's validation error once the user has left the field\n if (state.blurred && state.error) return state.error;\n return undefined;\n };\n\n const fieldErrors: PaymentFieldErrors | undefined = cardFieldStates\n ? {\n number: fieldErrorFor('number'),\n expiry: fieldErrorFor('expiry'),\n cvc: fieldErrorFor('cvc'),\n }\n : undefined;\n\n const countryError = showValidationErrors && !billing.country ? requiredMsg : undefined;\n\n const stateError = (() => {\n if (!countryFields.needsState) return undefined;\n if (!billing.state.trim()) {\n return showValidationErrors ? requiredMsg : undefined;\n }\n // Subdivision is a select of known values — only surface if somehow invalid on submit.\n if (showValidationErrors && !isValidBillingSubdivision(countryCode, billing.state)) {\n return requiredMsg;\n }\n return undefined;\n })();\n\n const postalError = (() => {\n if (!countryFields.needsPostal) return undefined;\n const zip = billing.zip.trim();\n if (!zip) {\n return showValidationErrors ? requiredMsg : undefined;\n }\n // Format check only after blur (or when Pay now forces validation).\n if (!(postalBlurred || showValidationErrors)) return undefined;\n if (!isValidBillingPostal(countryCode, zip)) {\n return formatMessage(\n countryCode === 'US' ? messages.zipCodeInvalid : messages.postalCodeInvalid,\n );\n }\n return undefined;\n })();\n\n const handleCountryChange = useCallback((country: string) => {\n setField('country', country);\n // Subdivision / postal values are country-specific — clear them on change.\n setField('state', '');\n setField('zip', '');\n setPostalBlurred(false);\n }, [setField]);\n\n // This design has no terms/consent checkboxes, so there's nothing for the\n // learner to opt into on screen — send a default consent record instead of\n // building it from UI state that doesn't exist here.\n const handlePayNow = useCallback(() => {\n // Check if any required field is empty.\n const numberEmpty = cardFieldStates?.number.empty ?? true;\n const expiryEmpty = cardFieldStates?.expiry.empty ?? true;\n const cvcEmpty = cardFieldStates?.cvc.empty ?? true;\n const billingMissing = !hasRequiredBillingFields(billing);\n\n if (numberEmpty || expiryEmpty || cvcEmpty || billingMissing) {\n setShowValidationErrors(true);\n onValidationError?.(true);\n return;\n }\n\n setShowValidationErrors(false);\n onValidationError?.(false);\n onPlaceOrder({\n tConsentRecurring: true,\n tConsentUkCa: null,\n tConsentAt: new Date().toISOString(),\n tConsentText: '',\n }, 'card');\n }, [cardFieldStates, billing, onPlaceOrder, onValidationError]);\n\n const countryControl = (\n <Form.Group\n controlId=\"country\"\n className={`mb-0 payment__field--country${countryError ? ' payment__field--country-error' : ''}`}\n >\n <Form.Label className=\"payment--field__label text-gray-900\">\n {formatMessage(messages.country)}\n </Form.Label>\n <Form.Control\n as=\"select\"\n value={billing.country}\n onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {\n handleCountryChange(e.target.value);\n }}\n aria-label={formatMessage(messages.country)}\n >\n <option value=\"\" disabled hidden>\n {formatMessage(messages.countryPlaceholder)}\n </option>\n {BILLING_COUNTRIES.map((country) => {\n return (\n <option key={country[0]} value={country[0]}>\n {country[1]}\n </option>\n );\n })}\n </Form.Control>\n {countryError && (\n <div className=\"payment--field__error\" role=\"alert\">\n <span className=\"payment--field__error-icon\" aria-hidden=\"true\" />\n {countryError}\n </div>\n )}\n </Form.Group>\n );\n\n const stateControl = countryFields.needsState && countryFields.subdivisions ? (\n <Form.Group\n controlId=\"state\"\n className={`mb-0 payment__field--state${stateError ? ' payment__field--state-error' : ''}`}\n >\n <Form.Label className=\"payment--field__label text-gray-900\">\n {formatMessage(countryCode === 'CA' ? messages.province : messages.state)}\n </Form.Label>\n <Form.Control\n as=\"select\"\n value={billing.state}\n onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {\n setField('state', e.target.value);\n }}\n aria-label={formatMessage(countryCode === 'CA' ? messages.province : messages.state)}\n >\n <option value=\"\" disabled hidden>\n {formatMessage(\n countryCode === 'CA' ? messages.provincePlaceholder : messages.statePlaceholder,\n )}\n </option>\n {countryFields.subdivisions.map((subdivision) => (\n <option key={subdivision[0]} value={subdivision[0]}>\n {subdivision[1]}\n </option>\n ))}\n </Form.Control>\n {stateError && (\n <div className=\"payment--field__error\" role=\"alert\">\n <span className=\"payment--field__error-icon\" aria-hidden=\"true\" />\n {stateError}\n </div>\n )}\n </Form.Group>\n ) : null;\n\n const postalControl = countryFields.needsPostal ? (\n <Form.Group\n controlId=\"postal\"\n className={`mb-0 payment__field--postal${postalError ? ' payment__field--postal-error' : ''}`}\n >\n <Form.Label className=\"payment--field__label text-gray-900\">\n {formatMessage(countryCode === 'US' ? messages.zipCode : messages.postalCode)}\n </Form.Label>\n <Form.Control\n value={billing.zip}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) => {\n setField('zip', sanitizeBillingPostalInput(countryCode, e.target.value));\n // Hide format errors while typing; re-validate on the next blur.\n setPostalBlurred(false);\n }}\n onBlur={() => {\n setPostalBlurred(true);\n }}\n maxLength={getBillingPostalMaxLength(countryCode) ?? undefined}\n placeholder={formatMessage(\n countryCode === 'US' ? messages.zipCodePlaceholder : messages.postalCodePlaceholder,\n )}\n aria-label={formatMessage(countryCode === 'US' ? messages.zipCode : messages.postalCode)}\n autoComplete=\"postal-code\"\n />\n {postalError && (\n <div className=\"payment--field__error\" role=\"alert\">\n <span className=\"payment--field__error-icon\" aria-hidden=\"true\" />\n {postalError}\n </div>\n )}\n </Form.Group>\n ) : null;\n\n return (\n <div className=\"payment-form bg-white\">\n <div>\n <h2 className=\"payment-form__heading font-weight-bold mb-3 text-primary-500\">\n {formatMessage(messages.selectPaymentMethod)}\n </h2>\n\n <p className=\"payment-form__help small mb-0\">\n <strong className=\"d-block text-primary-500\">\n {formatMessage(messages.needAssistance)}\n </strong>{' '}\n <span className=\"text-gray-700 gap-y-2\">\n {formatMessage(messages.needAssistanceBody, {\n link: (\n <a\n href={LEARNER_HELP_CENTER_URL}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-gray-700\"\n >\n {formatMessage(messages.learnerHelpCenter)}\n </a>\n ),\n })}\n </span>\n </p>\n </div>\n\n <ExpressCheckout\n stripe={stripe}\n totalAmountCents={\n alacarteLine?.amountDue\n ? Math.round(alacarteLine.amountDue.amount * 100)\n : null\n }\n currency={alacarteLine?.amountDue?.currency ?? null}\n country={expressCountry}\n onPaymentAuthorized={onExpressPayment ?? (async () => {})}\n // Wallets need a real place-order handler (à la carte ready) — a missing\n // handler used to no-op after wallet auth and mark the sheet successful.\n disabled={isDisabled || !onExpressPayment}\n />\n\n {/* ── Payment method ── */}\n <div className=\"payment-form__methods\">\n <div className=\"payment-form__method payment-form__method--selected\">\n <div className=\"payment-form__card-panel\">\n <h3 className=\"payment-form__card-heading font-weight-bold text-center text-primary-500 mb-3\">\n {formatMessage(messages.payWithCard)}\n </h3>\n <PaymentFields\n numberMountRef={numberMountRef}\n expiryMountRef={expiryMountRef}\n cvcMountRef={cvcMountRef}\n fieldErrors={fieldErrors}\n />\n <div className=\"payment-form__billing mt-3\">\n {countryFields.countryFullWidth ? (\n <>\n {countryControl}\n {(stateControl || postalControl) && (\n <div className=\"payment-form__billing-row\">\n {stateControl}\n {postalControl}\n </div>\n )}\n </>\n ) : (\n <div className={`payment-form__billing-row${\n !stateControl && !postalControl ? ' payment-form__billing-row--single' : ''\n }`}\n >\n {countryControl}\n {stateControl}\n {postalControl}\n </div>\n )}\n </div>\n </div>\n </div>\n </div>\n\n {/* ── Order total ── */}\n <div className=\"payment-form__total\">\n <p className=\"payment-form__total-disclaimer text-gray-700 mb-3\">\n {formatMessage(messages.orderDisclaimer)}\n </p>\n <div className=\"payment-form__total-row\">\n <p className=\"mb-0 text-gray-900\">\n <strong className=\"payment-form__total-label\">{formatMessage(messages.total)}</strong>{' '}\n <span className=\"payment-form__total-value\">\n {totalText}\n </span>\n </p>\n <Button\n variant=\"brand\"\n className=\"payment-form__submit\"\n onClick={handlePayNow}\n disabled={payNowDisabled}\n >\n {formatMessage(submitting ? messages.processing : messages.payNow)}\n </Button>\n </div>\n </div>\n </div>\n );\n};\n\nexport default PaymentForm;\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polling rules for tPlacedOrder / getTwouOrder after the learner pays.
|
|
3
|
+
*
|
|
4
|
+
* PAYMENT_PENDING means the payment is still being confirmed (including a
|
|
5
|
+
* 3D Secure check). Poll for up to 30 seconds. The next read is one of:
|
|
6
|
+
* - ok: true with the order — payment went through. Show confirmation for
|
|
7
|
+
* ORDER_PLACED, ORDER_FULFILLING, ORDER_COMPLETED, and
|
|
8
|
+
* ORDER_FULFILMENT_FAILED. All four are paid; status only tracks enrollment.
|
|
9
|
+
* - error: NOT_PLACED — the payment did not go through. The cart is editable
|
|
10
|
+
* again (tCheckout includes paymentDecline). Leave the poll and return
|
|
11
|
+
* the learner to checkout.
|
|
12
|
+
*/
|
|
13
|
+
import type { OrderDatesResult, PlacedOrder, PlacedOrderResult } from '../../types/alacarte';
|
|
14
|
+
export declare const PLACED_ORDER_POLL_INTERVAL_MS = 2000;
|
|
15
|
+
export declare const PLACED_ORDER_POLL_TIMEOUT_MS = 30000;
|
|
16
|
+
/** Paid placements. Fulfillment progress does not change the confirmation. */
|
|
17
|
+
export declare const PAID_PLACED_ORDER_STATUSES: readonly ["ORDER_PLACED", "ORDER_FULFILLING", "ORDER_COMPLETED", "ORDER_FULFILMENT_FAILED"];
|
|
18
|
+
export declare function isPaidPlacedOrder(result: PlacedOrderResult | undefined): result is PlacedOrderResult & {
|
|
19
|
+
order: PlacedOrder;
|
|
20
|
+
};
|
|
21
|
+
/** How long to wait before the next tPlacedOrder read, or false to stop. */
|
|
22
|
+
export declare function placedOrderRefetchInterval(result: PlacedOrderResult | undefined, elapsedMs: number): number | false;
|
|
23
|
+
/** Keep reading getTwouOrder until tPlacedAt is set, or the same 30s window ends. */
|
|
24
|
+
export declare function orderDatesRefetchInterval(result: OrderDatesResult | undefined, elapsedMs: number): number | false;
|
|
25
|
+
export interface PlacedOrderView {
|
|
26
|
+
placedOrder: PlacedOrder | null;
|
|
27
|
+
placedOrderError: string | null;
|
|
28
|
+
awaitingPlacedOrder: boolean;
|
|
29
|
+
/** NOT_PLACED: stop waiting and send the learner back to the editable cart. */
|
|
30
|
+
returnToCheckout: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare function derivePlacedOrderView(args: {
|
|
33
|
+
orderBiId: string | null;
|
|
34
|
+
result: PlacedOrderResult | undefined;
|
|
35
|
+
pollTimedOut: boolean;
|
|
36
|
+
isLoading: boolean;
|
|
37
|
+
isFetching: boolean;
|
|
38
|
+
}): PlacedOrderView;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export const PLACED_ORDER_POLL_INTERVAL_MS = 2000;
|
|
2
|
+
export const PLACED_ORDER_POLL_TIMEOUT_MS = 30000;
|
|
3
|
+
/** Paid placements. Fulfillment progress does not change the confirmation. */
|
|
4
|
+
export const PAID_PLACED_ORDER_STATUSES = [
|
|
5
|
+
'ORDER_PLACED',
|
|
6
|
+
'ORDER_FULFILLING',
|
|
7
|
+
'ORDER_COMPLETED',
|
|
8
|
+
'ORDER_FULFILMENT_FAILED',
|
|
9
|
+
];
|
|
10
|
+
const TRANSIENT_PLACED_ORDER_ERRORS = new Set([
|
|
11
|
+
'PAYMENT_PENDING',
|
|
12
|
+
'ORDER_READ_FAILED',
|
|
13
|
+
]);
|
|
14
|
+
export function isPaidPlacedOrder(result) {
|
|
15
|
+
return !!(result === null || result === void 0 ? void 0 : result.ok) && !!result.order;
|
|
16
|
+
}
|
|
17
|
+
/** How long to wait before the next tPlacedOrder read, or false to stop. */
|
|
18
|
+
export function placedOrderRefetchInterval(result, elapsedMs) {
|
|
19
|
+
if (isPaidPlacedOrder(result))
|
|
20
|
+
return false;
|
|
21
|
+
// Declined (or otherwise not placed): cart is editable again. Do not keep polling.
|
|
22
|
+
if (result && !result.ok && result.error === 'NOT_PLACED')
|
|
23
|
+
return false;
|
|
24
|
+
if (elapsedMs >= PLACED_ORDER_POLL_TIMEOUT_MS)
|
|
25
|
+
return false;
|
|
26
|
+
if (!result || (result.ok && !result.order))
|
|
27
|
+
return PLACED_ORDER_POLL_INTERVAL_MS;
|
|
28
|
+
if (!result.ok && result.error && TRANSIENT_PLACED_ORDER_ERRORS.has(result.error)) {
|
|
29
|
+
return PLACED_ORDER_POLL_INTERVAL_MS;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
/** Keep reading getTwouOrder until tPlacedAt is set, or the same 30s window ends. */
|
|
34
|
+
export function orderDatesRefetchInterval(result, elapsedMs) {
|
|
35
|
+
var _a;
|
|
36
|
+
if ((result === null || result === void 0 ? void 0 : result.ok) && ((_a = result.dates) === null || _a === void 0 ? void 0 : _a.tPlacedAt))
|
|
37
|
+
return false;
|
|
38
|
+
if (elapsedMs >= PLACED_ORDER_POLL_TIMEOUT_MS)
|
|
39
|
+
return false;
|
|
40
|
+
return PLACED_ORDER_POLL_INTERVAL_MS;
|
|
41
|
+
}
|
|
42
|
+
export function derivePlacedOrderView(args) {
|
|
43
|
+
var _a, _b;
|
|
44
|
+
const { orderBiId, result, pollTimedOut, isLoading, isFetching, } = args;
|
|
45
|
+
const placedOrder = isPaidPlacedOrder(result) ? result.order : null;
|
|
46
|
+
const returnToCheckout = !!orderBiId
|
|
47
|
+
&& !placedOrder
|
|
48
|
+
&& (result === null || result === void 0 ? void 0 : result.ok) === false
|
|
49
|
+
&& result.error === 'NOT_PLACED';
|
|
50
|
+
let placedOrderError = null;
|
|
51
|
+
if (orderBiId && !placedOrder && !returnToCheckout && result && !result.ok) {
|
|
52
|
+
const error = (_a = result.error) !== null && _a !== void 0 ? _a : 'ORDER_READ_FAILED';
|
|
53
|
+
const stillWaiting = TRANSIENT_PLACED_ORDER_ERRORS.has(error) && !pollTimedOut;
|
|
54
|
+
if (!stillWaiting)
|
|
55
|
+
placedOrderError = error;
|
|
56
|
+
}
|
|
57
|
+
else if (orderBiId
|
|
58
|
+
&& !placedOrder
|
|
59
|
+
&& !returnToCheckout
|
|
60
|
+
&& pollTimedOut
|
|
61
|
+
&& !(result === null || result === void 0 ? void 0 : result.ok)) {
|
|
62
|
+
placedOrderError = (_b = result === null || result === void 0 ? void 0 : result.error) !== null && _b !== void 0 ? _b : 'PAYMENT_PENDING';
|
|
63
|
+
}
|
|
64
|
+
const stillPending = !!result
|
|
65
|
+
&& !result.ok
|
|
66
|
+
&& !!result.error
|
|
67
|
+
&& TRANSIENT_PLACED_ORDER_ERRORS.has(result.error);
|
|
68
|
+
const awaitingPlacedOrder = !!orderBiId && !placedOrder && !placedOrderError && (returnToCheckout
|
|
69
|
+
|| isLoading
|
|
70
|
+
|| isFetching
|
|
71
|
+
|| !result
|
|
72
|
+
|| (result.ok && !result.order)
|
|
73
|
+
|| stillPending);
|
|
74
|
+
return {
|
|
75
|
+
placedOrder,
|
|
76
|
+
placedOrderError,
|
|
77
|
+
awaitingPlacedOrder,
|
|
78
|
+
returnToCheckout,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=placedOrderPoll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placedOrderPoll.js","sourceRoot":"","sources":["../../../src/data/hooks/placedOrderPoll.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Polling rules for tPlacedOrder / getTwouOrder after the learner pays.\n *\n * PAYMENT_PENDING means the payment is still being confirmed (including a\n * 3D Secure check). Poll for up to 30 seconds. The next read is one of:\n * - ok: true with the order — payment went through. Show confirmation for\n * ORDER_PLACED, ORDER_FULFILLING, ORDER_COMPLETED, and\n * ORDER_FULFILMENT_FAILED. All four are paid; status only tracks enrollment.\n * - error: NOT_PLACED — the payment did not go through. The cart is editable\n * again (tCheckout includes paymentDecline). Leave the poll and return\n * the learner to checkout.\n */\nimport type { OrderDatesResult, PlacedOrder, PlacedOrderResult } from '../../types/alacarte';\n\nexport const PLACED_ORDER_POLL_INTERVAL_MS = 2000;\nexport const PLACED_ORDER_POLL_TIMEOUT_MS = 30_000;\n\n/** Paid placements. Fulfillment progress does not change the confirmation. */\nexport const PAID_PLACED_ORDER_STATUSES = [\n 'ORDER_PLACED',\n 'ORDER_FULFILLING',\n 'ORDER_COMPLETED',\n 'ORDER_FULFILMENT_FAILED',\n] as const;\n\nconst TRANSIENT_PLACED_ORDER_ERRORS = new Set([\n 'PAYMENT_PENDING',\n 'ORDER_READ_FAILED',\n]);\n\nexport function isPaidPlacedOrder(result: PlacedOrderResult | undefined): result is PlacedOrderResult & { order: PlacedOrder } {\n return !!result?.ok && !!result.order;\n}\n\n/** How long to wait before the next tPlacedOrder read, or false to stop. */\nexport function placedOrderRefetchInterval(\n result: PlacedOrderResult | undefined,\n elapsedMs: number,\n): number | false {\n if (isPaidPlacedOrder(result)) return false;\n // Declined (or otherwise not placed): cart is editable again. Do not keep polling.\n if (result && !result.ok && result.error === 'NOT_PLACED') return false;\n if (elapsedMs >= PLACED_ORDER_POLL_TIMEOUT_MS) return false;\n if (!result || (result.ok && !result.order)) return PLACED_ORDER_POLL_INTERVAL_MS;\n if (!result.ok && result.error && TRANSIENT_PLACED_ORDER_ERRORS.has(result.error)) {\n return PLACED_ORDER_POLL_INTERVAL_MS;\n }\n return false;\n}\n\n/** Keep reading getTwouOrder until tPlacedAt is set, or the same 30s window ends. */\nexport function orderDatesRefetchInterval(\n result: OrderDatesResult | undefined,\n elapsedMs: number,\n): number | false {\n if (result?.ok && result.dates?.tPlacedAt) return false;\n if (elapsedMs >= PLACED_ORDER_POLL_TIMEOUT_MS) return false;\n return PLACED_ORDER_POLL_INTERVAL_MS;\n}\n\nexport interface PlacedOrderView {\n placedOrder: PlacedOrder | null,\n placedOrderError: string | null,\n awaitingPlacedOrder: boolean,\n /** NOT_PLACED: stop waiting and send the learner back to the editable cart. */\n returnToCheckout: boolean,\n}\n\nexport function derivePlacedOrderView(args: {\n orderBiId: string | null,\n result: PlacedOrderResult | undefined,\n pollTimedOut: boolean,\n isLoading: boolean,\n isFetching: boolean,\n}): PlacedOrderView {\n const {\n orderBiId, result, pollTimedOut, isLoading, isFetching,\n } = args;\n const placedOrder = isPaidPlacedOrder(result) ? result.order : null;\n const returnToCheckout = !!orderBiId\n && !placedOrder\n && result?.ok === false\n && result.error === 'NOT_PLACED';\n\n let placedOrderError: string | null = null;\n if (orderBiId && !placedOrder && !returnToCheckout && result && !result.ok) {\n const error = result.error ?? 'ORDER_READ_FAILED';\n const stillWaiting = TRANSIENT_PLACED_ORDER_ERRORS.has(error) && !pollTimedOut;\n if (!stillWaiting) placedOrderError = error;\n } else if (\n orderBiId\n && !placedOrder\n && !returnToCheckout\n && pollTimedOut\n && !result?.ok\n ) {\n placedOrderError = result?.error ?? 'PAYMENT_PENDING';\n }\n\n const stillPending = !!result\n && !result.ok\n && !!result.error\n && TRANSIENT_PLACED_ORDER_ERRORS.has(result.error);\n\n const awaitingPlacedOrder = !!orderBiId && !placedOrder && !placedOrderError && (\n returnToCheckout\n || isLoading\n || isFetching\n || !result\n || (result.ok && !result.order)\n || stillPending\n );\n\n return {\n placedOrder,\n placedOrderError,\n awaitingPlacedOrder,\n returnToCheckout,\n };\n}\n"]}
|
|
@@ -58,6 +58,10 @@ export interface UseAlacarteCheckoutResult extends AlacartePhaseState {
|
|
|
58
58
|
paymentDecline: AlacartePaymentDecline | null;
|
|
59
59
|
/** When ?order_bi_id= is present, the placed order (or null while polling). */
|
|
60
60
|
placedOrder: PlacedOrder | null;
|
|
61
|
+
/**
|
|
62
|
+
* True while tPlacedOrder is still PAYMENT_PENDING (payment confirming,
|
|
63
|
+
* including 3DS) or while we reload tCheckout after NOT_PLACED.
|
|
64
|
+
*/
|
|
61
65
|
awaitingPlacedOrder: boolean;
|
|
62
66
|
placedOrderError: string | null;
|
|
63
67
|
/** ISO timestamp the order was placed (tPlacedAt, falling back to
|
|
@@ -22,7 +22,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
22
22
|
* See /alacarte-checkout-api.md and /alacarte-owned-products-api.md at the
|
|
23
23
|
* repo root for the underlying API contract.
|
|
24
24
|
*/
|
|
25
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
25
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
26
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
26
27
|
import { useQueryStates, useQueryState, parseAsString } from 'nuqs';
|
|
27
28
|
import { createAlacarteCheckoutClient } from '../../utils/alacarteCheckout';
|
|
28
29
|
import { getCouponErrorMessage, getCouponRecheckMessage } from '../../utils/couponErrors';
|
|
@@ -34,6 +35,8 @@ import { useSubscriptionCourseKeyQuery, useViaxSessionQuery } from './useCheckou
|
|
|
34
35
|
import { courseKeyFromCourseRunKey, lmsOriginFromSessionUrl, isCourseNotFoundError, } from '../../utils/provision';
|
|
35
36
|
import { getCourseRunKeyFromUrl } from '../../utils/url';
|
|
36
37
|
import { useOwnedProductQuery, useAlacarteCheckoutQuery, useSelectProductMutation, useProductDetailQuery, useUpdateCheckoutMutation, usePlacedOrderQuery, useOrderDatesQuery, } from './useAlacarteQueries';
|
|
38
|
+
import { alacarteKeys } from './keys';
|
|
39
|
+
import { derivePlacedOrderView } from './placedOrderPoll';
|
|
37
40
|
/**
|
|
38
41
|
* Read the product selector from the URL: course_uid+course_run_key, or
|
|
39
42
|
* program_uid, or neither. Both course params must be present together; if a
|
|
@@ -236,8 +239,9 @@ export function deriveAlacartePhase(input) {
|
|
|
236
239
|
return Object.assign(Object.assign({ phase: 'noActiveCheckout', noticeMessageId: 'noActiveCheckout' }, empty), { isAlreadyOwned: false, ownedProductName: null });
|
|
237
240
|
}
|
|
238
241
|
export function useAlacarteCheckout(options) {
|
|
239
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q
|
|
242
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
240
243
|
const { apiUrl, edxSessionUrl, skipSubscriptionCourseRuns = false } = options;
|
|
244
|
+
const queryClient = useQueryClient();
|
|
241
245
|
const selector = useProductSelector();
|
|
242
246
|
const discountCode = useDiscountCode();
|
|
243
247
|
const [orderBiId, setOrderBiId] = useQueryState('order_bi_id', parseAsString);
|
|
@@ -540,31 +544,53 @@ export function useAlacarteCheckout(options) {
|
|
|
540
544
|
return;
|
|
541
545
|
placeOrderCore(opts).catch(() => { });
|
|
542
546
|
}, [placeOrderCore, placingOrder, paymentTimedOut]);
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
547
|
+
const placedView = derivePlacedOrderView({
|
|
548
|
+
orderBiId,
|
|
549
|
+
result: placedOrderQuery.data,
|
|
550
|
+
pollTimedOut: placedOrderQuery.pollTimedOut,
|
|
551
|
+
isLoading: placedOrderQuery.isLoading,
|
|
552
|
+
isFetching: placedOrderQuery.isFetching,
|
|
553
|
+
});
|
|
554
|
+
const { placedOrder } = placedView;
|
|
555
|
+
const dates = ((_l = orderDatesQuery.data) === null || _l === void 0 ? void 0 : _l.ok) ? orderDatesQuery.data.dates : null;
|
|
556
|
+
const placedOrderPlacedAt = (_o = (_m = dates === null || dates === void 0 ? void 0 : dates.tPlacedAt) !== null && _m !== void 0 ? _m : dates === null || dates === void 0 ? void 0 : dates.biCreatedAt) !== null && _o !== void 0 ? _o : null;
|
|
557
|
+
// NOT_PLACED: the payment did not go through and the cart is editable again.
|
|
558
|
+
// Reload tCheckout (it carries paymentDecline) and drop ?order_bi_id= so the
|
|
559
|
+
// learner can try another card instead of sitting on the confirmation error.
|
|
560
|
+
// The ref blocks a second reload for the same attempt. It is not cleared in
|
|
561
|
+
// cleanup — a re-run must not cancel the reload that is already in flight.
|
|
562
|
+
const returnedToCheckoutFor = useRef(null);
|
|
563
|
+
useEffect(() => {
|
|
564
|
+
if (!orderBiId) {
|
|
565
|
+
returnedToCheckoutFor.current = null;
|
|
566
|
+
return undefined;
|
|
555
567
|
}
|
|
556
|
-
|
|
557
|
-
|
|
568
|
+
if (!placedView.returnToCheckout)
|
|
569
|
+
return undefined;
|
|
570
|
+
if (returnedToCheckoutFor.current === orderBiId)
|
|
571
|
+
return undefined;
|
|
572
|
+
returnedToCheckoutFor.current = orderBiId;
|
|
573
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
574
|
+
if (client) {
|
|
575
|
+
try {
|
|
576
|
+
const fresh = yield client.getCheckout();
|
|
577
|
+
if (fresh.ok && fresh.checkout) {
|
|
578
|
+
setCheckoutOverride(fresh.checkout);
|
|
579
|
+
queryClient.setQueryData(alacarteKeys.checkout(), fresh);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
catch (_a) {
|
|
583
|
+
// Still leave the poll — the cart is editable even if this read fails.
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
yield setOrderBiId(null);
|
|
587
|
+
}))();
|
|
588
|
+
return undefined;
|
|
589
|
+
}, [orderBiId, placedView.returnToCheckout, client, setOrderBiId, queryClient]);
|
|
558
590
|
return Object.assign(Object.assign({}, derived), { couponError, couponApplying: manualUpdateMutation.isPending, applyCoupon,
|
|
559
591
|
removeCoupon, clearCouponError: () => setCouponError(null), removedCouponCode, clearRemovedCouponCode: () => setRemovedCouponCode(null), placingOrder,
|
|
560
592
|
placeOrderError,
|
|
561
|
-
placeOrder, placeOrderAsync: placeOrderCore, clearPlaceOrderError: () => setPlaceOrderError(null), paymentTimedOut, paymentDecline: (
|
|
562
|
-
|| placedOrderQuery.isFetching
|
|
563
|
-
|| ((_s = placedOrderQuery.data) === null || _s === void 0 ? void 0 : _s.error) === 'NOT_PLACED'
|
|
564
|
-
|| ((_t = placedOrderQuery.data) === null || _t === void 0 ? void 0 : _t.error) === 'ORDER_READ_FAILED'
|
|
565
|
-
|| (((_u = placedOrderQuery.data) === null || _u === void 0 ? void 0 : _u.ok) === true && !placedOrderQuery.data.order)
|
|
566
|
-
|| !placedOrderQuery.data), placedOrderError,
|
|
567
|
-
placedOrderPlacedAt });
|
|
593
|
+
placeOrder, placeOrderAsync: placeOrderCore, clearPlaceOrderError: () => setPlaceOrderError(null), paymentTimedOut, paymentDecline: (_q = (_p = derived.checkout) === null || _p === void 0 ? void 0 : _p.paymentDecline) !== null && _q !== void 0 ? _q : null, placedOrder, awaitingPlacedOrder: placedView.awaitingPlacedOrder, placedOrderError: placedView.placedOrderError, placedOrderPlacedAt });
|
|
568
594
|
}
|
|
569
595
|
export default useAlacarteCheckout;
|
|
570
596
|
//# sourceMappingURL=useAlacarteCheckout.js.map
|