@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.
@@ -104,5 +104,15 @@ declare const messages: {
104
104
  defaultMessage: string;
105
105
  description: string;
106
106
  };
107
+ zipCodeInvalid: {
108
+ id: string;
109
+ defaultMessage: string;
110
+ description: string;
111
+ };
112
+ postalCodeInvalid: {
113
+ id: string;
114
+ defaultMessage: string;
115
+ description: string;
116
+ };
107
117
  };
108
118
  export default messages;
@@ -108,6 +108,16 @@ const messages = defineMessages({
108
108
  defaultMessage: 'This field is required',
109
109
  description: 'Error text shown below a required field that is empty',
110
110
  },
111
+ zipCodeInvalid: {
112
+ id: 'checkout.paymentForm.zipCodeInvalid',
113
+ defaultMessage: 'Enter a valid ZIP code',
114
+ description: 'Error text shown when a US ZIP code has an invalid format',
115
+ },
116
+ postalCodeInvalid: {
117
+ id: 'checkout.paymentForm.postalCodeInvalid',
118
+ defaultMessage: 'Enter a valid postal code',
119
+ description: 'Error text shown when a Canadian postal code has an invalid format',
120
+ },
111
121
  });
112
122
  export default messages;
113
123
  //# sourceMappingURL=Messages.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Messages.js","sourceRoot":"","sources":["../../../src/components/PaymentForm/Messages.ts"],"names":[],"mappings":"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","sourcesContent":["import { defineMessages } from '@openedx/frontend-base';\n\nconst messages = defineMessages({\n // Section headings\n selectPaymentMethod: {\n id: 'checkout.paymentForm.selectPaymentMethod',\n defaultMessage: 'Select Payment Method',\n description: 'Heading for the payment method selection section',\n },\n needAssistance: {\n id: 'checkout.paymentForm.needAssistance',\n defaultMessage: 'Need assistance?',\n description: 'Help text heading',\n },\n needAssistanceBody: {\n id: 'checkout.paymentForm.needAssistanceBody',\n defaultMessage: 'Visit the {link} to review purchase and refund policies.',\n description: 'Help text with learner help center link',\n },\n learnerHelpCenter: {\n id: 'checkout.paymentForm.learnerHelpCenter',\n defaultMessage: 'Learner Help Center',\n description: 'Learner help center link text',\n },\n\n payWithCard: {\n id: 'checkout.paymentForm.payWithCard',\n defaultMessage: 'Pay with card',\n description: 'Heading for the card payment fields',\n },\n\n // Order total\n orderDisclaimer: {\n id: 'checkout.paymentForm.orderDisclaimer',\n defaultMessage: 'After you complete your order you will be able to select course dates from your dashboard.',\n description: 'Disclaimer shown above the order total explaining taxes and enrollment',\n },\n total: {\n id: 'checkout.paymentForm.total',\n defaultMessage: 'Total:',\n description: 'Label for the order total amount',\n },\n totalAmount: {\n id: 'checkout.paymentForm.totalAmount',\n defaultMessage: '${amount} {currency}',\n description: 'Formatted order total with currency code e.g. $219.00 USD',\n },\n\n // Submit button\n payNow: {\n id: 'checkout.paymentForm.payNow',\n defaultMessage: 'Pay now',\n description: 'Submit button label',\n },\n processing: {\n id: 'checkout.paymentForm.processing',\n defaultMessage: 'Processing...',\n description: 'Submit button label while the order is being placed',\n },\n country: {\n id: 'checkout.paymentForm.country',\n defaultMessage: 'Country',\n description: 'Country selector label',\n },\n countryPlaceholder: {\n id: 'checkout.paymentForm.countryPlaceholder',\n defaultMessage: 'Select your country',\n description: 'Placeholder option shown before a country is selected',\n },\n state: {\n id: 'checkout.paymentForm.state',\n defaultMessage: 'State',\n description: 'State selector label (India)',\n },\n statePlaceholder: {\n id: 'checkout.paymentForm.statePlaceholder',\n defaultMessage: 'Select your state',\n description: 'Placeholder option shown before a state is selected',\n },\n province: {\n id: 'checkout.paymentForm.province',\n defaultMessage: 'Province',\n description: 'Province selector label (Canada)',\n },\n provincePlaceholder: {\n id: 'checkout.paymentForm.provincePlaceholder',\n defaultMessage: 'Select your province',\n description: 'Placeholder option shown before a province is selected',\n },\n zipCode: {\n id: 'checkout.paymentForm.zipCode',\n defaultMessage: 'ZIP Code',\n description: 'ZIP code label (United States)',\n },\n zipCodePlaceholder: {\n id: 'checkout.paymentForm.zipCodePlaceholder',\n defaultMessage: '12345',\n description: 'ZIP code input placeholder (United States)',\n },\n postalCode: {\n id: 'checkout.paymentForm.postalCode',\n defaultMessage: 'Postal code',\n description: 'Postal code label (Canada)',\n },\n postalCodePlaceholder: {\n id: 'checkout.paymentForm.postalCodePlaceholder',\n defaultMessage: 'A1A 1A1',\n description: 'Postal code input placeholder (Canada)',\n },\n fieldRequired: {\n id: 'checkout.paymentForm.fieldRequired',\n defaultMessage: 'This field is required',\n description: 'Error text shown below a required field that is empty',\n },\n});\n\nexport default messages;\n"]}
