@edx/frontend-app-alacarte-checkout 1.22.0 → 1.23.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/useAlacarteQueries.d.ts +1 -1
- package/dist/data/hooks/useAlacarteQueries.js +9 -2
- 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.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/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":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,QAAQ,GAAG,cAAc,CAAC;IAC9B,mBAAmB;IACnB,mBAAmB,EAAE;QACnB,EAAE,EAAE,0CAA0C;QAC9C,cAAc,EAAE,uBAAuB;QACvC,WAAW,EAAE,kDAAkD;KAChE;IACD,cAAc,EAAE;QACd,EAAE,EAAE,qCAAqC;QACzC,cAAc,EAAE,kBAAkB;QAClC,WAAW,EAAE,mBAAmB;KACjC;IACD,kBAAkB,EAAE;QAClB,EAAE,EAAE,yCAAyC;QAC7C,cAAc,EAAE,0DAA0D;QAC1E,WAAW,EAAE,yCAAyC;KACvD;IACD,iBAAiB,EAAE;QACjB,EAAE,EAAE,wCAAwC;QAC5C,cAAc,EAAE,qBAAqB;QACrC,WAAW,EAAE,+BAA+B;KAC7C;IAED,WAAW,EAAE;QACX,EAAE,EAAE,kCAAkC;QACtC,cAAc,EAAE,eAAe;QAC/B,WAAW,EAAE,qCAAqC;KACnD;IAED,cAAc;IACd,eAAe,EAAE;QACf,EAAE,EAAE,sCAAsC;QAC1C,cAAc,EAAE,4FAA4F;QAC5G,WAAW,EAAE,wEAAwE;KACtF;IACD,KAAK,EAAE;QACL,EAAE,EAAE,4BAA4B;QAChC,cAAc,EAAE,QAAQ;QACxB,WAAW,EAAE,kCAAkC;KAChD;IACD,WAAW,EAAE;QACX,EAAE,EAAE,kCAAkC;QACtC,cAAc,EAAE,sBAAsB;QACtC,WAAW,EAAE,2DAA2D;KACzE;IAED,gBAAgB;IAChB,MAAM,EAAE;QACN,EAAE,EAAE,6BAA6B;QACjC,cAAc,EAAE,SAAS;QACzB,WAAW,EAAE,qBAAqB;KACnC;IACD,UAAU,EAAE;QACV,EAAE,EAAE,iCAAiC;QACrC,cAAc,EAAE,eAAe;QAC/B,WAAW,EAAE,qDAAqD;KACnE;IACD,OAAO,EAAE;QACP,EAAE,EAAE,8BAA8B;QAClC,cAAc,EAAE,SAAS;QACzB,WAAW,EAAE,wBAAwB;KACtC;IACD,kBAAkB,EAAE;QAClB,EAAE,EAAE,yCAAyC;QAC7C,cAAc,EAAE,qBAAqB;QACrC,WAAW,EAAE,uDAAuD;KACrE;IACD,KAAK,EAAE;QACL,EAAE,EAAE,4BAA4B;QAChC,cAAc,EAAE,OAAO;QACvB,WAAW,EAAE,8BAA8B;KAC5C;IACD,gBAAgB,EAAE;QAChB,EAAE,EAAE,uCAAuC;QAC3C,cAAc,EAAE,mBAAmB;QACnC,WAAW,EAAE,qDAAqD;KACnE;IACD,QAAQ,EAAE;QACR,EAAE,EAAE,+BAA+B;QACnC,cAAc,EAAE,UAAU;QAC1B,WAAW,EAAE,kCAAkC;KAChD;IACD,mBAAmB,EAAE;QACnB,EAAE,EAAE,0CAA0C;QAC9C,cAAc,EAAE,sBAAsB;QACtC,WAAW,EAAE,wDAAwD;KACtE;IACD,OAAO,EAAE;QACP,EAAE,EAAE,8BAA8B;QAClC,cAAc,EAAE,UAAU;QAC1B,WAAW,EAAE,gCAAgC;KAC9C;IACD,kBAAkB,EAAE;QAClB,EAAE,EAAE,yCAAyC;QAC7C,cAAc,EAAE,OAAO;QACvB,WAAW,EAAE,4CAA4C;KAC1D;IACD,UAAU,EAAE;QACV,EAAE,EAAE,iCAAiC;QACrC,cAAc,EAAE,aAAa;QAC7B,WAAW,EAAE,4BAA4B;KAC1C;IACD,qBAAqB,EAAE;QACrB,EAAE,EAAE,4CAA4C;QAChD,cAAc,EAAE,SAAS;QACzB,WAAW,EAAE,wCAAwC;KACtD;IACD,aAAa,EAAE;QACb,EAAE,EAAE,oCAAoC;QACxC,cAAc,EAAE,wBAAwB;QACxC,WAAW,EAAE,uDAAuD;KACrE;CACF,CAAC,CAAC;AAEH,eAAe,QAAQ,CAAC","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":";;;;;;;;;;AAAA,OAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAErD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,aAAa,MAAM,gCAAgC,CAAC;AAE3D,OAAO,eAAe,MAAM,oCAAoC,CAAC;AAGjE,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAC/F,OAAO,QAAQ,MAAM,YAAY,CAAC;AAuClC,MAAM,WAAW,GAAG,CAAC,EACnB,OAAO,EACP,QAAQ,EACR,cAAc,EACd,cAAc,EACd,WAAW,EACX,WAAW,EACX,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,YAAY,GAAG,IAAI,EACnB,MAAM,GAAG,IAAI,EACb,cAAc,GAAG,IAAI,EACrB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,GACA,EAAE,EAAE;;IACrB,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,CAAC;IACpC,MAAM,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAExE,yEAAyE;IACzE,6EAA6E;IAC7E,MAAM,SAAS,GAAG,CAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,SAAS;QACvC,CAAC,CAAC,aAAa,CAAC,QAAQ,CAAC,WAAW,EAAE;YAClC,MAAM,EAAE,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,YAAY,CAAC,SAAS,CAAC,QAAQ;SAC5F,CAAC;QACJ,CAAC,CAAC,EAAE,CAAC;IAEP,MAAM,WAAW,GAAG,UAAU,IAAI,CAAC,WAAW,IAAI,CAAC,YAAY,CAAC;IAChE,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACzD,MAAM,aAAa,GAAG,uBAAuB,CAAC,WAAW,CAAC,CAAC;IAE3D,MAAM,eAAe,GAAG,CAAC,CAAC,eAAe;WACpC,eAAe,CAAC,MAAM,CAAC,QAAQ;WAC/B,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK;WAC7B,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK;WAC7B,eAAe,CAAC,MAAM,CAAC,QAAQ;WAC/B,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK;WAC7B,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK;WAC7B,eAAe,CAAC,GAAG,CAAC,QAAQ;WAC5B,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK;WAC1B,CAAC,eAAe,CAAC,GAAG,CAAC,KAAK,CAAC;IAChC,MAAM,YAAY,GAAG,wBAAwB,CAAC,OAAO,CAAC,CAAC;IACvD,6EAA6E;IAC7E,yFAAyF;IACzF,MAAM,UAAU,GAAG,WAAW,CAAC;IAC/B,MAAM,cAAc,GAAG,WAAW,IAAI,CAAC,eAAe,IAAI,CAAC,YAAY,CAAC;IAExE,MAAM,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;IAE1D,gFAAgF;IAChF,MAAM,aAAa,GAAG,CAAC,KAAkC,EAAsB,EAAE;QAC/E,MAAM,KAAK,GAAG,eAAe,aAAf,eAAe,uBAAf,eAAe,CAAG,KAAK,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,0DAA0D;QAC1D,IAAI,oBAAoB,IAAI,KAAK,CAAC,KAAK;YAAE,OAAO,WAAW,CAAC;QAC5D,6EAA6E;QAC7E,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC,KAAK,CAAC;QACrD,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;IAEF,MAAM,WAAW,GAAmC,eAAe;QACjE,CAAC,CAAC;YACE,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC;YAC/B,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC;YAC/B,GAAG,EAAE,aAAa,CAAC,KAAK,CAAC;SAC1B;QACH,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,YAAY,GAAG,oBAAoB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC;IACxF,MAAM,UAAU,GAAG,oBAAoB,IAAI,aAAa,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE;QAC1F,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,WAAW,GAAG,oBAAoB,IAAI,aAAa,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE;QAC1F,CAAC,CAAC,WAAW;QACb,CAAC,CAAC,SAAS,CAAC;IAEd,MAAM,mBAAmB,GAAG,WAAW,CAAC,CAAC,OAAe,EAAE,EAAE;QAC1D,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC7B,2EAA2E;QAC3E,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACtB,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACtB,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,0EAA0E;IAC1E,2EAA2E;IAC3E,qDAAqD;IACrD,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;;QACpC,wCAAwC;QACxC,MAAM,WAAW,GAAG,MAAA,eAAe,aAAf,eAAe,uBAAf,eAAe,CAAE,MAAM,CAAC,KAAK,mCAAI,IAAI,CAAC;QAC1D,MAAM,WAAW,GAAG,MAAA,eAAe,aAAf,eAAe,uBAAf,eAAe,CAAE,MAAM,CAAC,KAAK,mCAAI,IAAI,CAAC;QAC1D,MAAM,QAAQ,GAAG,MAAA,eAAe,aAAf,eAAe,uBAAf,eAAe,CAAE,GAAG,CAAC,KAAK,mCAAI,IAAI,CAAC;QACpD,MAAM,cAAc,GAAG,CAAC,wBAAwB,CAAC,OAAO,CAAC,CAAC;QAE1D,IAAI,WAAW,IAAI,WAAW,IAAI,QAAQ,IAAI,cAAc,EAAE,CAAC;YAC7D,uBAAuB,CAAC,IAAI,CAAC,CAAC;YAC9B,iBAAiB,aAAjB,iBAAiB,uBAAjB,iBAAiB,CAAG,IAAI,CAAC,CAAC;YAC1B,OAAO;QACT,CAAC;QAED,uBAAuB,CAAC,KAAK,CAAC,CAAC;QAC/B,iBAAiB,aAAjB,iBAAiB,uBAAjB,iBAAiB,CAAG,KAAK,CAAC,CAAC;QAC3B,YAAY,CAAC;YACX,iBAAiB,EAAE,IAAI;YACvB,YAAY,EAAE,IAAI;YAClB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACpC,YAAY,EAAE,EAAE;SACjB,EAAE,MAAM,CAAC,CAAC;IACb,CAAC,EAAE,CAAC,eAAe,EAAE,OAAO,EAAE,YAAY,EAAE,iBAAiB,CAAC,CAAC,CAAC;IAEhE,MAAM,cAAc,GAAG,CACrB,MAAC,IAAI,CAAC,KAAK,IACT,SAAS,EAAC,SAAS,EACnB,SAAS,EAAE,+BAA+B,YAAY,CAAC,CAAC,CAAC,gCAAgC,CAAC,CAAC,CAAC,EAAE,EAAE,aAEhG,KAAC,IAAI,CAAC,KAAK,IAAC,SAAS,EAAC,qCAAqC,YACxD,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,GACrB,EACb,MAAC,IAAI,CAAC,OAAO,IACX,EAAE,EAAC,QAAQ,EACX,KAAK,EAAE,OAAO,CAAC,OAAO,EACtB,QAAQ,EAAE,CAAC,CAAuC,EAAE,EAAE;oBACpD,mBAAmB,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACtC,CAAC,gBACW,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,aAE3C,iBAAQ,KAAK,EAAC,EAAE,EAAC,QAAQ,QAAC,MAAM,kBAC7B,aAAa,CAAC,QAAQ,CAAC,kBAAkB,CAAC,GACpC,EACR,iBAAiB,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;wBACjC,OAAO,CACL,iBAAyB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,YACvC,OAAO,CAAC,CAAC,CAAC,IADA,OAAO,CAAC,CAAC,CAAC,CAEd,CACV,CAAC;oBACJ,CAAC,CAAC,IACW,EACd,YAAY,IAAI,CACf,eAAK,SAAS,EAAC,uBAAuB,EAAC,IAAI,EAAC,OAAO,aACjD,eAAM,SAAS,EAAC,4BAA4B,iBAAa,MAAM,GAAG,EACjE,YAAY,IACT,CACP,IACU,CACd,CAAC;IAEF,MAAM,YAAY,GAAG,aAAa,CAAC,UAAU,IAAI,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAC5E,MAAC,IAAI,CAAC,KAAK,IACT,SAAS,EAAC,OAAO,EACjB,SAAS,EAAE,6BAA6B,UAAU,CAAC,CAAC,CAAC,8BAA8B,CAAC,CAAC,CAAC,EAAE,EAAE,aAE1F,KAAC,IAAI,CAAC,KAAK,IAAC,SAAS,EAAC,qCAAqC,YACxD,aAAa,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAC9D,EACb,MAAC,IAAI,CAAC,OAAO,IACX,EAAE,EAAC,QAAQ,EACX,KAAK,EAAE,OAAO,CAAC,KAAK,EACpB,QAAQ,EAAE,CAAC,CAAuC,EAAE,EAAE;oBACpD,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBACpC,CAAC,gBACW,aAAa,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,aAEpF,iBAAQ,KAAK,EAAC,EAAE,EAAC,QAAQ,QAAC,MAAM,kBAC7B,aAAa,CACZ,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAChF,GACM,EACR,aAAa,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAC/C,iBAA6B,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,YAC/C,WAAW,CAAC,CAAC,CAAC,IADJ,WAAW,CAAC,CAAC,CAAC,CAElB,CACV,CAAC,IACW,EACd,UAAU,IAAI,CACb,eAAK,SAAS,EAAC,uBAAuB,EAAC,IAAI,EAAC,OAAO,aACjD,eAAM,SAAS,EAAC,4BAA4B,iBAAa,MAAM,GAAG,EACjE,UAAU,IACP,CACP,IACU,CACd,CAAC,CAAC,CAAC,IAAI,CAAC;IAET,MAAM,aAAa,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC,CAChD,MAAC,IAAI,CAAC,KAAK,IACT,SAAS,EAAC,QAAQ,EAClB,SAAS,EAAE,8BAA8B,WAAW,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,EAAE,EAAE,aAE7F,KAAC,IAAI,CAAC,KAAK,IAAC,SAAS,EAAC,qCAAqC,YACxD,aAAa,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,GAClE,EACb,KAAC,IAAI,CAAC,OAAO,IACX,KAAK,EAAE,OAAO,CAAC,GAAG,EAClB,QAAQ,EAAE,CAAC,CAAsC,EAAE,EAAE;oBACnD,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAClC,CAAC,EACD,WAAW,EAAE,aAAa,CACxB,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,QAAQ,CAAC,qBAAqB,CACpF,gBACW,aAAa,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EACxF,YAAY,EAAC,aAAa,GAC1B,EACD,WAAW,IAAI,CACd,eAAK,SAAS,EAAC,uBAAuB,EAAC,IAAI,EAAC,OAAO,aACjD,eAAM,SAAS,EAAC,4BAA4B,iBAAa,MAAM,GAAG,EACjE,WAAW,IACR,CACP,IACU,CACd,CAAC,CAAC,CAAC,IAAI,CAAC;IAET,OAAO,CACL,eAAK,SAAS,EAAC,uBAAuB,aACpC,0BACE,aAAI,SAAS,EAAC,8DAA8D,YACzE,aAAa,CAAC,QAAQ,CAAC,mBAAmB,CAAC,GACzC,EAEL,aAAG,SAAS,EAAC,+BAA+B,aAC1C,iBAAQ,SAAS,EAAC,0BAA0B,YACzC,aAAa,CAAC,QAAQ,CAAC,cAAc,CAAC,GAChC,EAAC,GAAG,EACb,eAAM,SAAS,EAAC,uBAAuB,YACpC,aAAa,CAAC,QAAQ,CAAC,kBAAkB,EAAE;oCAC1C,IAAI,EAAE,CACJ,YACE,IAAI,EAAE,uBAAuB,EAC7B,MAAM,EAAC,QAAQ,EACf,GAAG,EAAC,YAAY,EAChB,SAAS,EAAC,eAAe,YAExB,aAAa,CAAC,QAAQ,CAAC,iBAAiB,CAAC,GACxC,CACL;iCACF,CAAC,GACG,IACL,IACA,EAEN,KAAC,eAAe,IACd,MAAM,EAAE,MAAM,EACd,gBAAgB,EACd,CAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,SAAS;oBACrB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,MAAM,GAAG,GAAG,CAAC;oBACjD,CAAC,CAAC,IAAI,EAEV,QAAQ,EAAE,MAAA,MAAA,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,SAAS,0CAAE,QAAQ,mCAAI,IAAI,EACnD,OAAO,EAAE,cAAc,EACvB,mBAAmB,EAAE,gBAAgB,aAAhB,gBAAgB,cAAhB,gBAAgB,GAAI,CAAC,GAAS,EAAE,kDAAE,CAAC,CAAA,CAAC;gBACzD,yEAAyE;gBACzE,yEAAyE;gBACzE,QAAQ,EAAE,UAAU,IAAI,CAAC,gBAAgB,GACzC,EAGF,cAAK,SAAS,EAAC,uBAAuB,YACpC,cAAK,SAAS,EAAC,qDAAqD,YAClE,eAAK,SAAS,EAAC,0BAA0B,aACvC,aAAI,SAAS,EAAC,+EAA+E,YAC1F,aAAa,CAAC,QAAQ,CAAC,WAAW,CAAC,GACjC,EACL,KAAC,aAAa,IACZ,cAAc,EAAE,cAAc,EAC9B,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,WAAW,EACxB,WAAW,EAAE,WAAW,GACxB,EACF,cAAK,SAAS,EAAC,4BAA4B,YACxC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAChC,8BACG,cAAc,EACd,CAAC,YAAY,IAAI,aAAa,CAAC,IAAI,CAClC,eAAK,SAAS,EAAC,2BAA2B,aACvC,YAAY,EACZ,aAAa,IACV,CACP,IACA,CACJ,CAAC,CAAC,CAAC,CACF,eAAK,SAAS,EAAE,4BACd,CAAC,YAAY,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,oCAAoC,CAAC,CAAC,CAAC,EAC3E,EAAE,aAEC,cAAc,EACd,YAAY,EACZ,aAAa,IACV,CACP,GACG,IACF,GACF,GACF,EAGN,eAAK,SAAS,EAAC,qBAAqB,aAClC,YAAG,SAAS,EAAC,mDAAmD,YAC7D,aAAa,CAAC,QAAQ,CAAC,eAAe,CAAC,GACtC,EACJ,eAAK,SAAS,EAAC,yBAAyB,aACtC,aAAG,SAAS,EAAC,oBAAoB,aAC/B,iBAAQ,SAAS,EAAC,2BAA2B,YAAE,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAU,EAAC,GAAG,EAC1F,eAAM,SAAS,EAAC,2BAA2B,YACxC,SAAS,GACL,IACL,EACJ,KAAC,MAAM,IACL,OAAO,EAAC,OAAO,EACf,SAAS,EAAC,sBAAsB,EAChC,OAAO,EAAE,YAAY,EACrB,QAAQ,EAAE,cAAc,YAEvB,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,GAC3D,IACL,IACF,IACF,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,WAAW,CAAC","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"]}
|
|
@@ -20,7 +20,7 @@ export declare function useUpdateCheckoutMutation(client: AlacarteCheckoutClient
|
|
|
20
20
|
/** Fetches display details for the selected product.
|
|
21
21
|
* A course is looked up in Viax by elements[0].course from the
|
|
22
22
|
* subscription course-run API (not the course-run key or the URL param).
|
|
23
|
-
* A program uses filterTwouProgram by
|
|
23
|
+
* A program uses filterTwouProgram by tProgramUuid. */
|
|
24
24
|
export declare function useProductDetailQuery(client: AlacarteCheckoutClient | null, enabled: boolean, selector: ProductSelector, courseKey?: string | null): import("@tanstack/react-query").UseQueryResult<ProductDetail | null, Error>;
|
|
25
25
|
/** Fetches a placed order for the confirmation page. Polls while the order
|
|
26
26
|
* is not yet placed (Stripe confirms to the backend asynchronously). */
|
|
@@ -11,6 +11,7 @@ export function useOwnedProductQuery(client, enabled, productId) {
|
|
|
11
11
|
enabled: enabled && !!client && !!productId,
|
|
12
12
|
queryFn: () => client.getOwnedProduct(productId),
|
|
13
13
|
staleTime: Infinity,
|
|
14
|
+
refetchOnWindowFocus: false,
|
|
14
15
|
retry: 1,
|
|
15
16
|
});
|
|
16
17
|
}
|
|
@@ -20,7 +21,10 @@ export function useAlacarteCheckoutQuery(client, enabled) {
|
|
|
20
21
|
queryKey: alacarteKeys.checkout(),
|
|
21
22
|
enabled: enabled && !!client,
|
|
22
23
|
queryFn: () => client.getCheckout(),
|
|
23
|
-
|
|
24
|
+
// Keep the open checkout stable while the tab is backgrounded — a focus
|
|
25
|
+
// refetch would remount payment UI / re-run select for no user action.
|
|
26
|
+
staleTime: Infinity,
|
|
27
|
+
refetchOnWindowFocus: false,
|
|
24
28
|
retry: 1,
|
|
25
29
|
});
|
|
26
30
|
}
|
|
@@ -56,7 +60,7 @@ export function useUpdateCheckoutMutation(client) {
|
|
|
56
60
|
/** Fetches display details for the selected product.
|
|
57
61
|
* A course is looked up in Viax by elements[0].course from the
|
|
58
62
|
* subscription course-run API (not the course-run key or the URL param).
|
|
59
|
-
* A program uses filterTwouProgram by
|
|
63
|
+
* A program uses filterTwouProgram by tProgramUuid. */
|
|
60
64
|
export function useProductDetailQuery(client, enabled, selector, courseKey = null) {
|
|
61
65
|
const productKey = selector.kind === 'course'
|
|
62
66
|
? (courseKey !== null && courseKey !== void 0 ? courseKey : 'none')
|
|
@@ -72,6 +76,7 @@ export function useProductDetailQuery(client, enabled, selector, courseKey = nul
|
|
|
72
76
|
return Promise.resolve(null);
|
|
73
77
|
},
|
|
74
78
|
staleTime: Infinity,
|
|
79
|
+
refetchOnWindowFocus: false,
|
|
75
80
|
retry: 1,
|
|
76
81
|
});
|
|
77
82
|
}
|
|
@@ -83,6 +88,7 @@ export function usePlacedOrderQuery(client, biId, enabled) {
|
|
|
83
88
|
enabled: enabled && !!client && !!biId,
|
|
84
89
|
queryFn: () => client.getPlacedOrder(biId),
|
|
85
90
|
staleTime: 0,
|
|
91
|
+
refetchOnWindowFocus: false,
|
|
86
92
|
retry: 1,
|
|
87
93
|
refetchInterval: (query) => {
|
|
88
94
|
const result = query.state.data;
|
|
@@ -105,6 +111,7 @@ export function useOrderDatesQuery(client, biId, enabled) {
|
|
|
105
111
|
enabled: enabled && !!client && !!biId,
|
|
106
112
|
queryFn: () => client.getOrderDates(biId),
|
|
107
113
|
staleTime: Infinity,
|
|
114
|
+
refetchOnWindowFocus: false,
|
|
108
115
|
retry: 1,
|
|
109
116
|
});
|
|
110
117
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAlacarteQueries.js","sourceRoot":"","sources":["../../../src/data/hooks/useAlacarteQueries.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAMtC,sEAAsE;AACtE,MAAM,UAAU,oBAAoB,CAClC,MAAqC,EACrC,OAAgB,EAChB,SAAwB;IAExB,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,YAAY,CAAC,YAAY,CAAC,SAAS,aAAT,SAAS,cAAT,SAAS,GAAI,MAAM,CAAC;QACxD,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,SAAS;QAC3C,OAAO,EAAE,GAAgC,EAAE,CAAC,MAAO,CAAC,eAAe,CAAC,SAAU,CAAC;QAC/E,SAAS,EAAE,QAAQ;QACnB,KAAK,EAAE,CAAC;KACT,CAAC,CAAC;AACL,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,wBAAwB,CAAC,MAAqC,EAAE,OAAgB;IAC9F,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,YAAY,CAAC,QAAQ,EAAE;QACjC,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM;QAC5B,OAAO,EAAE,GAAoC,EAAE,CAAC,MAAO,CAAC,WAAW,EAAE;QACrE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"useAlacarteQueries.js","sourceRoot":"","sources":["../../../src/data/hooks/useAlacarteQueries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * data/hooks/useAlacarteQueries.ts — read/write-side TanStack Query hooks for\n * the à la carte checkout flow. Mirrors useCheckoutQueries.ts's conventions.\n */\nimport { useQuery, useMutation } from '@tanstack/react-query';\nimport { alacarteKeys } from './keys';\nimport type {\n AlacarteCheckoutClient, AlacarteCheckoutResult, OwnedProductResult, ProductSelector, ProductDetail,\n PlacedOrderResult, OrderDatesResult,\n} from '../../types/alacarte';\n\n/** Checks ownership of the product identified by the query params. */\nexport function useOwnedProductQuery(\n client: AlacarteCheckoutClient | null,\n enabled: boolean,\n productId: string | null,\n) {\n return useQuery({\n queryKey: alacarteKeys.ownedProduct(productId ?? 'none'),\n enabled: enabled && !!client && !!productId,\n queryFn: (): Promise<OwnedProductResult> => client!.getOwnedProduct(productId!),\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/** Reads the learner's existing open checkout (used when there's no product selector). */\nexport function useAlacarteCheckoutQuery(client: AlacarteCheckoutClient | null, enabled: boolean) {\n return useQuery({\n queryKey: alacarteKeys.checkout(),\n enabled: enabled && !!client,\n queryFn: (): Promise<AlacarteCheckoutResult> => client!.getCheckout(),\n // Keep the open checkout stable while the tab is backgrounded — a focus\n // refetch would remount payment UI / re-run select for no user action.\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/** Selects a course or program into the learner's checkout — fired once ownership is confirmed not-full.\n * `viewedPrice` is `tOwnedProducts.price.amountDue` (USD, before any coupon) for ALC-159. */\nexport function useSelectProductMutation(client: AlacarteCheckoutClient | null) {\n return useMutation({\n mutationFn: (args: {\n selector: ProductSelector,\n viewedPrice?: number,\n }): Promise<AlacarteCheckoutResult> => {\n if (!client) return Promise.reject(new Error('Alacarte client not ready.'));\n const { selector, viewedPrice } = args;\n if (selector.kind === 'course') {\n return client.selectCourse(selector.courseUid, selector.runKey, viewedPrice);\n }\n if (selector.kind === 'program') {\n return client.selectProgram(selector.programUid, viewedPrice);\n }\n return Promise.reject(new Error('selectMutation called with no product selector.'));\n },\n });\n}\n\n/** Updates the learner's checkout — used here to apply ?discount_code= right\n * after the checkout is created. */\nexport function useUpdateCheckoutMutation(client: AlacarteCheckoutClient | null) {\n return useMutation({\n mutationFn: (args: {\n biId?: string, couponCode?: string, termsAccepted?: boolean, acceptPrice?: boolean,\n }): Promise<AlacarteCheckoutResult> => {\n if (!client) return Promise.reject(new Error('Alacarte client not ready.'));\n return client.updateCheckout(args);\n },\n });\n}\n\n/** Fetches display details for the selected product.\n * A course is looked up in Viax by elements[0].course from the\n * subscription course-run API (not the course-run key or the URL param).\n * A program uses filterTwouProgram by tProgramUuid. */\nexport function useProductDetailQuery(\n client: AlacarteCheckoutClient | null,\n enabled: boolean,\n selector: ProductSelector,\n courseKey: string | null = null,\n) {\n const productKey = selector.kind === 'course'\n ? (courseKey ?? 'none')\n : selector.kind === 'program' ? selector.programUid : 'none';\n\n return useQuery({\n queryKey: alacarteKeys.productDetail(`${selector.kind}:${productKey}`),\n enabled: enabled && !!client && (\n selector.kind === 'program' || (selector.kind === 'course' && !!courseKey)\n ),\n queryFn: (): Promise<ProductDetail | null> => {\n if (selector.kind === 'course') return client!.getCourseDetail(courseKey!);\n if (selector.kind === 'program') return client!.getProgramDetail(selector.programUid);\n return Promise.resolve(null);\n },\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/** Fetches a placed order for the confirmation page. Polls while the order\n * is not yet placed (Stripe confirms to the backend asynchronously). */\nexport function usePlacedOrderQuery(\n client: AlacarteCheckoutClient | null,\n biId: string | null,\n enabled: boolean,\n) {\n return useQuery({\n queryKey: alacarteKeys.placedOrder(biId ?? 'none'),\n enabled: enabled && !!client && !!biId,\n queryFn: (): Promise<PlacedOrderResult> => client!.getPlacedOrder(biId!),\n staleTime: 0,\n refetchOnWindowFocus: false,\n retry: 1,\n refetchInterval: (query) => {\n const result = query.state.data;\n // Keep polling while payment is still settling on the backend.\n if (!result) return 2000;\n if (result.ok && result.order) return false;\n if (result.error === 'NOT_PLACED' || result.error === 'ORDER_READ_FAILED') return 2000;\n return false;\n },\n });\n}\n\n/** Fetches the order's placement/creation timestamps — used for the\n * confirmation page's purchase date and refund-window deadline. */\nexport function useOrderDatesQuery(\n client: AlacarteCheckoutClient | null,\n biId: string | null,\n enabled: boolean,\n) {\n return useQuery({\n queryKey: alacarteKeys.orderDates(biId ?? 'none'),\n enabled: enabled && !!client && !!biId,\n queryFn: (): Promise<OrderDatesResult> => client!.getOrderDates(biId!),\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n"]}
|
|
@@ -15,6 +15,9 @@ export function useViaxSessionQuery(sessionUrl) {
|
|
|
15
15
|
enabled: !!sessionUrl,
|
|
16
16
|
queryFn: () => fetchEdxSession(sessionUrl),
|
|
17
17
|
staleTime: 5 * 60 * 1000,
|
|
18
|
+
// Avoid tearing down CheckoutGate (and remounting Checkout) when the
|
|
19
|
+
// learner alt-tabs away and back after the session becomes stale.
|
|
20
|
+
refetchOnWindowFocus: false,
|
|
18
21
|
retry: 1,
|
|
19
22
|
});
|
|
20
23
|
}
|
|
@@ -25,6 +28,7 @@ export function useCurrentUserSubscriberQuery(client, sessionReady) {
|
|
|
25
28
|
enabled: !!client && sessionReady,
|
|
26
29
|
queryFn: () => client.isCurrentUserSubscriber(),
|
|
27
30
|
staleTime: 5 * 60 * 1000,
|
|
31
|
+
refetchOnWindowFocus: false,
|
|
28
32
|
retry: 1,
|
|
29
33
|
});
|
|
30
34
|
}
|
|
@@ -42,6 +46,7 @@ export function useUserProfileQuery(sessionUrl, username) {
|
|
|
42
46
|
return fetchUserProfile(lmsBaseUrl, username);
|
|
43
47
|
},
|
|
44
48
|
staleTime: Infinity,
|
|
49
|
+
refetchOnWindowFocus: false,
|
|
45
50
|
retry: 1,
|
|
46
51
|
});
|
|
47
52
|
}
|
|
@@ -52,6 +57,7 @@ export function usePublicIpQuery(sessionReady) {
|
|
|
52
57
|
enabled: sessionReady,
|
|
53
58
|
queryFn: () => fetchPublicIp(),
|
|
54
59
|
staleTime: Infinity,
|
|
60
|
+
refetchOnWindowFocus: false,
|
|
55
61
|
retry: 1,
|
|
56
62
|
});
|
|
57
63
|
}
|
|
@@ -62,6 +68,7 @@ export function useIpGeolocationQuery(geolocationUrl, ipAddress) {
|
|
|
62
68
|
enabled: !!geolocationUrl && !!ipAddress,
|
|
63
69
|
queryFn: () => fetchIpGeolocation(geolocationUrl, ipAddress),
|
|
64
70
|
staleTime: Infinity,
|
|
71
|
+
refetchOnWindowFocus: false,
|
|
65
72
|
retry: 1,
|
|
66
73
|
});
|
|
67
74
|
}
|
|
@@ -89,6 +96,7 @@ export function useComplianceCheckQuery(complianceCheckUrl, inputs, ready) {
|
|
|
89
96
|
return fetchComplianceCheck(complianceCheckUrl, requestBody);
|
|
90
97
|
},
|
|
91
98
|
staleTime: Infinity,
|
|
99
|
+
refetchOnWindowFocus: false,
|
|
92
100
|
retry: 1,
|
|
93
101
|
});
|
|
94
102
|
}
|
|
@@ -110,6 +118,7 @@ export function useSubscriptionIdQuery(client, cartBiId, enabled) {
|
|
|
110
118
|
enabled: enabled && !!client && !!cartBiId,
|
|
111
119
|
queryFn: () => client.findSubscriptionBiId(cartBiId),
|
|
112
120
|
staleTime: Infinity,
|
|
121
|
+
refetchOnWindowFocus: false,
|
|
113
122
|
});
|
|
114
123
|
}
|
|
115
124
|
/**
|
|
@@ -125,6 +134,7 @@ export function useSubscriptionCourseKeyQuery(lmsBaseUrl, courseRunKey) {
|
|
|
125
134
|
enabled: !!lmsBaseUrl && !!courseRunKey,
|
|
126
135
|
queryFn: () => fetchSubscriptionCourseKey(lmsBaseUrl, courseRunKey),
|
|
127
136
|
staleTime: Infinity,
|
|
137
|
+
refetchOnWindowFocus: false,
|
|
128
138
|
retry: 1,
|
|
129
139
|
});
|
|
130
140
|
}
|
|
@@ -139,6 +149,7 @@ export function useCourseDetailQuery(client, sessionReady, courseRunKey) {
|
|
|
139
149
|
enabled: !!client && sessionReady && !!courseRunKey,
|
|
140
150
|
queryFn: () => client.getCourseDetail(courseRunKey),
|
|
141
151
|
staleTime: Infinity,
|
|
152
|
+
refetchOnWindowFocus: false,
|
|
142
153
|
retry: 1,
|
|
143
154
|
});
|
|
144
155
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCheckoutQueries.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutQueries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * query/useCheckoutQueries.ts — read-side TanStack Query hooks.\n *\n * useViaxSessionQuery runs on mount to exchange the edX session for a viax\n * token.\n */\nimport { useQuery } from '@tanstack/react-query';\nimport { isTerminalStatus } from '../../constants';\nimport { checkoutKeys } from './keys';\nimport {\n fetchEdxSession,\n fetchUserProfile,\n fetchPublicIp,\n fetchIpGeolocation,\n fetchComplianceCheck,\n fetchSubscriptionCourseKey\n} from '../../utils/provision';\nimport type { ComplianceCheckRequest } from '../../utils/provision';\nimport type {\n CheckoutClient, CartStatusResult, CourseDetail,\n} from '../../types';\n\n/** Exchange edX session cookies for a viax token on page load. */\nexport function useViaxSessionQuery(sessionUrl: string | null) {\n return useQuery({\n queryKey: checkoutKeys.viaxSession(),\n enabled: !!sessionUrl,\n queryFn: () => fetchEdxSession(sessionUrl!),\n staleTime: 5 * 60 * 1000,\n retry: 1,\n });\n}\n\n/** Checks whether the authenticated learner already has an active subscription. */\nexport function useCurrentUserSubscriberQuery(client: CheckoutClient | null, sessionReady: boolean) {\n return useQuery({\n queryKey: ['checkout', 'currentUserSubscriber'],\n enabled: !!client && sessionReady,\n queryFn: () => client!.isCurrentUserSubscriber(),\n staleTime: 5 * 60 * 1000,\n retry: 1,\n });\n}\n\n/**\n * Fetches the user's country and full name from their edX account profile.\n * Derives the LMS base URL from the session URL (same origin).\n * Returns nulls if the fetch fails or a field is absent — checkout stays open in that case.\n */\nexport function useUserProfileQuery(sessionUrl: string | null, username: string | null) {\n return useQuery({\n queryKey: ['checkout', 'userProfile', username],\n enabled: !!sessionUrl && !!username,\n queryFn: () => {\n const lmsBaseUrl = new URL(sessionUrl!).origin;\n return fetchUserProfile(lmsBaseUrl, username!);\n },\n staleTime: Infinity,\n retry: 1,\n });\n}\n\n/** Fetches the learner's current public IP once the viax session is ready. */\nexport function usePublicIpQuery(sessionReady: boolean) {\n return useQuery({\n queryKey: ['checkout', 'publicIp'],\n enabled: sessionReady,\n queryFn: () => fetchPublicIp(),\n staleTime: Infinity,\n retry: 1,\n });\n}\n\n/** Resolves city/etc. for the public IP via the LMS geolocation endpoint. */\nexport function useIpGeolocationQuery(geolocationUrl: string | null, ipAddress: string | null) {\n return useQuery({\n queryKey: ['checkout', 'ipGeolocation', ipAddress ?? 'none'],\n enabled: !!geolocationUrl && !!ipAddress,\n queryFn: () => fetchIpGeolocation(geolocationUrl!, ipAddress!),\n staleTime: Infinity,\n retry: 1,\n });\n}\n\nexport interface ComplianceCheckInputs {\n lmsUserId: string | null,\n fullName: string | null,\n country: string | null,\n city: string | null,\n username: string | null,\n ipAddress: string | null,\n courseIds: string[],\n}\n\n/**\n * Calls the ALC-307 compliance-check endpoint once identity, country, and\n * IP/city are resolved. Errors surface via isError; useCheckoutGate treats\n * those as fail-open (gateStatus = 'allowed') so CORS/network failures do\n * not block checkout — only an explicit isBlocked result does.\n */\nexport function useComplianceCheckQuery(\n complianceCheckUrl: string | null,\n inputs: ComplianceCheckInputs,\n ready: boolean,\n) {\n const {\n lmsUserId, fullName, country, city, username, ipAddress, courseIds,\n } = inputs;\n\n return useQuery({\n queryKey: ['checkout', 'complianceCheck', username ?? 'none', ipAddress ?? 'none'],\n enabled: !!complianceCheckUrl && ready && !!lmsUserId && !!username,\n queryFn: () => {\n const requestBody: ComplianceCheckRequest = {\n lms_user_id: lmsUserId!,\n full_name: fullName ?? '',\n country: country ?? '',\n city: city ?? '',\n user: username!,\n ip_address: ipAddress ?? '',\n course_ids: courseIds,\n };\n return fetchComplianceCheck(complianceCheckUrl!, requestBody);\n },\n staleTime: Infinity,\n retry: 1,\n });\n}\n\n/** Polls the cart state machine every second; stops on terminal states. */\nexport function useCartStatusQuery(client: CheckoutClient | null, cartBiId: string | null) {\n return useQuery({\n queryKey: checkoutKeys.status(cartBiId ?? 'none'),\n enabled: !!client && !!cartBiId,\n queryFn: (): Promise<CartStatusResult> => client!.getCartStatus(cartBiId!),\n refetchInterval: (query) =>\n isTerminalStatus(query.state.data?.status ?? null) ? false : 1000,\n refetchOnWindowFocus: false,\n gcTime: 0,\n });\n}\n\n/** Looks up the created subscription biId after success. */\nexport function useSubscriptionIdQuery(\n client: CheckoutClient | null,\n cartBiId: string | null,\n enabled: boolean,\n) {\n return useQuery({\n queryKey: checkoutKeys.subscriptionId(cartBiId ?? 'none'),\n enabled: enabled && !!client && !!cartBiId,\n queryFn: () => client!.findSubscriptionBiId(cartBiId!),\n staleTime: Infinity,\n });\n}\n\n/**\n * Resolves the Viax course key for a course run via\n * GET /api/subscription/course_runs/?key=<course run key>.\n * Used when the URL has both course_uid and course_run_key.\n * Pass null for lmsBaseUrl or courseRunKey to disable (e.g. when\n * SKIP_SUBSCRIPTION_COURSE_RUNS derives the key locally instead).\n */\nexport function useSubscriptionCourseKeyQuery(\n lmsBaseUrl: string | null,\n courseRunKey: string | null,\n) {\n return useQuery({\n queryKey: checkoutKeys.subscriptionCourseKey(courseRunKey ?? 'none'),\n enabled: !!lmsBaseUrl && !!courseRunKey,\n queryFn: () => fetchSubscriptionCourseKey(lmsBaseUrl!, courseRunKey!),\n staleTime: Infinity,\n retry: 1,\n });\n}\n\n/**\n * Fetches Viax course details by the `course` value returned from\n * GET /api/subscription/course_runs/ (not the course-run `key` or the raw URL param).\n * Skipped entirely when that value is absent.\n */\nexport function useCourseDetailQuery(\n client: CheckoutClient | null,\n sessionReady: boolean,\n courseRunKey: string | null,\n) {\n return useQuery({\n queryKey: checkoutKeys.courseDetail(courseRunKey ?? 'none'),\n enabled: !!client && sessionReady && !!courseRunKey,\n queryFn: (): Promise<CourseDetail | null> => client!.getCourseDetail(courseRunKey!),\n staleTime: Infinity,\n retry: 1,\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useCheckoutQueries.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutQueries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * query/useCheckoutQueries.ts — read-side TanStack Query hooks.\n *\n * useViaxSessionQuery runs on mount to exchange the edX session for a viax\n * token.\n */\nimport { useQuery } from '@tanstack/react-query';\nimport { isTerminalStatus } from '../../constants';\nimport { checkoutKeys } from './keys';\nimport {\n fetchEdxSession,\n fetchUserProfile,\n fetchPublicIp,\n fetchIpGeolocation,\n fetchComplianceCheck,\n fetchSubscriptionCourseKey\n} from '../../utils/provision';\nimport type { ComplianceCheckRequest } from '../../utils/provision';\nimport type {\n CheckoutClient, CartStatusResult, CourseDetail,\n} from '../../types';\n\n/** Exchange edX session cookies for a viax token on page load. */\nexport function useViaxSessionQuery(sessionUrl: string | null) {\n return useQuery({\n queryKey: checkoutKeys.viaxSession(),\n enabled: !!sessionUrl,\n queryFn: () => fetchEdxSession(sessionUrl!),\n staleTime: 5 * 60 * 1000,\n // Avoid tearing down CheckoutGate (and remounting Checkout) when the\n // learner alt-tabs away and back after the session becomes stale.\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/** Checks whether the authenticated learner already has an active subscription. */\nexport function useCurrentUserSubscriberQuery(client: CheckoutClient | null, sessionReady: boolean) {\n return useQuery({\n queryKey: ['checkout', 'currentUserSubscriber'],\n enabled: !!client && sessionReady,\n queryFn: () => client!.isCurrentUserSubscriber(),\n staleTime: 5 * 60 * 1000,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/**\n * Fetches the user's country and full name from their edX account profile.\n * Derives the LMS base URL from the session URL (same origin).\n * Returns nulls if the fetch fails or a field is absent — checkout stays open in that case.\n */\nexport function useUserProfileQuery(sessionUrl: string | null, username: string | null) {\n return useQuery({\n queryKey: ['checkout', 'userProfile', username],\n enabled: !!sessionUrl && !!username,\n queryFn: () => {\n const lmsBaseUrl = new URL(sessionUrl!).origin;\n return fetchUserProfile(lmsBaseUrl, username!);\n },\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/** Fetches the learner's current public IP once the viax session is ready. */\nexport function usePublicIpQuery(sessionReady: boolean) {\n return useQuery({\n queryKey: ['checkout', 'publicIp'],\n enabled: sessionReady,\n queryFn: () => fetchPublicIp(),\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/** Resolves city/etc. for the public IP via the LMS geolocation endpoint. */\nexport function useIpGeolocationQuery(geolocationUrl: string | null, ipAddress: string | null) {\n return useQuery({\n queryKey: ['checkout', 'ipGeolocation', ipAddress ?? 'none'],\n enabled: !!geolocationUrl && !!ipAddress,\n queryFn: () => fetchIpGeolocation(geolocationUrl!, ipAddress!),\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\nexport interface ComplianceCheckInputs {\n lmsUserId: string | null,\n fullName: string | null,\n country: string | null,\n city: string | null,\n username: string | null,\n ipAddress: string | null,\n courseIds: string[],\n}\n\n/**\n * Calls the ALC-307 compliance-check endpoint once identity, country, and\n * IP/city are resolved. Errors surface via isError; useCheckoutGate treats\n * those as fail-open (gateStatus = 'allowed') so CORS/network failures do\n * not block checkout — only an explicit isBlocked result does.\n */\nexport function useComplianceCheckQuery(\n complianceCheckUrl: string | null,\n inputs: ComplianceCheckInputs,\n ready: boolean,\n) {\n const {\n lmsUserId, fullName, country, city, username, ipAddress, courseIds,\n } = inputs;\n\n return useQuery({\n queryKey: ['checkout', 'complianceCheck', username ?? 'none', ipAddress ?? 'none'],\n enabled: !!complianceCheckUrl && ready && !!lmsUserId && !!username,\n queryFn: () => {\n const requestBody: ComplianceCheckRequest = {\n lms_user_id: lmsUserId!,\n full_name: fullName ?? '',\n country: country ?? '',\n city: city ?? '',\n user: username!,\n ip_address: ipAddress ?? '',\n course_ids: courseIds,\n };\n return fetchComplianceCheck(complianceCheckUrl!, requestBody);\n },\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/** Polls the cart state machine every second; stops on terminal states. */\nexport function useCartStatusQuery(client: CheckoutClient | null, cartBiId: string | null) {\n return useQuery({\n queryKey: checkoutKeys.status(cartBiId ?? 'none'),\n enabled: !!client && !!cartBiId,\n queryFn: (): Promise<CartStatusResult> => client!.getCartStatus(cartBiId!),\n refetchInterval: (query) =>\n isTerminalStatus(query.state.data?.status ?? null) ? false : 1000,\n refetchOnWindowFocus: false,\n gcTime: 0,\n });\n}\n\n/** Looks up the created subscription biId after success. */\nexport function useSubscriptionIdQuery(\n client: CheckoutClient | null,\n cartBiId: string | null,\n enabled: boolean,\n) {\n return useQuery({\n queryKey: checkoutKeys.subscriptionId(cartBiId ?? 'none'),\n enabled: enabled && !!client && !!cartBiId,\n queryFn: () => client!.findSubscriptionBiId(cartBiId!),\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n });\n}\n\n/**\n * Resolves the Viax course key for a course run via\n * GET /api/subscription/course_runs/?key=<course run key>.\n * Used when the URL has both course_uid and course_run_key.\n * Pass null for lmsBaseUrl or courseRunKey to disable (e.g. when\n * SKIP_SUBSCRIPTION_COURSE_RUNS derives the key locally instead).\n */\nexport function useSubscriptionCourseKeyQuery(\n lmsBaseUrl: string | null,\n courseRunKey: string | null,\n) {\n return useQuery({\n queryKey: checkoutKeys.subscriptionCourseKey(courseRunKey ?? 'none'),\n enabled: !!lmsBaseUrl && !!courseRunKey,\n queryFn: () => fetchSubscriptionCourseKey(lmsBaseUrl!, courseRunKey!),\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/**\n * Fetches Viax course details by the `course` value returned from\n * GET /api/subscription/course_runs/ (not the course-run `key` or the raw URL param).\n * Skipped entirely when that value is absent.\n */\nexport function useCourseDetailQuery(\n client: CheckoutClient | null,\n sessionReady: boolean,\n courseRunKey: string | null,\n) {\n return useQuery({\n queryKey: checkoutKeys.courseDetail(courseRunKey ?? 'none'),\n enabled: !!client && sessionReady && !!courseRunKey,\n queryFn: (): Promise<CourseDetail | null> => client!.getCourseDetail(courseRunKey!),\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alacarte.js","sourceRoot":"","sources":["../../src/types/alacarte.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * types/alacarte.ts — domain types for the à la carte checkout flow\n * (tCheckout / tCheckoutSelect / tCheckoutUpdate / tOwnedProducts), a separate\n * bounded context from the subscription checkout modeled in ../types.ts.\n * See alacarte-checkout-api.md and alacarte-owned-products-api.md at the repo\n * root for the full contract.\n */\nimport type { GqlRunner, CourseDetail } from '../types';\n\nexport type { GqlRunner };\n\n// --- Checkout status -----------------------------------------------------------\nexport type AlacarteStatus =\n | 'CART_CREATED'\n | 'CART_SANCTION_PASSED'\n | 'CART_PAYMENT_FAILED';\n\n// --- Money -----------------------------------------------------------------------\nexport interface AlacarteMoney {\n amount: number,\n currency: string,\n}\n\n// --- Checkout line (discriminated union on `kind`) --------------------------------\n/** Where the line discount came from — coupon code vs automatic offer (bundle / first-order). */\nexport type AlacarteDiscountSource = 'coupon' | 'automatic';\n\nexport interface AlacarteCourseLine {\n biiId: string,\n kind: 'course',\n courseName: string,\n courseUid: string,\n courseRunKey: string,\n /** The seat being bought on that run, e.g. \"verified\". */\n seatType: string | null,\n price: AlacarteMoney | null,\n /** Taken off by an applied coupon or automatic offer; absent when neither applies. */\n discount: AlacarteMoney | null,\n /** `\"coupon\"` or `\"automatic\"`; absent with no discount. */\n discountSource?: AlacarteDiscountSource | null,\n /** Discount name for the order summary, e.g. \"First order discount\". */\n discountName?: string | null,\n /** price less discount. */\n amountDue: AlacarteMoney | null,\n billingAddressSet: boolean,\n}\n\n/** Owned member course on a program checkout line (simpler than OwnedCourseRef). */\nexport interface AlacarteProgramOwnedCourse {\n courseId: string,\n name: string,\n via: 'order' | 'program' | 'enrollment',\n}\n\nexport interface AlacarteProgramLine {\n biiId: string,\n kind: 'program',\n programId: string,\n programName: string,\n price: AlacarteMoney | null,\n /** Taken off for member courses the learner already owns; absent when they own none. */\n deduction: AlacarteMoney | null,\n /**\n * Member courses the learner already owns — explains the deduction\n * (\"Already yours: …\"). Empty/absent when they own none.\n */\n ownedCourses?: AlacarteProgramOwnedCourse[] | null,\n /** Taken off by an applied coupon or automatic offer; absent when neither applies. */\n discount: AlacarteMoney | null,\n /** `\"coupon\"` or `\"automatic\"`; absent with no discount. */\n discountSource?: AlacarteDiscountSource | null,\n /** Discount name for the order summary, e.g. \"Bundle discount\". */\n discountName?: string | null,\n /** price less deduction less discount. */\n amountDue: AlacarteMoney | null,\n billingAddressSet: boolean,\n}\n\nexport type AlacarteLine = AlacarteCourseLine | AlacarteProgramLine;\n\n/** Present when the learner switches product while a coupon is on the cart. */\nexport type AlacarteCouponRecheckOutcome = 'kept' | 'rejected' | 'unverified';\n\nexport interface AlacarteCouponRecheck {\n outcome: AlacarteCouponRecheckOutcome,\n /** Coupon error code when outcome is `rejected`. */\n reason?: AlacarteErrorCode | string | null,\n}\n\n/** Last card-decline details; cleared when the next payment attempt starts. */\nexport interface AlacartePaymentDecline {\n code?: string | null,\n /** Ready to show the learner. */\n message: string,\n}\n\n/** Why a selection was refused — see section 5 of alacarte-checkout-api.md. */\nexport interface AlacarteBlocked {\n reason: 'ALREADY_OWNED' | 'NOT_PURCHASABLE',\n productId: string,\n kind: 'course' | 'program',\n /** ALREADY_OWNED only — where to send the learner (the learner dashboard). */\n route?: string,\n /** ALREADY_OWNED only. */\n ownedCourses?: OwnedCourseRef[],\n /** NOT_PURCHASABLE only — the catalog's reason, e.g. \"program_not_one_click_eligible\". */\n detail?: string,\n}\n\n/** Present when the price the learner saw (viewedPrice) differs from what checkout resolves now. */\nexport interface AlacartePriceChanged {\n viewed: AlacarteMoney,\n current: AlacarteMoney,\n}\n\n// --- Checkout envelope -------------------------------------------------------------\nexport interface AlacarteCheckout {\n biId: string,\n uid: string,\n status: AlacarteStatus,\n // Not always present in real responses (absent, not null, when no coupon was entered).\n couponCode?: string | null,\n termsAcceptedAt: string | null,\n acceptedAmount?: AlacarteMoney | null,\n priceAccepted?: boolean,\n /** True when this is the learner's first à-la-carte order (first-order offer applies). */\n firstOrder?: boolean,\n /** Present after the card was declined on the last attempt. */\n paymentDecline?: AlacartePaymentDecline | null,\n line: AlacarteLine | null,\n}\n\nexport type AlacarteErrorCode =\n | 'NOT_AUTHENTICATED'\n | 'RUN_KEY_REQUIRED'\n | 'CONFLICTING_SELECTION'\n | 'NOTHING_SELECTED'\n | 'COURSE_NOT_FOUND'\n | 'PROGRAM_NOT_FOUND'\n | 'CHECKOUT_NOT_FOUND'\n | 'CHECKOUT_NOT_YOURS'\n | 'PRODUCT_NOT_FOUND'\n // Blocked selection — details in the `blocked` envelope field.\n | 'ALREADY_OWNED'\n | 'NOT_PURCHASABLE'\n // No price can be resolved for the product right now.\n | 'PRICING_UNAVAILABLE'\n // Coupon errors — returned from tCheckoutUpdate, checkout left unchanged.\n | 'COUPON_INVALID'\n | 'COUPON_NOT_ACTIVE'\n | 'COUPON_EXPIRED'\n | 'COUPON_CAP_REACHED'\n | 'COUPON_PRODUCT_NOT_ELIGIBLE'\n | 'COUPON_PARTY_NOT_ELIGIBLE'\n | 'COUPON_PER_CUSTOMER_LIMIT'\n | 'COUPON_REJECTED'\n // tPlacedOrder errors.\n | 'ORDER_ID_REQUIRED'\n | 'ORDER_NOT_FOUND'\n | 'NOT_PLACED'\n | 'ORDER_READ_FAILED';\n\nexport interface AlacarteReplaced {\n courseName?: string,\n courseUid?: string,\n courseRunKey?: string,\n programId?: string,\n programName?: string,\n}\n\n/** Shared response envelope for tCheckout / tCheckoutSelect / tCheckoutUpdate. */\nexport interface AlacarteCheckoutResult {\n ok: boolean,\n checkout: AlacarteCheckout | null,\n replaced?: AlacarteReplaced,\n priceChanged?: AlacartePriceChanged,\n blocked?: AlacarteBlocked,\n /** Present when select switches product while a coupon was applied. */\n couponRecheck?: AlacarteCouponRecheck,\n error?: AlacarteErrorCode | string,\n}\n\n// --- Owned products ------------------------------------------------------------------\nexport type OwnedProductStatus = 'full' | 'partial' | 'none';\n\nexport interface OwnedCourseRef {\n courseId: string,\n via: 'order' | 'program' | 'enrollment',\n orderBiId?: string,\n enrollmentBiId?: string,\n programId?: string,\n name: string,\n slug: string,\n}\n\nexport interface OwnedProductPrice {\n full: number,\n deduction: number,\n amountDue: number,\n currency: string,\n}\n\nexport interface OwnedProduct {\n productId: string,\n kind: 'course' | 'program',\n name: string,\n slug: string,\n status: OwnedProductStatus,\n memberCount?: number,\n ownedCourses: OwnedCourseRef[],\n /** Pricing breakdown — available when the backend returns it (e.g. for\n * already-owned products where the checkout line is not created). */\n price?: OwnedProductPrice | null,\n}\n\nexport interface OwnedProductResult {\n ok: boolean,\n product: OwnedProduct | null,\n error?: AlacarteErrorCode | string,\n}\n\nexport interface OwnedProgramRef {\n programId: string,\n orderBiId: string,\n}\n\nexport interface OwnedProductsSummary {\n courses: OwnedCourseRef[],\n programs: OwnedProgramRef[],\n}\n\nexport interface AllOwnedProductsResult {\n ok: boolean,\n owned: OwnedProductsSummary | null,\n error?: AlacarteErrorCode | string,\n}\n\n// --- Program details (filterTwouProgram, by uid) --------------------------------------\nexport interface ProgramCourseRef {\n id: string,\n uid: string | null,\n maName: string | null,\n maDescription: string | null,\n tShortDescription: string | null,\n imageUrl: string | null,\n tBrandText: string | null,\n tOrgLogoUrl: string | null,\n tUrlCourse: string | null,\n tMarketingSlug: string | null,\n tStatus: { code: string } | null,\n}\n\nexport interface ProgramDetail {\n uid: string,\n tEffortLow: number | null,\n tEnrollmentCount: number | null,\n tMarketingSlug: string | null,\n tSkills: string[] | null,\n tFaq: string | null,\n tLevelType: string | null,\n tCourses: ProgramCourseRef[],\n}\n\n/** Course or program detail, depending on which was selected. */\nexport type ProductDetail = CourseDetail | ProgramDetail;\n\n// --- Banner details (PDP banner before checkout) ---------------------------------\n\nexport interface BannerCourseInstructor {\n tGivenName: string | null,\n tFamilyName: string | null,\n tOrganization: string | null,\n tInstructorUuid: string | null,\n tSortValue: number | null,\n}\n\nexport interface BannerCourseDetail {\n uid: string,\n id: string | null,\n maName: string,\n tEnrollmentCount: number | null,\n tAlacartePurchasable: boolean | null,\n tCourseKey: string | null,\n tShortDescription: string | null,\n maDescription: string | null,\n tBrandText: string | null,\n tOrgLogoUrl: string | null,\n imageUrl: string | null,\n tUrlCourse: string | null,\n tMarketingSlug: string | null,\n tLevelType: string | null,\n tPacingType: string | null,\n tDurationLow: number | null,\n tDurationHigh: number | null,\n tDurationUnit: string | null,\n tEffortLow: number | null,\n tEffortHigh: number | null,\n tEffortUnit: string | null,\n tLanguage: string | null,\n tExpectedLearningItems: string | null,\n tSkills: string[] | null,\n tPrerequisites: string | null,\n tSyllabus: string | null,\n tFaq: string | null,\n tTestimonials: string | null,\n tTranscriptLanguages: string[] | null,\n tInstructors: BannerCourseInstructor[],\n}\n\nexport interface BannerProgramCourse {\n id: string,\n uid: string | null,\n maName: string | null,\n tShortDescription: string | null,\n maDescription: string | null,\n tBrandText: string | null,\n tOrgLogoUrl: string | null,\n tCourseKey: string | null,\n imageUrl: string | null,\n tUrlCourse: string | null,\n tMarketingSlug: string | null,\n tStatus: { code: string } | null,\n}\n\nexport interface BannerProgramDetail {\n uid: string,\n tEffortLow: number | null,\n tEnrollmentCount: number | null,\n tMarketingSlug: string | null,\n tAlacartePurchasable: boolean | null,\n tSkills: string[] | null,\n tFaq: string | null,\n tLevelType: string | null,\n imageUrl: string | null,\n tBannerImageUrl: string | null,\n tCourses: BannerProgramCourse[],\n}\n\nexport type BannerData =\n | { kind: 'course', data: BannerCourseDetail }\n | { kind: 'program', data: BannerProgramDetail };\n\n// --- Placed order (tPlacedOrder, confirmation page) -------------------------------------\nexport interface PlacedOrderAmounts {\n price: number,\n deduction: number,\n discount: number,\n paid: number,\n currency: string,\n}\n\nexport interface PlacedOrderLine {\n lineId: string,\n kind: 'course' | 'program',\n productId?: string,\n productUid?: string,\n name: string,\n /** Present on course lines and on each course line of a program order. */\n courseRunKey?: string | null,\n seatType?: string | null,\n /** Set on every line of a program order (one line per course bought). */\n programId?: string,\n bundleRef?: string,\n discountRuleId?: string,\n amounts: PlacedOrderAmounts,\n}\n\nexport interface PlacedOrder {\n orderId: string,\n orderNumber: string,\n status: string,\n couponCode: string | null,\n paymentMethod: { brand: string, last4: string } | null,\n lines: PlacedOrderLine[],\n totals: PlacedOrderAmounts,\n}\n\nexport interface PlacedOrderResult {\n ok: boolean,\n order: PlacedOrder | null,\n error?: AlacarteErrorCode | string,\n}\n\n/** getTwouOrder(biId) — order placement/creation timestamps, ISO strings. */\nexport interface OrderDates {\n tPlacedAt: string | null,\n biCreatedAt: string | null,\n}\n\nexport interface OrderDatesResult {\n ok: boolean,\n dates: OrderDates | null,\n}\n\n/** Billing for tCheckoutPlaceOrder. Only `country` (ISO-2) is required;\n * name/line/city/etc. are optional when the UI doesn't collect a full address. */\nexport interface AlacarteBillingAddress {\n country: string,\n firstName?: string,\n lastName?: string,\n line1?: string,\n line2?: string,\n city?: string,\n state?: string,\n postcode?: string,\n}\n\n/** Stripe payment details returned by a successful tCheckoutPlaceOrder. */\nexport interface AlacartePlaceOrderPayment {\n clientSecret: string,\n paymentIntentId: string,\n /** Amount in cents. */\n amount: number,\n currency: string,\n}\n\nexport type AlacartePlaceOrderErrorCode =\n | 'TERMS_NOT_ACCEPTED'\n | 'PRICE_NOT_ACCEPTED'\n | 'BILLING_ADDRESS_REQUIRED'\n | 'SANCTIONS_BLOCKED'\n | 'COUPON_UNAVAILABLE'\n | 'PAYMENT_NOT_STARTED'\n | 'CHECKOUT_NOT_FOUND'\n | 'NOT_AUTHENTICATED';\n\nexport interface AlacartePlaceOrderApiResult {\n ok: boolean,\n payment: AlacartePlaceOrderPayment | null,\n /** Present on some failures (e.g. PRICE_NOT_ACCEPTED, COUPON_UNAVAILABLE). */\n checkout?: AlacarteCheckout | null,\n error?: AlacartePlaceOrderErrorCode | string,\n}\n\n// --- Product selector (derived from query params) --------------------------------------\nexport type ProductSelector =\n | { kind: 'course', courseUid: string, runKey: string }\n | { kind: 'program', programUid: string }\n | { kind: 'invalid' }\n | { kind: 'none' };\n\n// --- Client surface --------------------------------------------------------------------\nexport interface AlacarteCheckoutClient {\n gql: GqlRunner,\n setToken: (token: string) => void,\n getCheckout: () => Promise<AlacarteCheckoutResult>,\n /** viewedPrice: the amount the learner saw on the page before checkout (USD, before any coupon). */\n selectCourse: (courseUid: string, runKey: string, viewedPrice?: number) => Promise<AlacarteCheckoutResult>,\n selectProgram: (programUid: string, viewedPrice?: number) => Promise<AlacarteCheckoutResult>,\n updateCheckout: (args: {\n biId?: string, couponCode?: string, termsAccepted?: boolean, acceptPrice?: boolean,\n }) => Promise<AlacarteCheckoutResult>,\n getOwnedProduct: (productId: string) => Promise<OwnedProductResult>,\n getAllOwnedProducts: () => Promise<AllOwnedProductsResult>,\n getProgramDetail: (programUid: string) => Promise<ProgramDetail | null>,\n getCourseDetail: (courseKey: string) => Promise<CourseDetail | null>,\n getPlacedOrder: (biId: string) => Promise<PlacedOrderResult>,\n getOrderDates: (biId: string) => Promise<OrderDatesResult>,\n /** One-call place order — returns Stripe client secret (section 5a). */\n placeCheckoutOrder: (\n biId: string,\n billing?: AlacarteBillingAddress,\n ) => Promise<AlacartePlaceOrderApiResult>,\n}\n"]}
|
|
1
|
+
{"version":3,"file":"alacarte.js","sourceRoot":"","sources":["../../src/types/alacarte.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * types/alacarte.ts — domain types for the à la carte checkout flow\n * (tCheckout / tCheckoutSelect / tCheckoutUpdate / tOwnedProducts), a separate\n * bounded context from the subscription checkout modeled in ../types.ts.\n * See alacarte-checkout-api.md and alacarte-owned-products-api.md at the repo\n * root for the full contract.\n */\nimport type { GqlRunner, CourseDetail } from '../types';\n\nexport type { GqlRunner };\n\n// --- Checkout status -----------------------------------------------------------\nexport type AlacarteStatus =\n | 'CART_CREATED'\n | 'CART_SANCTION_PASSED'\n | 'CART_PAYMENT_FAILED';\n\n// --- Money -----------------------------------------------------------------------\nexport interface AlacarteMoney {\n amount: number,\n currency: string,\n}\n\n// --- Checkout line (discriminated union on `kind`) --------------------------------\n/** Where the line discount came from — coupon code vs automatic offer (bundle / first-order). */\nexport type AlacarteDiscountSource = 'coupon' | 'automatic';\n\nexport interface AlacarteCourseLine {\n biiId: string,\n kind: 'course',\n courseName: string,\n courseUid: string,\n courseRunKey: string,\n /** The seat being bought on that run, e.g. \"verified\". */\n seatType: string | null,\n price: AlacarteMoney | null,\n /** Taken off by an applied coupon or automatic offer; absent when neither applies. */\n discount: AlacarteMoney | null,\n /** `\"coupon\"` or `\"automatic\"`; absent with no discount. */\n discountSource?: AlacarteDiscountSource | null,\n /** Discount name for the order summary, e.g. \"First order discount\". */\n discountName?: string | null,\n /** price less discount. */\n amountDue: AlacarteMoney | null,\n billingAddressSet: boolean,\n}\n\n/** Owned member course on a program checkout line (simpler than OwnedCourseRef). */\nexport interface AlacarteProgramOwnedCourse {\n courseId: string,\n name: string,\n via: 'order' | 'program' | 'enrollment',\n}\n\nexport interface AlacarteProgramLine {\n biiId: string,\n kind: 'program',\n programId: string,\n programName: string,\n price: AlacarteMoney | null,\n /** Taken off for member courses the learner already owns; absent when they own none. */\n deduction: AlacarteMoney | null,\n /**\n * Member courses the learner already owns — explains the deduction\n * (\"Already yours: …\"). Empty/absent when they own none.\n */\n ownedCourses?: AlacarteProgramOwnedCourse[] | null,\n /** Taken off by an applied coupon or automatic offer; absent when neither applies. */\n discount: AlacarteMoney | null,\n /** `\"coupon\"` or `\"automatic\"`; absent with no discount. */\n discountSource?: AlacarteDiscountSource | null,\n /** Discount name for the order summary, e.g. \"Bundle discount\". */\n discountName?: string | null,\n /** price less deduction less discount. */\n amountDue: AlacarteMoney | null,\n billingAddressSet: boolean,\n}\n\nexport type AlacarteLine = AlacarteCourseLine | AlacarteProgramLine;\n\n/** Present when the learner switches product while a coupon is on the cart. */\nexport type AlacarteCouponRecheckOutcome = 'kept' | 'rejected' | 'unverified';\n\nexport interface AlacarteCouponRecheck {\n outcome: AlacarteCouponRecheckOutcome,\n /** Coupon error code when outcome is `rejected`. */\n reason?: AlacarteErrorCode | string | null,\n}\n\n/** Last card-decline details; cleared when the next payment attempt starts. */\nexport interface AlacartePaymentDecline {\n code?: string | null,\n /** Ready to show the learner. */\n message: string,\n}\n\n/** Why a selection was refused — see section 5 of alacarte-checkout-api.md. */\nexport interface AlacarteBlocked {\n reason: 'ALREADY_OWNED' | 'NOT_PURCHASABLE',\n productId: string,\n kind: 'course' | 'program',\n /** ALREADY_OWNED only — where to send the learner (the learner dashboard). */\n route?: string,\n /** ALREADY_OWNED only. */\n ownedCourses?: OwnedCourseRef[],\n /** NOT_PURCHASABLE only — the catalog's reason, e.g. \"program_not_one_click_eligible\". */\n detail?: string,\n}\n\n/** Present when the price the learner saw (viewedPrice) differs from what checkout resolves now. */\nexport interface AlacartePriceChanged {\n viewed: AlacarteMoney,\n current: AlacarteMoney,\n}\n\n// --- Checkout envelope -------------------------------------------------------------\nexport interface AlacarteCheckout {\n biId: string,\n uid: string,\n status: AlacarteStatus,\n // Not always present in real responses (absent, not null, when no coupon was entered).\n couponCode?: string | null,\n termsAcceptedAt: string | null,\n acceptedAmount?: AlacarteMoney | null,\n priceAccepted?: boolean,\n /** True when this is the learner's first à-la-carte order (first-order offer applies). */\n firstOrder?: boolean,\n /** Present after the card was declined on the last attempt. */\n paymentDecline?: AlacartePaymentDecline | null,\n line: AlacarteLine | null,\n}\n\nexport type AlacarteErrorCode =\n | 'NOT_AUTHENTICATED'\n | 'RUN_KEY_REQUIRED'\n | 'CONFLICTING_SELECTION'\n | 'NOTHING_SELECTED'\n | 'COURSE_NOT_FOUND'\n | 'PROGRAM_NOT_FOUND'\n | 'CHECKOUT_NOT_FOUND'\n | 'CHECKOUT_NOT_YOURS'\n | 'PRODUCT_NOT_FOUND'\n // Blocked selection — details in the `blocked` envelope field.\n | 'ALREADY_OWNED'\n | 'NOT_PURCHASABLE'\n // No price can be resolved for the product right now.\n | 'PRICING_UNAVAILABLE'\n // Coupon errors — returned from tCheckoutUpdate, checkout left unchanged.\n | 'COUPON_INVALID'\n | 'COUPON_NOT_ACTIVE'\n | 'COUPON_EXPIRED'\n | 'COUPON_CAP_REACHED'\n | 'COUPON_PRODUCT_NOT_ELIGIBLE'\n | 'COUPON_PARTY_NOT_ELIGIBLE'\n | 'COUPON_PER_CUSTOMER_LIMIT'\n | 'COUPON_REJECTED'\n // tPlacedOrder errors.\n | 'ORDER_ID_REQUIRED'\n | 'ORDER_NOT_FOUND'\n | 'NOT_PLACED'\n | 'ORDER_READ_FAILED';\n\nexport interface AlacarteReplaced {\n courseName?: string,\n courseUid?: string,\n courseRunKey?: string,\n programId?: string,\n programName?: string,\n}\n\n/** Shared response envelope for tCheckout / tCheckoutSelect / tCheckoutUpdate. */\nexport interface AlacarteCheckoutResult {\n ok: boolean,\n checkout: AlacarteCheckout | null,\n replaced?: AlacarteReplaced,\n priceChanged?: AlacartePriceChanged,\n blocked?: AlacarteBlocked,\n /** Present when select switches product while a coupon was applied. */\n couponRecheck?: AlacarteCouponRecheck,\n error?: AlacarteErrorCode | string,\n}\n\n// --- Owned products ------------------------------------------------------------------\nexport type OwnedProductStatus = 'full' | 'partial' | 'none';\n\nexport interface OwnedCourseRef {\n courseId: string,\n via: 'order' | 'program' | 'enrollment',\n orderBiId?: string,\n enrollmentBiId?: string,\n programId?: string,\n name: string,\n slug: string,\n}\n\nexport interface OwnedProductPrice {\n full: number,\n deduction: number,\n amountDue: number,\n currency: string,\n}\n\nexport interface OwnedProduct {\n productId: string,\n kind: 'course' | 'program',\n name: string,\n slug: string,\n status: OwnedProductStatus,\n memberCount?: number,\n ownedCourses: OwnedCourseRef[],\n /** Pricing breakdown — available when the backend returns it (e.g. for\n * already-owned products where the checkout line is not created). */\n price?: OwnedProductPrice | null,\n}\n\nexport interface OwnedProductResult {\n ok: boolean,\n product: OwnedProduct | null,\n error?: AlacarteErrorCode | string,\n}\n\nexport interface OwnedProgramRef {\n programId: string,\n orderBiId: string,\n}\n\nexport interface OwnedProductsSummary {\n courses: OwnedCourseRef[],\n programs: OwnedProgramRef[],\n}\n\nexport interface AllOwnedProductsResult {\n ok: boolean,\n owned: OwnedProductsSummary | null,\n error?: AlacarteErrorCode | string,\n}\n\n// --- Program details (filterTwouProgram, by tProgramUuid) -----------------------------\nexport interface ProgramCourseRef {\n id: string,\n uid: string | null,\n maName: string | null,\n maDescription: string | null,\n tShortDescription: string | null,\n imageUrl: string | null,\n tBrandText: string | null,\n tOrgLogoUrl: string | null,\n tUrlCourse: string | null,\n tMarketingSlug: string | null,\n tStatus: { code: string } | null,\n}\n\nexport interface ProgramDetail {\n uid: string,\n tEffortLow: number | null,\n tEnrollmentCount: number | null,\n tMarketingSlug: string | null,\n tSkills: string[] | null,\n tFaq: string | null,\n tLevelType: string | null,\n tCourses: ProgramCourseRef[],\n}\n\n/** Course or program detail, depending on which was selected. */\nexport type ProductDetail = CourseDetail | ProgramDetail;\n\n// --- Banner details (PDP banner before checkout) ---------------------------------\n\nexport interface BannerCourseInstructor {\n tGivenName: string | null,\n tFamilyName: string | null,\n tOrganization: string | null,\n tInstructorUuid: string | null,\n tSortValue: number | null,\n}\n\nexport interface BannerCourseDetail {\n uid: string,\n id: string | null,\n maName: string,\n tEnrollmentCount: number | null,\n tAlacartePurchasable: boolean | null,\n tCourseKey: string | null,\n tShortDescription: string | null,\n maDescription: string | null,\n tBrandText: string | null,\n tOrgLogoUrl: string | null,\n imageUrl: string | null,\n tUrlCourse: string | null,\n tMarketingSlug: string | null,\n tLevelType: string | null,\n tPacingType: string | null,\n tDurationLow: number | null,\n tDurationHigh: number | null,\n tDurationUnit: string | null,\n tEffortLow: number | null,\n tEffortHigh: number | null,\n tEffortUnit: string | null,\n tLanguage: string | null,\n tExpectedLearningItems: string | null,\n tSkills: string[] | null,\n tPrerequisites: string | null,\n tSyllabus: string | null,\n tFaq: string | null,\n tTestimonials: string | null,\n tTranscriptLanguages: string[] | null,\n tInstructors: BannerCourseInstructor[],\n}\n\nexport interface BannerProgramCourse {\n id: string,\n uid: string | null,\n maName: string | null,\n tShortDescription: string | null,\n maDescription: string | null,\n tBrandText: string | null,\n tOrgLogoUrl: string | null,\n tCourseKey: string | null,\n imageUrl: string | null,\n tUrlCourse: string | null,\n tMarketingSlug: string | null,\n tStatus: { code: string } | null,\n}\n\nexport interface BannerProgramDetail {\n uid: string,\n tEffortLow: number | null,\n tEnrollmentCount: number | null,\n tMarketingSlug: string | null,\n tAlacartePurchasable: boolean | null,\n tSkills: string[] | null,\n tFaq: string | null,\n tLevelType: string | null,\n imageUrl: string | null,\n tBannerImageUrl: string | null,\n tCourses: BannerProgramCourse[],\n}\n\nexport type BannerData =\n | { kind: 'course', data: BannerCourseDetail }\n | { kind: 'program', data: BannerProgramDetail };\n\n// --- Placed order (tPlacedOrder, confirmation page) -------------------------------------\nexport interface PlacedOrderAmounts {\n price: number,\n deduction: number,\n discount: number,\n paid: number,\n currency: string,\n}\n\nexport interface PlacedOrderLine {\n lineId: string,\n kind: 'course' | 'program',\n productId?: string,\n productUid?: string,\n name: string,\n /** Present on course lines and on each course line of a program order. */\n courseRunKey?: string | null,\n seatType?: string | null,\n /** Set on every line of a program order (one line per course bought). */\n programId?: string,\n bundleRef?: string,\n discountRuleId?: string,\n amounts: PlacedOrderAmounts,\n}\n\nexport interface PlacedOrder {\n orderId: string,\n orderNumber: string,\n status: string,\n couponCode: string | null,\n paymentMethod: { brand: string, last4: string } | null,\n lines: PlacedOrderLine[],\n totals: PlacedOrderAmounts,\n}\n\nexport interface PlacedOrderResult {\n ok: boolean,\n order: PlacedOrder | null,\n error?: AlacarteErrorCode | string,\n}\n\n/** getTwouOrder(biId) — order placement/creation timestamps, ISO strings. */\nexport interface OrderDates {\n tPlacedAt: string | null,\n biCreatedAt: string | null,\n}\n\nexport interface OrderDatesResult {\n ok: boolean,\n dates: OrderDates | null,\n}\n\n/** Billing for tCheckoutPlaceOrder. Only `country` (ISO-2) is required;\n * name/line/city/etc. are optional when the UI doesn't collect a full address. */\nexport interface AlacarteBillingAddress {\n country: string,\n firstName?: string,\n lastName?: string,\n line1?: string,\n line2?: string,\n city?: string,\n state?: string,\n postcode?: string,\n}\n\n/** Stripe payment details returned by a successful tCheckoutPlaceOrder. */\nexport interface AlacartePlaceOrderPayment {\n clientSecret: string,\n paymentIntentId: string,\n /** Amount in cents. */\n amount: number,\n currency: string,\n}\n\nexport type AlacartePlaceOrderErrorCode =\n | 'TERMS_NOT_ACCEPTED'\n | 'PRICE_NOT_ACCEPTED'\n | 'BILLING_ADDRESS_REQUIRED'\n | 'SANCTIONS_BLOCKED'\n | 'COUPON_UNAVAILABLE'\n | 'PAYMENT_NOT_STARTED'\n | 'CHECKOUT_NOT_FOUND'\n | 'NOT_AUTHENTICATED';\n\nexport interface AlacartePlaceOrderApiResult {\n ok: boolean,\n payment: AlacartePlaceOrderPayment | null,\n /** Present on some failures (e.g. PRICE_NOT_ACCEPTED, COUPON_UNAVAILABLE). */\n checkout?: AlacarteCheckout | null,\n error?: AlacartePlaceOrderErrorCode | string,\n}\n\n// --- Product selector (derived from query params) --------------------------------------\nexport type ProductSelector =\n | { kind: 'course', courseUid: string, runKey: string }\n | { kind: 'program', programUid: string }\n | { kind: 'invalid' }\n | { kind: 'none' };\n\n// --- Client surface --------------------------------------------------------------------\nexport interface AlacarteCheckoutClient {\n gql: GqlRunner,\n setToken: (token: string) => void,\n getCheckout: () => Promise<AlacarteCheckoutResult>,\n /** viewedPrice: the amount the learner saw on the page before checkout (USD, before any coupon). */\n selectCourse: (courseUid: string, runKey: string, viewedPrice?: number) => Promise<AlacarteCheckoutResult>,\n selectProgram: (programUid: string, viewedPrice?: number) => Promise<AlacarteCheckoutResult>,\n updateCheckout: (args: {\n biId?: string, couponCode?: string, termsAccepted?: boolean, acceptPrice?: boolean,\n }) => Promise<AlacarteCheckoutResult>,\n getOwnedProduct: (productId: string) => Promise<OwnedProductResult>,\n getAllOwnedProducts: () => Promise<AllOwnedProductsResult>,\n getProgramDetail: (programUid: string) => Promise<ProgramDetail | null>,\n getCourseDetail: (courseKey: string) => Promise<CourseDetail | null>,\n getPlacedOrder: (biId: string) => Promise<PlacedOrderResult>,\n getOrderDates: (biId: string) => Promise<OrderDatesResult>,\n /** One-call place order — returns Stripe client secret (section 5a). */\n placeCheckoutOrder: (\n biId: string,\n billing?: AlacarteBillingAddress,\n ) => Promise<AlacartePlaceOrderApiResult>,\n}\n"]}
|
|
@@ -22,9 +22,9 @@ export declare function getOrderDates(gql: GqlRunner, biId: string): Promise<Ord
|
|
|
22
22
|
export declare function placeCheckoutOrder(gql: GqlRunner, biId: string, billing?: AlacarteBillingAddress): Promise<AlacartePlaceOrderApiResult>;
|
|
23
23
|
export declare function getOwnedProduct(gql: GqlRunner, productId: string): Promise<OwnedProductResult>;
|
|
24
24
|
export declare function getAllOwnedProducts(gql: GqlRunner): Promise<AllOwnedProductsResult>;
|
|
25
|
-
/** Fetch a program's catalog/marketing details by
|
|
25
|
+
/** Fetch a program's catalog/marketing details by tProgramUuid. */
|
|
26
26
|
export declare function getProgramDetail(gql: GqlRunner, programUid: string): Promise<ProgramDetail | null>;
|
|
27
27
|
/** Fetch a course's full catalog/marketing details by Viax tCourseKey for the PDP banner. */
|
|
28
28
|
export declare function getBannerCourseDetail(gql: GqlRunner, courseKey: string): Promise<BannerCourseDetail | null>;
|
|
29
|
-
/** Fetch a program's full catalog/marketing details by
|
|
29
|
+
/** Fetch a program's full catalog/marketing details by tProgramUuid for the PDP banner. */
|
|
30
30
|
export declare function getBannerProgramDetail(gql: GqlRunner, programUid: string): Promise<BannerProgramDetail | null>;
|
|
@@ -147,14 +147,14 @@ export function getAllOwnedProducts(gql) {
|
|
|
147
147
|
};
|
|
148
148
|
});
|
|
149
149
|
}
|
|
150
|
-
/** Fetch a program's catalog/marketing details by
|
|
150
|
+
/** Fetch a program's catalog/marketing details by tProgramUuid. */
|
|
151
151
|
export function getProgramDetail(gql, programUid) {
|
|
152
152
|
return __awaiter(this, void 0, void 0, function* () {
|
|
153
153
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
154
|
-
const
|
|
155
|
-
if (!
|
|
154
|
+
const tProgramUuid = programUid.trim();
|
|
155
|
+
if (!tProgramUuid)
|
|
156
156
|
return null;
|
|
157
|
-
const filter = `
|
|
157
|
+
const filter = `tProgramUuid='${tProgramUuid.replace(/'/g, "''")}'`;
|
|
158
158
|
const data = yield gql(GET_PROGRAM_DETAIL, { filter });
|
|
159
159
|
const node = (_c = (_b = (_a = data === null || data === void 0 ? void 0 : data.filterTwouProgram) === null || _a === void 0 ? void 0 : _a.edges) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.node;
|
|
160
160
|
if (!(node === null || node === void 0 ? void 0 : node.uid))
|
|
@@ -244,14 +244,14 @@ export function getBannerCourseDetail(gql, courseKey) {
|
|
|
244
244
|
};
|
|
245
245
|
});
|
|
246
246
|
}
|
|
247
|
-
/** Fetch a program's full catalog/marketing details by
|
|
247
|
+
/** Fetch a program's full catalog/marketing details by tProgramUuid for the PDP banner. */
|
|
248
248
|
export function getBannerProgramDetail(gql, programUid) {
|
|
249
249
|
return __awaiter(this, void 0, void 0, function* () {
|
|
250
250
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
251
|
-
const
|
|
252
|
-
if (!
|
|
251
|
+
const tProgramUuid = programUid.trim();
|
|
252
|
+
if (!tProgramUuid)
|
|
253
253
|
return null;
|
|
254
|
-
const filter = `
|
|
254
|
+
const filter = `tProgramUuid='${tProgramUuid.replace(/'/g, "''")}' AND tAlacartePurchasable=true`;
|
|
255
255
|
const data = yield gql(GET_BANNER_PROGRAM_DETAIL, { filter });
|
|
256
256
|
const node = (_c = (_b = (_a = data === null || data === void 0 ? void 0 : data.filterTwouProgram) === null || _a === void 0 ? void 0 : _a.edges) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.node;
|
|
257
257
|
if (!(node === null || node === void 0 ? void 0 : node.uid))
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alacarteApi.js","sourceRoot":"","sources":["../../src/utils/alacarteApi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * utils/alacarteApi.ts — à la carte checkout + owned-products operations.\n *\n * Convention: every function here returns the `{ ok, ... }` envelope as-is.\n * `ok: false` is a normal domain outcome (see alacarte-*-api.md `error` table)\n * and must be branched on by the caller — it is NOT thrown here. Transport\n * errors (missing token, HTTP 401, GraphQL errors[]) are thrown by gql()\n * itself and propagate unchanged, exactly as with every other function in\n * bootstrapApi.ts/cartApi.ts.\n */\nimport {\n GET_ALACARTE_CHECKOUT, SELECT_ALACARTE_PRODUCT, UPDATE_ALACARTE_CHECKOUT,\n GET_OWNED_PRODUCT, GET_ALL_OWNED_PRODUCTS, GET_PROGRAM_DETAIL, GET_PLACED_ORDER,\n GET_ORDER_DATES, PLACE_ALACARTE_ORDER, GET_BANNER_COURSE_DETAIL, GET_BANNER_PROGRAM_DETAIL,\n} from './alacarteQueries';\nimport type {\n GqlRunner, AlacarteCheckoutResult, OwnedProductResult, AllOwnedProductsResult,\n ProgramDetail, ProgramCourseRef, PlacedOrderResult, OrderDatesResult,\n AlacarteBillingAddress, AlacartePlaceOrderApiResult,\n BannerCourseDetail, BannerCourseInstructor, BannerProgramDetail, BannerProgramCourse,\n} from '../types/alacarte';\n\nfunction normalizeCheckoutResult(payload: any): AlacarteCheckoutResult {\n return {\n ok: payload?.ok === true,\n checkout: payload?.checkout ?? null,\n replaced: payload?.replaced,\n priceChanged: payload?.priceChanged,\n blocked: payload?.blocked,\n couponRecheck: payload?.couponRecheck,\n error: payload?.error,\n };\n}\n\nexport async function getCheckout(gql: GqlRunner): Promise<AlacarteCheckoutResult> {\n const data = await gql(GET_ALACARTE_CHECKOUT);\n return normalizeCheckoutResult(data?.tCheckout);\n}\n\nexport async function selectCourse(\n gql: GqlRunner, courseUid: string, runKey: string, viewedPrice?: number,\n): Promise<AlacarteCheckoutResult> {\n const data = await gql(SELECT_ALACARTE_PRODUCT, { courseUid, runKey, viewedPrice });\n return normalizeCheckoutResult(data?.tCheckoutSelect);\n}\n\nexport async function selectProgram(\n gql: GqlRunner, programUid: string, viewedPrice?: number,\n): Promise<AlacarteCheckoutResult> {\n const data = await gql(SELECT_ALACARTE_PRODUCT, { programUid, viewedPrice });\n return normalizeCheckoutResult(data?.tCheckoutSelect);\n}\n\nexport async function updateCheckout(\n gql: GqlRunner,\n args: { biId?: string, couponCode?: string, termsAccepted?: boolean, acceptPrice?: boolean },\n): Promise<AlacarteCheckoutResult> {\n const data = await gql(UPDATE_ALACARTE_CHECKOUT, args);\n return normalizeCheckoutResult(data?.tCheckoutUpdate);\n}\n\n/** Reads the placed order for the confirmation page — never re-priced. */\nexport async function getPlacedOrder(gql: GqlRunner, biId: string): Promise<PlacedOrderResult> {\n const data = await gql(GET_PLACED_ORDER, { biId });\n const payload = data?.tPlacedOrder;\n return {\n ok: payload?.ok === true,\n order: payload?.order ?? null,\n error: payload?.error,\n };\n}\n\n/** Reads the order's placement/creation timestamps for the confirmation page. */\nexport async function getOrderDates(gql: GqlRunner, biId: string): Promise<OrderDatesResult> {\n const data = await gql(GET_ORDER_DATES, { biId });\n const payload = data?.getTwouOrder;\n return {\n ok: !!payload,\n dates: payload\n ? { tPlacedAt: payload.tPlacedAt ?? null, biCreatedAt: payload.biCreatedAt ?? null }\n : null,\n };\n}\n\n/**\n * One-call place order (section 5a). Runs sanctions + payment start and\n * returns the Stripe client secret.\n *\n * Only `country` (two-letter ISO) is required. Other address fields are\n * optional — the checkout UI also collects country-specific fields (ALC-177).\n */\nexport async function placeCheckoutOrder(\n gql: GqlRunner,\n biId: string,\n billing?: AlacarteBillingAddress,\n): Promise<AlacartePlaceOrderApiResult> {\n const optional = (value?: string): string | undefined => {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n };\n\n const data = await gql(PLACE_ALACARTE_ORDER, {\n biId,\n country: billing?.country?.trim().toUpperCase() || undefined,\n firstName: optional(billing?.firstName),\n lastName: optional(billing?.lastName),\n line1: optional(billing?.line1),\n line2: optional(billing?.line2),\n city: optional(billing?.city),\n state: optional(billing?.state),\n postcode: optional(billing?.postcode),\n });\n const payload = data?.tCheckoutPlaceOrder;\n const payment = payload?.payment;\n return {\n ok: payload?.ok === true,\n payment: payment?.clientSecret\n ? {\n clientSecret: payment.clientSecret,\n paymentIntentId: payment.paymentIntentId ?? '',\n amount: payment.amount ?? 0,\n currency: payment.currency ?? 'usd',\n }\n : null,\n checkout: payload?.checkout ?? null,\n error: payload?.error,\n };\n}\n\nexport async function getOwnedProduct(gql: GqlRunner, productId: string): Promise<OwnedProductResult> {\n const data = await gql(GET_OWNED_PRODUCT, { productId });\n const payload = data?.tOwnedProducts;\n return {\n ok: payload?.ok === true,\n product: payload?.product ?? null,\n error: payload?.error,\n };\n}\n\nexport async function getAllOwnedProducts(gql: GqlRunner): Promise<AllOwnedProductsResult> {\n const data = await gql(GET_ALL_OWNED_PRODUCTS);\n const payload = data?.tOwnedProducts;\n return {\n ok: payload?.ok === true,\n owned: payload?.owned ?? null,\n error: payload?.error,\n };\n}\n\n/** Fetch a program's catalog/marketing details by uid. */\nexport async function getProgramDetail(gql: GqlRunner, programUid: string): Promise<ProgramDetail | null> {\n const uid = programUid.trim();\n if (!uid) return null;\n\n const filter = `uid='${uid.replace(/'/g, \"''\")}'`;\n const data = await gql(GET_PROGRAM_DETAIL, { filter });\n const node = data?.filterTwouProgram?.edges?.[0]?.node;\n if (!node?.uid) return null;\n\n const tCourses: ProgramCourseRef[] = (node.tCourses ?? []).map((c: any) => ({\n id: c.id,\n uid: c.uid ?? null,\n maName: c.maName ?? null,\n maDescription: c.maDescription ?? null,\n tShortDescription: c.tShortDescription ?? null,\n imageUrl: c.imageUrl ?? null,\n tBrandText: c.tBrandText ?? null,\n tOrgLogoUrl: c.tOrgLogoUrl ?? null,\n tUrlCourse: c.tUrlCourse ?? null,\n tMarketingSlug: c.tMarketingSlug ?? null,\n tStatus: c.tStatus?.code ? { code: c.tStatus.code } : null,\n }));\n\n return {\n uid: node.uid,\n tEffortLow: node.tEffortLow ?? null,\n tEnrollmentCount: node.tEnrollmentCount ?? null,\n tMarketingSlug: node.tMarketingSlug ?? null,\n tSkills: node.tSkills ?? null,\n tFaq: node.tFaq ?? null,\n tLevelType: node.tLevelType ?? null,\n tCourses,\n };\n}\n\n// ─── Banner details (PDP banner before checkout) ─────────────────────────────\n\n/** Fetch a course's full catalog/marketing details by Viax tCourseKey for the PDP banner. */\nexport async function getBannerCourseDetail(gql: GqlRunner, courseKey: string): Promise<BannerCourseDetail | null> {\n const key = courseKey.trim();\n if (!key) return null;\n\n const filter = `tCourseKey = '${key.replace(/'/g, \"''\")}' and tAlacartePurchasable='true'`;\n const data = await gql(GET_BANNER_COURSE_DETAIL, { filter });\n const node = data?.filterTwouCourse?.edges?.[0]?.node;\n if (!node?.uid) return null;\n\n const instructors: BannerCourseInstructor[] = (node.tInstructors ?? []).map((i: any) => ({\n tGivenName: i.tGivenName ?? null,\n tFamilyName: i.tFamilyName ?? null,\n tOrganization: i.tOrganization ?? null,\n tInstructorUuid: i.tInstructorUuid ?? null,\n tSortValue: i.tSortValue ?? null,\n }));\n\n return {\n uid: node.uid,\n id: node.id ?? null,\n maName: node.maName ?? '',\n tEnrollmentCount: node.tEnrollmentCount ?? null,\n tAlacartePurchasable: node.tAlacartePurchasable ?? null,\n tCourseKey: node.tCourseKey ?? null,\n tShortDescription: node.tShortDescription ?? null,\n maDescription: node.maDescription ?? null,\n tBrandText: node.tBrandText ?? null,\n tOrgLogoUrl: node.tOrgLogoUrl ?? null,\n imageUrl: node.imageUrl ?? null,\n tUrlCourse: node.tUrlCourse ?? null,\n tMarketingSlug: node.tMarketingSlug ?? null,\n tLevelType: node.tLevelType ?? null,\n tPacingType: node.tPacingType?.code ?? null,\n tDurationLow: node.tDurationLow ?? null,\n tDurationHigh: node.tDurationHigh ?? null,\n tDurationUnit: node.tDurationUnit?.code ?? null,\n tEffortLow: node.tEffortLow ?? null,\n tEffortHigh: node.tEffortHigh ?? null,\n tEffortUnit: node.tEffortUnit?.code ?? null,\n tLanguage: node.tLanguage ?? null,\n tExpectedLearningItems: node.tExpectedLearningItems ?? null,\n tSkills: node.tSkills ?? null,\n tPrerequisites: node.tPrerequisites ?? null,\n tSyllabus: node.tSyllabus ?? null,\n tFaq: node.tFaq ?? null,\n tTestimonials: node.tTestimonials ?? null,\n tTranscriptLanguages: node.tTranscriptLanguages ?? null,\n tInstructors: instructors,\n };\n}\n\n/** Fetch a program's full catalog/marketing details by uid for the PDP banner. */\nexport async function getBannerProgramDetail(gql: GqlRunner, programUid: string): Promise<BannerProgramDetail | null> {\n const uid = programUid.trim();\n if (!uid) return null;\n\n const filter = `uid='${uid.replace(/'/g, \"''\")}' AND tAlacartePurchasable=true`;\n const data = await gql(GET_BANNER_PROGRAM_DETAIL, { filter });\n const node = data?.filterTwouProgram?.edges?.[0]?.node;\n if (!node?.uid) return null;\n\n const tCourses: BannerProgramCourse[] = (node.tCourses ?? []).map((c: any) => ({\n id: c.id,\n uid: c.uid ?? null,\n maName: c.maName ?? null,\n maDescription: c.maDescription ?? null,\n tShortDescription: c.tShortDescription ?? null,\n tBrandText: c.tBrandText ?? null,\n tOrgLogoUrl: c.tOrgLogoUrl ?? null,\n tCourseKey: c.tCourseKey ?? null,\n imageUrl: c.imageUrl ?? null,\n tUrlCourse: c.tUrlCourse ?? null,\n tMarketingSlug: c.tMarketingSlug ?? null,\n tStatus: c.tStatus?.code ? { code: c.tStatus.code } : null,\n }));\n\n return {\n uid: node.uid,\n tEffortLow: node.tEffortLow ?? null,\n tEnrollmentCount: node.tEnrollmentCount ?? null,\n tMarketingSlug: node.tMarketingSlug ?? null,\n tAlacartePurchasable: node.tAlacartePurchasable ?? null,\n tSkills: node.tSkills ?? null,\n tFaq: node.tFaq ?? null,\n tLevelType: node.tLevelType ?? null,\n imageUrl: node.imageUrl ?? null,\n tBannerImageUrl: node.tBannerImageUrl ?? null,\n tCourses,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"alacarteApi.js","sourceRoot":"","sources":["../../src/utils/alacarteApi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * utils/alacarteApi.ts — à la carte checkout + owned-products operations.\n *\n * Convention: every function here returns the `{ ok, ... }` envelope as-is.\n * `ok: false` is a normal domain outcome (see alacarte-*-api.md `error` table)\n * and must be branched on by the caller — it is NOT thrown here. Transport\n * errors (missing token, HTTP 401, GraphQL errors[]) are thrown by gql()\n * itself and propagate unchanged, exactly as with every other function in\n * bootstrapApi.ts/cartApi.ts.\n */\nimport {\n GET_ALACARTE_CHECKOUT, SELECT_ALACARTE_PRODUCT, UPDATE_ALACARTE_CHECKOUT,\n GET_OWNED_PRODUCT, GET_ALL_OWNED_PRODUCTS, GET_PROGRAM_DETAIL, GET_PLACED_ORDER,\n GET_ORDER_DATES, PLACE_ALACARTE_ORDER, GET_BANNER_COURSE_DETAIL, GET_BANNER_PROGRAM_DETAIL,\n} from './alacarteQueries';\nimport type {\n GqlRunner, AlacarteCheckoutResult, OwnedProductResult, AllOwnedProductsResult,\n ProgramDetail, ProgramCourseRef, PlacedOrderResult, OrderDatesResult,\n AlacarteBillingAddress, AlacartePlaceOrderApiResult,\n BannerCourseDetail, BannerCourseInstructor, BannerProgramDetail, BannerProgramCourse,\n} from '../types/alacarte';\n\nfunction normalizeCheckoutResult(payload: any): AlacarteCheckoutResult {\n return {\n ok: payload?.ok === true,\n checkout: payload?.checkout ?? null,\n replaced: payload?.replaced,\n priceChanged: payload?.priceChanged,\n blocked: payload?.blocked,\n couponRecheck: payload?.couponRecheck,\n error: payload?.error,\n };\n}\n\nexport async function getCheckout(gql: GqlRunner): Promise<AlacarteCheckoutResult> {\n const data = await gql(GET_ALACARTE_CHECKOUT);\n return normalizeCheckoutResult(data?.tCheckout);\n}\n\nexport async function selectCourse(\n gql: GqlRunner, courseUid: string, runKey: string, viewedPrice?: number,\n): Promise<AlacarteCheckoutResult> {\n const data = await gql(SELECT_ALACARTE_PRODUCT, { courseUid, runKey, viewedPrice });\n return normalizeCheckoutResult(data?.tCheckoutSelect);\n}\n\nexport async function selectProgram(\n gql: GqlRunner, programUid: string, viewedPrice?: number,\n): Promise<AlacarteCheckoutResult> {\n const data = await gql(SELECT_ALACARTE_PRODUCT, { programUid, viewedPrice });\n return normalizeCheckoutResult(data?.tCheckoutSelect);\n}\n\nexport async function updateCheckout(\n gql: GqlRunner,\n args: { biId?: string, couponCode?: string, termsAccepted?: boolean, acceptPrice?: boolean },\n): Promise<AlacarteCheckoutResult> {\n const data = await gql(UPDATE_ALACARTE_CHECKOUT, args);\n return normalizeCheckoutResult(data?.tCheckoutUpdate);\n}\n\n/** Reads the placed order for the confirmation page — never re-priced. */\nexport async function getPlacedOrder(gql: GqlRunner, biId: string): Promise<PlacedOrderResult> {\n const data = await gql(GET_PLACED_ORDER, { biId });\n const payload = data?.tPlacedOrder;\n return {\n ok: payload?.ok === true,\n order: payload?.order ?? null,\n error: payload?.error,\n };\n}\n\n/** Reads the order's placement/creation timestamps for the confirmation page. */\nexport async function getOrderDates(gql: GqlRunner, biId: string): Promise<OrderDatesResult> {\n const data = await gql(GET_ORDER_DATES, { biId });\n const payload = data?.getTwouOrder;\n return {\n ok: !!payload,\n dates: payload\n ? { tPlacedAt: payload.tPlacedAt ?? null, biCreatedAt: payload.biCreatedAt ?? null }\n : null,\n };\n}\n\n/**\n * One-call place order (section 5a). Runs sanctions + payment start and\n * returns the Stripe client secret.\n *\n * Only `country` (two-letter ISO) is required. Other address fields are\n * optional — the checkout UI also collects country-specific fields (ALC-177).\n */\nexport async function placeCheckoutOrder(\n gql: GqlRunner,\n biId: string,\n billing?: AlacarteBillingAddress,\n): Promise<AlacartePlaceOrderApiResult> {\n const optional = (value?: string): string | undefined => {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n };\n\n const data = await gql(PLACE_ALACARTE_ORDER, {\n biId,\n country: billing?.country?.trim().toUpperCase() || undefined,\n firstName: optional(billing?.firstName),\n lastName: optional(billing?.lastName),\n line1: optional(billing?.line1),\n line2: optional(billing?.line2),\n city: optional(billing?.city),\n state: optional(billing?.state),\n postcode: optional(billing?.postcode),\n });\n const payload = data?.tCheckoutPlaceOrder;\n const payment = payload?.payment;\n return {\n ok: payload?.ok === true,\n payment: payment?.clientSecret\n ? {\n clientSecret: payment.clientSecret,\n paymentIntentId: payment.paymentIntentId ?? '',\n amount: payment.amount ?? 0,\n currency: payment.currency ?? 'usd',\n }\n : null,\n checkout: payload?.checkout ?? null,\n error: payload?.error,\n };\n}\n\nexport async function getOwnedProduct(gql: GqlRunner, productId: string): Promise<OwnedProductResult> {\n const data = await gql(GET_OWNED_PRODUCT, { productId });\n const payload = data?.tOwnedProducts;\n return {\n ok: payload?.ok === true,\n product: payload?.product ?? null,\n error: payload?.error,\n };\n}\n\nexport async function getAllOwnedProducts(gql: GqlRunner): Promise<AllOwnedProductsResult> {\n const data = await gql(GET_ALL_OWNED_PRODUCTS);\n const payload = data?.tOwnedProducts;\n return {\n ok: payload?.ok === true,\n owned: payload?.owned ?? null,\n error: payload?.error,\n };\n}\n\n/** Fetch a program's catalog/marketing details by tProgramUuid. */\nexport async function getProgramDetail(gql: GqlRunner, programUid: string): Promise<ProgramDetail | null> {\n const tProgramUuid = programUid.trim();\n if (!tProgramUuid) return null;\n\n const filter = `tProgramUuid='${tProgramUuid.replace(/'/g, \"''\")}'`;\n const data = await gql(GET_PROGRAM_DETAIL, { filter });\n const node = data?.filterTwouProgram?.edges?.[0]?.node;\n if (!node?.uid) return null;\n\n const tCourses: ProgramCourseRef[] = (node.tCourses ?? []).map((c: any) => ({\n id: c.id,\n uid: c.uid ?? null,\n maName: c.maName ?? null,\n maDescription: c.maDescription ?? null,\n tShortDescription: c.tShortDescription ?? null,\n imageUrl: c.imageUrl ?? null,\n tBrandText: c.tBrandText ?? null,\n tOrgLogoUrl: c.tOrgLogoUrl ?? null,\n tUrlCourse: c.tUrlCourse ?? null,\n tMarketingSlug: c.tMarketingSlug ?? null,\n tStatus: c.tStatus?.code ? { code: c.tStatus.code } : null,\n }));\n\n return {\n uid: node.uid,\n tEffortLow: node.tEffortLow ?? null,\n tEnrollmentCount: node.tEnrollmentCount ?? null,\n tMarketingSlug: node.tMarketingSlug ?? null,\n tSkills: node.tSkills ?? null,\n tFaq: node.tFaq ?? null,\n tLevelType: node.tLevelType ?? null,\n tCourses,\n };\n}\n\n// ─── Banner details (PDP banner before checkout) ─────────────────────────────\n\n/** Fetch a course's full catalog/marketing details by Viax tCourseKey for the PDP banner. */\nexport async function getBannerCourseDetail(gql: GqlRunner, courseKey: string): Promise<BannerCourseDetail | null> {\n const key = courseKey.trim();\n if (!key) return null;\n\n const filter = `tCourseKey = '${key.replace(/'/g, \"''\")}' and tAlacartePurchasable='true'`;\n const data = await gql(GET_BANNER_COURSE_DETAIL, { filter });\n const node = data?.filterTwouCourse?.edges?.[0]?.node;\n if (!node?.uid) return null;\n\n const instructors: BannerCourseInstructor[] = (node.tInstructors ?? []).map((i: any) => ({\n tGivenName: i.tGivenName ?? null,\n tFamilyName: i.tFamilyName ?? null,\n tOrganization: i.tOrganization ?? null,\n tInstructorUuid: i.tInstructorUuid ?? null,\n tSortValue: i.tSortValue ?? null,\n }));\n\n return {\n uid: node.uid,\n id: node.id ?? null,\n maName: node.maName ?? '',\n tEnrollmentCount: node.tEnrollmentCount ?? null,\n tAlacartePurchasable: node.tAlacartePurchasable ?? null,\n tCourseKey: node.tCourseKey ?? null,\n tShortDescription: node.tShortDescription ?? null,\n maDescription: node.maDescription ?? null,\n tBrandText: node.tBrandText ?? null,\n tOrgLogoUrl: node.tOrgLogoUrl ?? null,\n imageUrl: node.imageUrl ?? null,\n tUrlCourse: node.tUrlCourse ?? null,\n tMarketingSlug: node.tMarketingSlug ?? null,\n tLevelType: node.tLevelType ?? null,\n tPacingType: node.tPacingType?.code ?? null,\n tDurationLow: node.tDurationLow ?? null,\n tDurationHigh: node.tDurationHigh ?? null,\n tDurationUnit: node.tDurationUnit?.code ?? null,\n tEffortLow: node.tEffortLow ?? null,\n tEffortHigh: node.tEffortHigh ?? null,\n tEffortUnit: node.tEffortUnit?.code ?? null,\n tLanguage: node.tLanguage ?? null,\n tExpectedLearningItems: node.tExpectedLearningItems ?? null,\n tSkills: node.tSkills ?? null,\n tPrerequisites: node.tPrerequisites ?? null,\n tSyllabus: node.tSyllabus ?? null,\n tFaq: node.tFaq ?? null,\n tTestimonials: node.tTestimonials ?? null,\n tTranscriptLanguages: node.tTranscriptLanguages ?? null,\n tInstructors: instructors,\n };\n}\n\n/** Fetch a program's full catalog/marketing details by tProgramUuid for the PDP banner. */\nexport async function getBannerProgramDetail(gql: GqlRunner, programUid: string): Promise<BannerProgramDetail | null> {\n const tProgramUuid = programUid.trim();\n if (!tProgramUuid) return null;\n\n const filter = `tProgramUuid='${tProgramUuid.replace(/'/g, \"''\")}' AND tAlacartePurchasable=true`;\n const data = await gql(GET_BANNER_PROGRAM_DETAIL, { filter });\n const node = data?.filterTwouProgram?.edges?.[0]?.node;\n if (!node?.uid) return null;\n\n const tCourses: BannerProgramCourse[] = (node.tCourses ?? []).map((c: any) => ({\n id: c.id,\n uid: c.uid ?? null,\n maName: c.maName ?? null,\n maDescription: c.maDescription ?? null,\n tShortDescription: c.tShortDescription ?? null,\n tBrandText: c.tBrandText ?? null,\n tOrgLogoUrl: c.tOrgLogoUrl ?? null,\n tCourseKey: c.tCourseKey ?? null,\n imageUrl: c.imageUrl ?? null,\n tUrlCourse: c.tUrlCourse ?? null,\n tMarketingSlug: c.tMarketingSlug ?? null,\n tStatus: c.tStatus?.code ? { code: c.tStatus.code } : null,\n }));\n\n return {\n uid: node.uid,\n tEffortLow: node.tEffortLow ?? null,\n tEnrollmentCount: node.tEnrollmentCount ?? null,\n tMarketingSlug: node.tMarketingSlug ?? null,\n tAlacartePurchasable: node.tAlacartePurchasable ?? null,\n tSkills: node.tSkills ?? null,\n tFaq: node.tFaq ?? null,\n tLevelType: node.tLevelType ?? null,\n imageUrl: node.imageUrl ?? null,\n tBannerImageUrl: node.tBannerImageUrl ?? null,\n tCourses,\n };\n}\n"]}
|
|
@@ -33,10 +33,10 @@ export declare const GET_ORDER_DATES = "query GetSingleOrder($biId: String!) {\n
|
|
|
33
33
|
* See alacarte-checkout-api.md section 5a.
|
|
34
34
|
*/
|
|
35
35
|
export declare const PLACE_ALACARTE_ORDER = "mutation PlaceAlacarteOrder(\n $biId: String,\n $firstName: String,\n $lastName: String,\n $line1: String,\n $line2: String,\n $city: String,\n $state: String,\n $postcode: String,\n $country: String\n) {\n tCheckoutPlaceOrder(\n biId: $biId,\n firstName: $firstName,\n lastName: $lastName,\n line1: $line1,\n line2: $line2,\n city: $city,\n state: $state,\n postcode: $postcode,\n country: $country\n )\n}";
|
|
36
|
-
/** Fetch a program's catalog/marketing details by
|
|
37
|
-
* GET_COURSE_DETAIL, which filters courses by tCourseKey
|
|
38
|
-
export declare const GET_PROGRAM_DETAIL = "query GetProgramDetail($filter: String!) {\n filterTwouProgram(_first: 1, _filter: $filter) {\n edges {\n node {\n uid\n tEffortLow\n tEnrollmentCount\n tMarketingSlug\n tSkills\n tFaq\n tLevelType\n tCourses {\n id\n maName\n uid\n tCourseKey\n tShortDescription\n maDescription\n tBrandText\n tOrgLogoUrl\n imageUrl\n tUrlCourse\n tMarketingSlug\n tStatus { code }\n }\n }\n }\n }\n}";
|
|
36
|
+
/** Fetch a program's catalog/marketing details by tProgramUuid (program analog of
|
|
37
|
+
* GET_COURSE_DETAIL, which filters courses by tCourseKey). */
|
|
38
|
+
export declare const GET_PROGRAM_DETAIL = "query GetProgramDetail($filter: String!) {\n filterTwouProgram(_first: 1, _filter: $filter) {\n edges {\n node {\n uid\n tProgramUuid\n tEffortLow\n tEnrollmentCount\n tMarketingSlug\n tSkills\n tFaq\n tLevelType\n tCourses {\n id\n maName\n uid\n tCourseKey\n tShortDescription\n maDescription\n tBrandText\n tOrgLogoUrl\n imageUrl\n tUrlCourse\n tMarketingSlug\n tStatus { code }\n }\n }\n }\n }\n}";
|
|
39
39
|
/** Fetch a course's full catalog/marketing details by tCourseKey for the PDP banner. */
|
|
40
40
|
export declare const GET_BANNER_COURSE_DETAIL = "query GetBannerCourseDetail($filter: String!) {\n filterTwouCourse(_first: 1, _filter: $filter) {\n edges {\n node {\n maName\n tEnrollmentCount\n tAlacartePurchasable\n uid\n id\n tCourseKey\n tShortDescription\n maDescription\n tBrandText\n tOrgLogoUrl\n imageUrl\n tUrlCourse\n tMarketingSlug\n tLevelType\n tPacingType { code }\n tDurationLow\n tDurationHigh\n tDurationUnit { code }\n tEffortLow\n tEffortHigh\n tEffortUnit { code }\n tLanguage\n tExpectedLearningItems\n tSkills\n tPrerequisites\n tSyllabus\n tFaq\n tTestimonials\n tTranscriptLanguages\n tInstructors {\n tGivenName\n tFamilyName\n tOrganization\n tInstructorUuid\n tSortValue\n }\n }\n }\n }\n}";
|
|
41
|
-
/** Fetch a program's full catalog/marketing details by
|
|
42
|
-
export declare const GET_BANNER_PROGRAM_DETAIL = "query GetBannerProgramDetail($filter: String!) {\n filterTwouProgram(_first: 1, _filter: $filter) {\n edges {\n node {\n uid\n tEffortLow\n tEnrollmentCount\n tMarketingSlug\n tAlacartePurchasable\n tSkills\n tFaq\n tLevelType\n imageUrl\n tBannerImageUrl\n tCourses {\n id\n maName\n uid\n tCourseKey\n tShortDescription\n maDescription\n tBrandText\n tOrgLogoUrl\n tCourseKey\n imageUrl\n tUrlCourse\n tMarketingSlug\n tStatus { code }\n }\n }\n }\n }\n}";
|
|
41
|
+
/** Fetch a program's full catalog/marketing details by tProgramUuid for the PDP banner. */
|
|
42
|
+
export declare const GET_BANNER_PROGRAM_DETAIL = "query GetBannerProgramDetail($filter: String!) {\n filterTwouProgram(_first: 1, _filter: $filter) {\n edges {\n node {\n uid\n tProgramUuid\n tEffortLow\n tEnrollmentCount\n tMarketingSlug\n tAlacartePurchasable\n tSkills\n tFaq\n tLevelType\n imageUrl\n tBannerImageUrl\n tCourses {\n id\n maName\n uid\n tCourseKey\n tShortDescription\n maDescription\n tBrandText\n tOrgLogoUrl\n tCourseKey\n imageUrl\n tUrlCourse\n tMarketingSlug\n tStatus { code }\n }\n }\n }\n }\n}";
|
|
@@ -76,13 +76,14 @@ export const PLACE_ALACARTE_ORDER = `mutation PlaceAlacarteOrder(
|
|
|
76
76
|
)
|
|
77
77
|
}`;
|
|
78
78
|
// ─── Program details ──────────────────────────────────────────────────────────
|
|
79
|
-
/** Fetch a program's catalog/marketing details by
|
|
80
|
-
* GET_COURSE_DETAIL, which filters courses by tCourseKey
|
|
79
|
+
/** Fetch a program's catalog/marketing details by tProgramUuid (program analog of
|
|
80
|
+
* GET_COURSE_DETAIL, which filters courses by tCourseKey). */
|
|
81
81
|
export const GET_PROGRAM_DETAIL = `query GetProgramDetail($filter: String!) {
|
|
82
82
|
filterTwouProgram(_first: 1, _filter: $filter) {
|
|
83
83
|
edges {
|
|
84
84
|
node {
|
|
85
85
|
uid
|
|
86
|
+
tProgramUuid
|
|
86
87
|
tEffortLow
|
|
87
88
|
tEnrollmentCount
|
|
88
89
|
tMarketingSlug
|
|
@@ -153,12 +154,13 @@ export const GET_BANNER_COURSE_DETAIL = `query GetBannerCourseDetail($filter: St
|
|
|
153
154
|
}
|
|
154
155
|
}
|
|
155
156
|
}`;
|
|
156
|
-
/** Fetch a program's full catalog/marketing details by
|
|
157
|
+
/** Fetch a program's full catalog/marketing details by tProgramUuid for the PDP banner. */
|
|
157
158
|
export const GET_BANNER_PROGRAM_DETAIL = `query GetBannerProgramDetail($filter: String!) {
|
|
158
159
|
filterTwouProgram(_first: 1, _filter: $filter) {
|
|
159
160
|
edges {
|
|
160
161
|
node {
|
|
161
162
|
uid
|
|
163
|
+
tProgramUuid
|
|
162
164
|
tEffortLow
|
|
163
165
|
tEnrollmentCount
|
|
164
166
|
tMarketingSlug
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alacarteQueries.js","sourceRoot":"","sources":["../../src/utils/alacarteQueries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,iFAAiF;AAEjF,MAAM,CAAC,MAAM,qBAAqB,GAAG;;EAEnC,CAAC;AAEH;;;;;+DAK+D;AAC/D,MAAM,CAAC,MAAM,uBAAuB,GAAG;;EAErC,CAAC;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG;;EAEtC,CAAC;AAEH,iFAAiF;AAEjF,MAAM,CAAC,MAAM,iBAAiB,GAAG;;EAE/B,CAAC;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG;;EAEpC,CAAC;AAEH,gFAAgF;AAEhF;gFACgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAG;;EAE9B,CAAC;AAEH;;6CAE6C;AAC7C,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;EAK7B,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;EAsBlC,CAAC;AAEH,iFAAiF;AAEjF;
|
|
1
|
+
{"version":3,"file":"alacarteQueries.js","sourceRoot":"","sources":["../../src/utils/alacarteQueries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,iFAAiF;AAEjF,MAAM,CAAC,MAAM,qBAAqB,GAAG;;EAEnC,CAAC;AAEH;;;;;+DAK+D;AAC/D,MAAM,CAAC,MAAM,uBAAuB,GAAG;;EAErC,CAAC;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG;;EAEtC,CAAC;AAEH,iFAAiF;AAEjF,MAAM,CAAC,MAAM,iBAAiB,GAAG;;EAE/B,CAAC;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG;;EAEpC,CAAC;AAEH,gFAAgF;AAEhF;gFACgF;AAChF,MAAM,CAAC,MAAM,gBAAgB,GAAG;;EAE9B,CAAC;AAEH;;6CAE6C;AAC7C,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;EAK7B,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;EAsBlC,CAAC;AAEH,iFAAiF;AAEjF;+DAC+D;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BhC,CAAC;AAEH,gFAAgF;AAEhF,wFAAwF;AACxF,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2CtC,CAAC;AAEH,2FAA2F;AAC3F,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCvC,CAAC","sourcesContent":["/**\n * utils/alacarteQueries.ts — GraphQL documents for the à la carte checkout flow.\n * tCheckout / tCheckoutSelect / tCheckoutUpdate / tOwnedProducts / tPlacedOrder\n * are JSON-scalar fields — no field selection sets allowed; the whole response\n * object comes back as-is (same style as GET_IS_CURRENT_USER_SUBSCRIBER in\n * queries.ts). GET_PROGRAM_DETAIL is the one exception — filterTwouProgram is\n * a normal typed field with an explicit selection set, mirroring GET_COURSE_DETAIL.\n * See alacarte-checkout-api.md and alacarte-owned-products-api.md at the repo\n * root for the full contract.\n */\n\n// ─── Checkout ─────────────────────────────────────────────────────────────────\n\nexport const GET_ALACARTE_CHECKOUT = `query GetAlacarteCheckout {\n tCheckout\n}`;\n\n/** Selects a course or a program into the learner's open checkout. Pass only\n * the relevant variables — courseUid+runKey for a course, programUid alone\n * for a program; GraphQL omits unset optional variables cleanly.\n * viewedPrice is the amount (USD, before any coupon) the learner saw before\n * checkout — for a program, the price after already-owned courses are taken\n * off. Omit it to accept whatever price checkout resolves. */\nexport const SELECT_ALACARTE_PRODUCT = `mutation SelectAlacarteProduct($courseUid: String, $runKey: String, $programUid: String, $viewedPrice: Float) {\n tCheckoutSelect(courseUid: $courseUid, runKey: $runKey, programUid: $programUid, viewedPrice: $viewedPrice)\n}`;\n\nexport const UPDATE_ALACARTE_CHECKOUT = `mutation UpdateAlacarteCheckout($biId: String, $couponCode: String, $termsAccepted: Boolean, $acceptPrice: Boolean) {\n tCheckoutUpdate(biId: $biId, couponCode: $couponCode, termsAccepted: $termsAccepted, acceptPrice: $acceptPrice)\n}`;\n\n// ─── Owned products ───────────────────────────────────────────────────────────\n\nexport const GET_OWNED_PRODUCT = `query GetOwnedProduct($productId: String!) {\n tOwnedProducts(productId: $productId)\n}`;\n\nexport const GET_ALL_OWNED_PRODUCTS = `query GetAllOwnedProducts {\n tOwnedProducts\n}`;\n\n// ─── Placed order (confirmation page) ────────────────────────────────────────\n\n/** Reads the order as recorded at placement — the same biId the checkout had.\n * Never re-priced, so a later catalog change doesn't alter what this shows. */\nexport const GET_PLACED_ORDER = `query GetPlacedOrder($biId: String!) {\n tPlacedOrder(biId: $biId)\n}`;\n\n/** Order placement/creation timestamps — used for purchase date and the\n * refund-window deadline on the confirmation page. Use tPlacedAt; fall back\n * to biCreatedAt when tPlacedAt is null. */\nexport const GET_ORDER_DATES = `query GetSingleOrder($biId: String!) {\n getTwouOrder(biId: $biId) {\n tPlacedAt\n biCreatedAt\n }\n}`;\n\n/**\n * One-call \"Place your order\" — sanctions, payment start, and Stripe client\n * secret. Only `country` (two-letter ISO) is required; other address fields\n * are optional when the UI collects country alone.\n * See alacarte-checkout-api.md section 5a.\n */\nexport const PLACE_ALACARTE_ORDER = `mutation PlaceAlacarteOrder(\n $biId: String,\n $firstName: String,\n $lastName: String,\n $line1: String,\n $line2: String,\n $city: String,\n $state: String,\n $postcode: String,\n $country: String\n) {\n tCheckoutPlaceOrder(\n biId: $biId,\n firstName: $firstName,\n lastName: $lastName,\n line1: $line1,\n line2: $line2,\n city: $city,\n state: $state,\n postcode: $postcode,\n country: $country\n )\n}`;\n\n// ─── Program details ──────────────────────────────────────────────────────────\n\n/** Fetch a program's catalog/marketing details by tProgramUuid (program analog of\n * GET_COURSE_DETAIL, which filters courses by tCourseKey). */\nexport const GET_PROGRAM_DETAIL = `query GetProgramDetail($filter: String!) {\n filterTwouProgram(_first: 1, _filter: $filter) {\n edges {\n node {\n uid\n tProgramUuid\n tEffortLow\n tEnrollmentCount\n tMarketingSlug\n tSkills\n tFaq\n tLevelType\n tCourses {\n id\n maName\n uid\n tCourseKey\n tShortDescription\n maDescription\n tBrandText\n tOrgLogoUrl\n imageUrl\n tUrlCourse\n tMarketingSlug\n tStatus { code }\n }\n }\n }\n }\n}`;\n\n// ─── Banner details (PDP banner before checkout) ─────────────────────────────\n\n/** Fetch a course's full catalog/marketing details by tCourseKey for the PDP banner. */\nexport const GET_BANNER_COURSE_DETAIL = `query GetBannerCourseDetail($filter: String!) {\n filterTwouCourse(_first: 1, _filter: $filter) {\n edges {\n node {\n maName\n tEnrollmentCount\n tAlacartePurchasable\n uid\n id\n tCourseKey\n tShortDescription\n maDescription\n tBrandText\n tOrgLogoUrl\n imageUrl\n tUrlCourse\n tMarketingSlug\n tLevelType\n tPacingType { code }\n tDurationLow\n tDurationHigh\n tDurationUnit { code }\n tEffortLow\n tEffortHigh\n tEffortUnit { code }\n tLanguage\n tExpectedLearningItems\n tSkills\n tPrerequisites\n tSyllabus\n tFaq\n tTestimonials\n tTranscriptLanguages\n tInstructors {\n tGivenName\n tFamilyName\n tOrganization\n tInstructorUuid\n tSortValue\n }\n }\n }\n }\n}`;\n\n/** Fetch a program's full catalog/marketing details by tProgramUuid for the PDP banner. */\nexport const GET_BANNER_PROGRAM_DETAIL = `query GetBannerProgramDetail($filter: String!) {\n filterTwouProgram(_first: 1, _filter: $filter) {\n edges {\n node {\n uid\n tProgramUuid\n tEffortLow\n tEnrollmentCount\n tMarketingSlug\n tAlacartePurchasable\n tSkills\n tFaq\n tLevelType\n imageUrl\n tBannerImageUrl\n tCourses {\n id\n maName\n uid\n tCourseKey\n tShortDescription\n maDescription\n tBrandText\n tOrgLogoUrl\n tCourseKey\n imageUrl\n tUrlCourse\n tMarketingSlug\n tStatus { code }\n }\n }\n }\n }\n}`;\n"]}
|
|
@@ -9,12 +9,26 @@ export interface BillingCountryFields {
|
|
|
9
9
|
/** Subdivision options for the state/province select, if any. */
|
|
10
10
|
subdivisions: BillingSubdivisionOptions | null;
|
|
11
11
|
}
|
|
12
|
+
/** Max input length for the country postal/ZIP field, or null if not collected. */
|
|
13
|
+
export declare function getBillingPostalMaxLength(country: string): number | null;
|
|
14
|
+
/**
|
|
15
|
+
* Clamp / filter postal input as the user types:
|
|
16
|
+
* US → digits only, max 5; Canada → letters/digits/space/hyphen, max 7.
|
|
17
|
+
*/
|
|
18
|
+
export declare function sanitizeBillingPostalInput(country: string, value: string): string;
|
|
12
19
|
/**
|
|
13
20
|
* Country-specific billing fields collected on checkout (ALC-177):
|
|
14
21
|
* US → ZIP; India → state; Canada → province + postal code.
|
|
15
22
|
*/
|
|
16
23
|
export declare function getBillingCountryFields(country: string): BillingCountryFields;
|
|
17
|
-
/** Whether
|
|
24
|
+
/** Whether a postal/ZIP value matches the format expected for the country. */
|
|
25
|
+
export declare function isValidBillingPostal(country: string, zip: string): boolean;
|
|
26
|
+
/** Whether state/province is a known subdivision for the country (when required). */
|
|
27
|
+
export declare function isValidBillingSubdivision(country: string, state: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Whether country-specific required billing fields are filled and valid
|
|
30
|
+
* (US ZIP / Canada postal format, India state / Canada province from the list).
|
|
31
|
+
*/
|
|
18
32
|
export declare function hasRequiredBillingFields(billing: {
|
|
19
33
|
country: string;
|
|
20
34
|
state: string;
|
|
@@ -1,4 +1,36 @@
|
|
|
1
1
|
import { CA_PROVINCES, IN_STATES } from '../constants';
|
|
2
|
+
/** US ZIP: exactly 5 digits. */
|
|
3
|
+
const US_ZIP_PATTERN = /^\d{5}$/;
|
|
4
|
+
const US_ZIP_MAX_LENGTH = 5;
|
|
5
|
+
/** Canadian postal: A1A 1A1 (optional space or hyphen) — max 7 with separator. */
|
|
6
|
+
const CA_POSTAL_PATTERN = /^[ABCEGHJ-NPRSTVXY]\d[ABCEGHJ-NPRSTV-Z][ -]?\d[ABCEGHJ-NPRSTV-Z]\d$/i;
|
|
7
|
+
const CA_POSTAL_MAX_LENGTH = 7;
|
|
8
|
+
/** Max input length for the country postal/ZIP field, or null if not collected. */
|
|
9
|
+
export function getBillingPostalMaxLength(country) {
|
|
10
|
+
switch (country.trim().toUpperCase()) {
|
|
11
|
+
case 'US':
|
|
12
|
+
return US_ZIP_MAX_LENGTH;
|
|
13
|
+
case 'CA':
|
|
14
|
+
return CA_POSTAL_MAX_LENGTH;
|
|
15
|
+
default:
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Clamp / filter postal input as the user types:
|
|
21
|
+
* US → digits only, max 5; Canada → letters/digits/space/hyphen, max 7.
|
|
22
|
+
*/
|
|
23
|
+
export function sanitizeBillingPostalInput(country, value) {
|
|
24
|
+
const code = country.trim().toUpperCase();
|
|
25
|
+
switch (code) {
|
|
26
|
+
case 'US':
|
|
27
|
+
return value.replace(/\D/g, '').slice(0, US_ZIP_MAX_LENGTH);
|
|
28
|
+
case 'CA':
|
|
29
|
+
return value.replace(/[^a-zA-Z0-9 -]/g, '').slice(0, CA_POSTAL_MAX_LENGTH);
|
|
30
|
+
default:
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
2
34
|
/**
|
|
3
35
|
* Country-specific billing fields collected on checkout (ALC-177):
|
|
4
36
|
* US → ZIP; India → state; Canada → province + postal code.
|
|
@@ -35,15 +67,44 @@ export function getBillingCountryFields(country) {
|
|
|
35
67
|
};
|
|
36
68
|
}
|
|
37
69
|
}
|
|
38
|
-
/** Whether
|
|
70
|
+
/** Whether a postal/ZIP value matches the format expected for the country. */
|
|
71
|
+
export function isValidBillingPostal(country, zip) {
|
|
72
|
+
const code = country.trim().toUpperCase();
|
|
73
|
+
const value = zip.trim();
|
|
74
|
+
if (!value)
|
|
75
|
+
return false;
|
|
76
|
+
switch (code) {
|
|
77
|
+
case 'US':
|
|
78
|
+
return US_ZIP_PATTERN.test(value);
|
|
79
|
+
case 'CA':
|
|
80
|
+
return CA_POSTAL_PATTERN.test(value);
|
|
81
|
+
default:
|
|
82
|
+
// Other countries do not collect postal on this form.
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Whether state/province is a known subdivision for the country (when required). */
|
|
87
|
+
export function isValidBillingSubdivision(country, state) {
|
|
88
|
+
const fields = getBillingCountryFields(country);
|
|
89
|
+
if (!fields.needsState)
|
|
90
|
+
return true;
|
|
91
|
+
const value = state.trim().toUpperCase();
|
|
92
|
+
if (!value || !fields.subdivisions)
|
|
93
|
+
return false;
|
|
94
|
+
return fields.subdivisions.some(([code]) => code === value);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Whether country-specific required billing fields are filled and valid
|
|
98
|
+
* (US ZIP / Canada postal format, India state / Canada province from the list).
|
|
99
|
+
*/
|
|
39
100
|
export function hasRequiredBillingFields(billing) {
|
|
40
101
|
const country = billing.country.trim();
|
|
41
102
|
if (!country)
|
|
42
103
|
return false;
|
|
43
104
|
const fields = getBillingCountryFields(country);
|
|
44
|
-
if (fields.needsState && !billing.state
|
|
105
|
+
if (fields.needsState && !isValidBillingSubdivision(country, billing.state))
|
|
45
106
|
return false;
|
|
46
|
-
if (fields.needsPostal && !billing.zip
|
|
107
|
+
if (fields.needsPostal && !isValidBillingPostal(country, billing.zip))
|
|
47
108
|
return false;
|
|
48
109
|
return true;
|
|
49
110
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"billingAddress.js","sourceRoot":"","sources":["../../src/utils/billingAddress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAevD;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;QACrC,KAAK,IAAI;YACP,OAAO;gBACL,UAAU,EAAE,KAAK;gBACjB,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,KAAK;gBACvB,YAAY,EAAE,IAAI;aACnB,CAAC;QACJ,KAAK,IAAI;YACP,OAAO;gBACL,UAAU,EAAE,IAAI;gBAChB,WAAW,EAAE,KAAK;gBAClB,gBAAgB,EAAE,KAAK;gBACvB,YAAY,EAAE,SAAS;aACxB,CAAC;QACJ,KAAK,IAAI;YACP,OAAO;gBACL,UAAU,EAAE,IAAI;gBAChB,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,IAAI;gBACtB,YAAY,EAAE,YAAY;aAC3B,CAAC;QACJ;YACE,OAAO;gBACL,UAAU,EAAE,KAAK;gBACjB,WAAW,EAAE,KAAK;gBAClB,gBAAgB,EAAE,KAAK;gBACvB,YAAY,EAAE,IAAI;aACnB,CAAC;IACN,CAAC;AACH,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"billingAddress.js","sourceRoot":"","sources":["../../src/utils/billingAddress.ts"],"names":[],"mappings":"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","sourcesContent":["import { CA_PROVINCES, IN_STATES } from '../constants';\n\nexport type BillingSubdivisionOptions = readonly (readonly [string, string])[];\n\nexport interface BillingCountryFields {\n /** Collect state/province for this country. */\n needsState: boolean,\n /** Collect postal/ZIP code for this country. */\n needsPostal: boolean,\n /** When true, country sits alone on the first row (Canada). */\n countryFullWidth: boolean,\n /** Subdivision options for the state/province select, if any. */\n subdivisions: BillingSubdivisionOptions | null,\n}\n\n/** US ZIP: exactly 5 digits. */\nconst US_ZIP_PATTERN = /^\\d{5}$/;\nconst US_ZIP_MAX_LENGTH = 5;\n\n/** Canadian postal: A1A 1A1 (optional space or hyphen) — max 7 with separator. */\nconst CA_POSTAL_PATTERN = /^[ABCEGHJ-NPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z][ -]?\\d[ABCEGHJ-NPRSTV-Z]\\d$/i;\nconst CA_POSTAL_MAX_LENGTH = 7;\n\n/** Max input length for the country postal/ZIP field, or null if not collected. */\nexport function getBillingPostalMaxLength(country: string): number | null {\n switch (country.trim().toUpperCase()) {\n case 'US':\n return US_ZIP_MAX_LENGTH;\n case 'CA':\n return CA_POSTAL_MAX_LENGTH;\n default:\n return null;\n }\n}\n\n/**\n * Clamp / filter postal input as the user types:\n * US → digits only, max 5; Canada → letters/digits/space/hyphen, max 7.\n */\nexport function sanitizeBillingPostalInput(country: string, value: string): string {\n const code = country.trim().toUpperCase();\n switch (code) {\n case 'US':\n return value.replace(/\\D/g, '').slice(0, US_ZIP_MAX_LENGTH);\n case 'CA':\n return value.replace(/[^a-zA-Z0-9 -]/g, '').slice(0, CA_POSTAL_MAX_LENGTH);\n default:\n return value;\n }\n}\n\n/**\n * Country-specific billing fields collected on checkout (ALC-177):\n * US → ZIP; India → state; Canada → province + postal code.\n */\nexport function getBillingCountryFields(country: string): BillingCountryFields {\n switch (country.trim().toUpperCase()) {\n case 'US':\n return {\n needsState: false,\n needsPostal: true,\n countryFullWidth: false,\n subdivisions: null,\n };\n case 'IN':\n return {\n needsState: true,\n needsPostal: false,\n countryFullWidth: false,\n subdivisions: IN_STATES,\n };\n case 'CA':\n return {\n needsState: true,\n needsPostal: true,\n countryFullWidth: true,\n subdivisions: CA_PROVINCES,\n };\n default:\n return {\n needsState: false,\n needsPostal: false,\n countryFullWidth: false,\n subdivisions: null,\n };\n }\n}\n\n/** Whether a postal/ZIP value matches the format expected for the country. */\nexport function isValidBillingPostal(country: string, zip: string): boolean {\n const code = country.trim().toUpperCase();\n const value = zip.trim();\n if (!value) return false;\n\n switch (code) {\n case 'US':\n return US_ZIP_PATTERN.test(value);\n case 'CA':\n return CA_POSTAL_PATTERN.test(value);\n default:\n // Other countries do not collect postal on this form.\n return true;\n }\n}\n\n/** Whether state/province is a known subdivision for the country (when required). */\nexport function isValidBillingSubdivision(country: string, state: string): boolean {\n const fields = getBillingCountryFields(country);\n if (!fields.needsState) return true;\n\n const value = state.trim().toUpperCase();\n if (!value || !fields.subdivisions) return false;\n return fields.subdivisions.some(([code]) => code === value);\n}\n\n/**\n * Whether country-specific required billing fields are filled and valid\n * (US ZIP / Canada postal format, India state / Canada province from the list).\n */\nexport function hasRequiredBillingFields(billing: {\n country: string,\n state: string,\n zip: string,\n}): boolean {\n const country = billing.country.trim();\n if (!country) return false;\n\n const fields = getBillingCountryFields(country);\n if (fields.needsState && !isValidBillingSubdivision(country, billing.state)) return false;\n if (fields.needsPostal && !isValidBillingPostal(country, billing.zip)) return false;\n return true;\n}\n"]}
|