@edx/frontend-app-alacarte-checkout 1.22.0 → 1.24.0

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