1
+ {"version":3,"file":"Messages.js","sourceRoot":"","sources":["../../../src/components/PaymentForm/Messages.ts"],"names":[],"mappings":"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","sourcesContent":["import { defineMessages } from '@openedx/frontend-base';\n\nconst messages = defineMessages({\n // Section headings\n selectPaymentMethod: {\n id: 'checkout.paymentForm.selectPaymentMethod',\n defaultMessage: 'Select Payment Method',\n description: 'Heading for the payment method selection section',\n },\n needAssistance: {\n id: 'checkout.paymentForm.needAssistance',\n defaultMessage: 'Need assistance?',\n description: 'Help text heading',\n },\n needAssistanceBody: {\n id: 'checkout.paymentForm.needAssistanceBody',\n defaultMessage: 'Visit the {link} to review purchase and refund policies.',\n description: 'Help text with learner help center link',\n },\n learnerHelpCenter: {\n id: 'checkout.paymentForm.learnerHelpCenter',\n defaultMessage: 'Learner Help Center',\n description: 'Learner help center link text',\n },\n\n payWithCard: {\n id: 'checkout.paymentForm.payWithCard',\n defaultMessage: 'Pay with card',\n description: 'Heading for the card payment fields',\n },\n\n // Order total\n orderDisclaimer: {\n id: 'checkout.paymentForm.orderDisclaimer',\n defaultMessage: 'After you complete your order you will be able to select course dates from your dashboard.',\n description: 'Disclaimer shown above the order total explaining taxes and enrollment',\n },\n total: {\n id: 'checkout.paymentForm.total',\n defaultMessage: 'Total:',\n description: 'Label for the order total amount',\n },\n totalAmount: {\n id: 'checkout.paymentForm.totalAmount',\n defaultMessage: '${amount} {currency}',\n description: 'Formatted order total with currency code e.g. $219.00 USD',\n },\n\n // Submit button\n payNow: {\n id: 'checkout.paymentForm.payNow',\n defaultMessage: 'Pay now',\n description: 'Submit button label',\n },\n processing: {\n id: 'checkout.paymentForm.processing',\n defaultMessage: 'Processing...',\n description: 'Submit button label while the order is being placed',\n },\n country: {\n id: 'checkout.paymentForm.country',\n defaultMessage: 'Country',\n description: 'Country selector label',\n },\n countryPlaceholder: {\n id: 'checkout.paymentForm.countryPlaceholder',\n defaultMessage: 'Select your country',\n description: 'Placeholder option shown before a country is selected',\n },\n state: {\n id: 'checkout.paymentForm.state',\n defaultMessage: 'State',\n description: 'State selector label (India)',\n },\n statePlaceholder: {\n id: 'checkout.paymentForm.statePlaceholder',\n defaultMessage: 'Select your state',\n description: 'Placeholder option shown before a state is selected',\n },\n province: {\n id: 'checkout.paymentForm.province',\n defaultMessage: 'Province',\n description: 'Province selector label (Canada)',\n },\n provincePlaceholder: {\n id: 'checkout.paymentForm.provincePlaceholder',\n defaultMessage: 'Select your province',\n description: 'Placeholder option shown before a province is selected',\n },\n zipCode: {\n id: 'checkout.paymentForm.zipCode',\n defaultMessage: 'ZIP Code',\n description: 'ZIP code label (United States)',\n },\n zipCodePlaceholder: {\n id: 'checkout.paymentForm.zipCodePlaceholder',\n defaultMessage: '12345',\n description: 'ZIP code input placeholder (United States)',\n },\n postalCode: {\n id: 'checkout.paymentForm.postalCode',\n defaultMessage: 'Postal code',\n description: 'Postal code label (Canada)',\n },\n postalCodePlaceholder: {\n id: 'checkout.paymentForm.postalCodePlaceholder',\n defaultMessage: 'A1A 1A1',\n description: 'Postal code input placeholder (Canada)',\n },\n fieldRequired: {\n id: 'checkout.paymentForm.fieldRequired',\n defaultMessage: 'This field is required',\n description: 'Error text shown below a required field that is empty',\n },\n zipCodeInvalid: {\n id: 'checkout.paymentForm.zipCodeInvalid',\n defaultMessage: 'Enter a valid ZIP code',\n description: 'Error text shown when a US ZIP code has an invalid format',\n },\n postalCodeInvalid: {\n id: 'checkout.paymentForm.postalCodeInvalid',\n defaultMessage: 'Enter a valid postal code',\n description: 'Error text shown when a Canadian postal code has an invalid format',\n },\n});\n\nexport default messages;\n"]}
@@ -14,12 +14,14 @@ import { useIntl } from '@openedx/frontend-base';
14
14
  import PaymentFields from '../PaymentFields/PaymentFields';
15
15
  import ExpressCheckout from '../ExpressCheckout/ExpressCheckout';
16
16
  import { BILLING_COUNTRIES, LEARNER_HELP_CENTER_URL } from '../../constants';
17
- import { getBillingCountryFields, hasRequiredBillingFields } from '../../utils/billingAddress';
17
+ import { getBillingCountryFields, getBillingPostalMaxLength, hasRequiredBillingFields, isValidBillingPostal, isValidBillingSubdivision, sanitizeBillingPostalInput, } from '../../utils/billingAddress';
18
18
  import messages from './Messages';
19
19
  const PaymentForm = ({ billing, setField, numberMountRef, expiryMountRef, cvcMountRef, stripeReady, sessionReady, submitting, onPlaceOrder, alacarteLine = null, stripe = null, expressCountry = 'US', onExpressPayment, cardFieldStates, onValidationError, }) => {
20
- var _a, _b;
20
+ var _a, _b, _c;
21
21
  const { formatMessage } = useIntl();
22
22
  const [showValidationErrors, setShowValidationErrors] = useState(false);
23
+ /** Postal format errors only after blur — cleared again while the user types. */
24
+ const [postalBlurred, setPostalBlurred] = useState(false);
23
25
  // No fallback amount — an unresolved à la carte line means we don't know
24
26
  // the price yet, so this is left blank rather than showing a made-up number.
25
27
  const totalText = (alacarteLine === null || alacarteLine === void 0 ? void 0 : alacarteLine.amountDue)
@@ -67,17 +69,39 @@ const PaymentForm = ({ billing, setField, numberMountRef, expiryMountRef, cvcMou
67
69
  }
68
70
  : undefined;
69
71
  const countryError = showValidationErrors && !billing.country ? requiredMsg : undefined;
70
- const stateError = 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"]}
@@ -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 uid. */
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
- staleTime: 0,
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 uid. */
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":"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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 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 staleTime: 0,\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 uid. */\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 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 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 retry: 1,\n });\n}\n"]}
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 uid. */
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 uid for the PDP banner. */
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 uid. */
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 uid = programUid.trim();
155
- if (!uid)
154
+ const tProgramUuid = programUid.trim();
155
+ if (!tProgramUuid)
156
156
  return null;
157
- const filter = `uid='${uid.replace(/'/g, "''")}'`;
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 uid for the PDP banner. */
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 uid = programUid.trim();
252
- if (!uid)
251
+ const tProgramUuid = programUid.trim();
252
+ if (!tProgramUuid)
253
253
  return null;
254
- const filter = `uid='${uid.replace(/'/g, "''")}' AND tAlacartePurchasable=true`;
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 uid (program analog of
37
- * GET_COURSE_DETAIL, which filters courses by tCourseKey instead of uid). */
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 uid 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 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 uid (program analog of
80
- * GET_COURSE_DETAIL, which filters courses by tCourseKey instead of uid). */
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 uid for the PDP banner. */
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;8EAC8E;AAC9E,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4BhC,CAAC;AAEH,gFAAgF;AAEhF,wFAAwF;AACxF,MAAM,CAAC,MAAM,wBAAwB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2CtC,CAAC;AAEH,kFAAkF;AAClF,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgCvC,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 uid (program analog of\n * GET_COURSE_DETAIL, which filters courses by tCourseKey instead of uid). */\nexport 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}`;\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 uid 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 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"]}
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 country-specific required billing fields are filled. */
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 country-specific required billing fields are filled. */
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.trim())
105
+ if (fields.needsState && !isValidBillingSubdivision(country, billing.state))
45
106
  return false;
46
- if (fields.needsPostal && !billing.zip.trim())
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,mEAAmE;AACnE,MAAM,UAAU,wBAAwB,CAAC,OAIxC;IACC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAE3B,MAAM,MAAM,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAChD,IAAI,MAAM,CAAC,UAAU,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,KAAK,CAAC;IAC7D,IAAI,MAAM,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,OAAO,KAAK,CAAC;IAC5D,OAAO,IAAI,CAAC;AACd,CAAC","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/**\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 country-specific required billing fields are filled. */\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 && !billing.state.trim()) return false;\n if (fields.needsPostal && !billing.zip.trim()) return false;\n return true;\n}\n"]}
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edx/frontend-app-alacarte-checkout",
3
- "version": "1.22.0",
3
+ "version": "1.23.0",
4
4
  "description": "alacarte Checkout Frontend application",
5
5
  "repository": {
6
6
  "type": "git",