@edx/frontend-app-subscription-checkout 1.12.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/components/PaymentForm/Messages.d.ts +5 -0
  2. package/dist/components/PaymentForm/Messages.js +5 -0
  3. package/dist/components/PaymentForm/Messages.js.map +1 -1
  4. package/dist/components/PaymentForm/PaymentForm.d.ts +2 -2
  5. package/dist/components/PaymentForm/PaymentForm.js +7 -7
  6. package/dist/components/PaymentForm/PaymentForm.js.map +1 -1
  7. package/dist/data/hooks/useCheckout.d.ts +2 -2
  8. package/dist/data/hooks/useCheckout.js +19 -13
  9. package/dist/data/hooks/useCheckout.js.map +1 -1
  10. package/dist/data/hooks/useCheckoutMutations.d.ts +6 -2
  11. package/dist/data/hooks/useCheckoutMutations.js +57 -30
  12. package/dist/data/hooks/useCheckoutMutations.js.map +1 -1
  13. package/dist/routes.js +2 -2
  14. package/dist/routes.js.map +1 -1
  15. package/dist/slots.js +1 -5
  16. package/dist/slots.js.map +1 -1
  17. package/dist/types.d.ts +8 -3
  18. package/dist/types.js.map +1 -1
  19. package/dist/utils/cartApi.d.ts +3 -3
  20. package/dist/utils/cartApi.js +13 -6
  21. package/dist/utils/cartApi.js.map +1 -1
  22. package/dist/utils/provision.js +2 -2
  23. package/dist/utils/provision.js.map +1 -1
  24. package/dist/utils/queries.d.ts +1 -1
  25. package/dist/utils/queries.js +1 -0
  26. package/dist/utils/queries.js.map +1 -1
  27. package/package.json +1 -1
  28. package/dist/widgets/ExampleHeader/app.d.ts +0 -3
  29. package/dist/widgets/ExampleHeader/app.js +0 -19
  30. package/dist/widgets/ExampleHeader/app.js.map +0 -1
  31. package/dist/widgets/ExampleHeader/index.d.ts +0 -1
  32. package/dist/widgets/ExampleHeader/index.js +0 -2
  33. package/dist/widgets/ExampleHeader/index.js.map +0 -1
@@ -84,6 +84,11 @@ declare const messages: {
84
84
  defaultMessage: string;
85
85
  description: string;
86
86
  };
87
+ continueWithPaypal: {
88
+ id: string;
89
+ defaultMessage: string;
90
+ description: string;
91
+ };
87
92
  processing: {
88
93
  id: string;
89
94
  defaultMessage: string;
@@ -90,6 +90,11 @@ const messages = defineMessages({
90
90
  defaultMessage: 'Confirm subscription',
91
91
  description: 'Submit button label',
92
92
  },
93
+ continueWithPaypal: {
94
+ id: 'checkout.paymentForm.continueWithPaypal',
95
+ defaultMessage: 'Continue with PayPal',
96
+ description: 'Submit button label when PayPal is the selected payment method',
97
+ },
93
98
  processing: {
94
99
  id: 'checkout.paymentForm.processing',
95
100
  defaultMessage: 'Confirming Subscription',
@@ -1 +1 @@
1
- {"version":3,"file":"Messages.js","sourceRoot":"","sources":["../../../src/components/PaymentForm/Messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,QAAQ,GAAG,cAAc,CAAC;IAC9B,mBAAmB;IACnB,mBAAmB,EAAE;QACnB,EAAE,EAAE,0CAA0C;QAC9C,cAAc,EAAE,uBAAuB;QACvC,WAAW,EAAE,kDAAkD;KAChE;IACD,cAAc,EAAE;QACd,EAAE,EAAE,qCAAqC;QACzC,cAAc,EAAE,kBAAkB;QAClC,WAAW,EAAE,mBAAmB;KACjC;IACD,kBAAkB,EAAE;QAClB,EAAE,EAAE,yCAAyC;QAC7C,cAAc,EAAE,0DAA0D;QAC1E,WAAW,EAAE,yCAAyC;KACvD;IACD,iBAAiB,EAAE;QACjB,EAAE,EAAE,wCAAwC;QAC5C,cAAc,EAAE,qBAAqB;QACrC,WAAW,EAAE,+BAA+B;KAC7C;IAED,yBAAyB;IACzB,IAAI,EAAE;QACJ,EAAE,EAAE,2BAA2B;QAC/B,cAAc,EAAE,MAAM;QACtB,WAAW,EAAE,wCAAwC;KACtD;IACD,SAAS,EAAE;QACT,EAAE,EAAE,gCAAgC;QACpC,cAAc,EAAE,YAAY;QAC5B,WAAW,EAAE,iCAAiC;KAC/C;IACD,MAAM,EAAE;QACN,EAAE,EAAE,6BAA6B;QACjC,cAAc,EAAE,QAAQ;QACxB,WAAW,EAAE,6BAA6B;KAC3C;IAED,QAAQ;IACR,WAAW,EAAE;QACX,EAAE,EAAE,kCAAkC;QACtC,cAAc,EAAE,yTAAyT;QACzU,WAAW,EAAE,oFAAoF;KAClG;IACD,iBAAiB,EAAE;QACjB,EAAE,EAAE,wCAAwC;QAC5C,cAAc,EAAE,6SAA6S;QAC7T,WAAW,EAAE,sFAAsF;KACpG;IACD,mBAAmB,EAAE;QACnB,EAAE,EAAE,0CAA0C;QAC9C,cAAc,EAAE,qEAAqE;QACrF,WAAW,EAAE,sFAAsF;KACpG;IACD,kBAAkB,EAAE;QAClB,EAAE,EAAE,yCAAyC;QAC7C,cAAc,EAAE,uBAAuB;QACvC,WAAW,EAAE,iFAAiF;KAC/F;IACD,UAAU,EAAE;QACV,EAAE,EAAE,iCAAiC;QACrC,cAAc,EAAE,cAAc;QAC9B,WAAW,EAAE,wBAAwB;KACtC;IACD,YAAY,EAAE;QACZ,EAAE,EAAE,mCAAmC;QACvC,cAAc,EAAE,eAAe;QAC/B,WAAW,EAAE,yBAAyB;KACvC;IAED,aAAa;IACb,uBAAuB,EAAE;QACvB,EAAE,EAAE,8CAA8C;QAClD,cAAc,EAAE,oIAAoI;QACpJ,WAAW,EAAE,4EAA4E;KAC1F;IACD,sBAAsB,EAAE;QACtB,EAAE,EAAE,6CAA6C;QACjD,cAAc,EAAE,6EAA6E;QAC7F,WAAW,EAAE,6CAA6C;KAC3D;IACD,mBAAmB,EAAE;QACnB,EAAE,EAAE,0CAA0C;QAC9C,cAAc,EAAE,8CAA8C;QAC9D,WAAW,EAAE,yCAAyC;KACvD;IAED,gBAAgB;IAChB,mBAAmB,EAAE;QACnB,EAAE,EAAE,0CAA0C;QAC9C,cAAc,EAAE,sBAAsB;QACtC,WAAW,EAAE,qBAAqB;KACnC;IACD,UAAU,EAAE;QACV,EAAE,EAAE,iCAAiC;QACrC,cAAc,EAAE,yBAAyB;QACzC,WAAW,EAAE,+DAA+D;KAC7E;IACD,cAAc,EAAE;QACd,EAAE,EAAE,qCAAqC;QACzC,cAAc,EAAE,kBAAkB;QAClC,WAAW,EAAE,wCAAwC;KACtD;IACD,OAAO,EAAE;QACP,EAAE,EAAE,8BAA8B;QAClC,cAAc,EAAE,SAAS;QACzB,WAAW,EAAE,wBAAwB;KACtC;IACD,uBAAuB,EAAE;QACvB,EAAE,EAAE,8CAA8C;QAClD,cAAc,EAAE,SAAS;QACzB,WAAW,EAAE,4CAA4C;KAC1D;CACF,CAAC,CAAC;AAEH,eAAe,QAAQ,CAAC","sourcesContent":["import { defineMessages } from '@openedx/frontend-base';\n\nconst messages = defineMessages({\n // Section headings\n selectPaymentMethod: {\n id: 'checkout.paymentForm.selectPaymentMethod',\n defaultMessage: 'Select Payment Method',\n description: 'Heading for the payment method selection section',\n },\n needAssistance: {\n id: 'checkout.paymentForm.needAssistance',\n defaultMessage: 'Need assistance?',\n description: 'Help text heading',\n },\n needAssistanceBody: {\n id: 'checkout.paymentForm.needAssistanceBody',\n defaultMessage: 'Visit the {link} to review purchase and refund policies.',\n description: 'Help text with learner help center link',\n },\n learnerHelpCenter: {\n id: 'checkout.paymentForm.learnerHelpCenter',\n defaultMessage: 'Learner Help Center',\n description: 'Learner help center link text',\n },\n\n // Payment method options\n card: {\n id: 'checkout.paymentForm.card',\n defaultMessage: 'Card',\n description: 'Credit/debit card payment method label',\n },\n googlePay: {\n id: 'checkout.paymentForm.googlePay',\n defaultMessage: 'Google Pay',\n description: 'Google Pay payment method label',\n },\n paypal: {\n id: 'checkout.paymentForm.paypal',\n defaultMessage: 'PayPal',\n description: 'PayPal payment method label',\n },\n\n // Terms\n termsNotice: {\n id: 'checkout.paymentForm.termsNotice',\n defaultMessage: 'Your edX Plus subscription will start today with a {trialDays}-day free trial. Your subscription automatically renews on {date} for {pricing} (inclusive of applicable taxes). Cancel your subscription anytime from your dashboard. By clicking “Confirm subscription” you agree to our {termsLink}, and {refundLink}.',\n description: 'Monthly subscription terms notice with free trial, shown before the confirm button',\n },\n termsNoticeAnnual: {\n id: 'checkout.paymentForm.termsNoticeAnnual',\n defaultMessage: 'Your edX Plus subscription will start today. You will be charged {pricing} today (inclusive of applicable taxes). Your subscription automatically renews annually. Cancel your subscription anytime from your dashboard. By clicking “Confirm subscription” you agree to our {termsLink}, and {refundLink}.',\n description: 'Annual subscription terms notice without free trial, shown before the confirm button',\n },\n termsPricingMonthly: {\n id: 'checkout.paymentForm.termsPricingMonthly',\n defaultMessage: '${promoAmount}/mo for {promoMonths} months then ${regularAmount}/mo',\n description: 'Monthly promotional pricing in the terms notice e.g. $39/mo for 3 months then $59/mo',\n },\n termsPricingAnnual: {\n id: 'checkout.paymentForm.termsPricingAnnual',\n defaultMessage: '${amount}/{frequency}',\n description: 'Annual plan pricing in the terms notice e.g. $299/year — no promotional pricing',\n },\n termsOfUse: {\n id: 'checkout.paymentForm.termsOfUse',\n defaultMessage: 'Terms of Use',\n description: 'Terms of Use link text',\n },\n refundPolicy: {\n id: 'checkout.paymentForm.refundPolicy',\n defaultMessage: 'Refund Policy',\n description: 'Refund Policy link text',\n },\n\n // Checkboxes\n recurringConfirmMonthly: {\n id: 'checkout.paymentForm.recurringConfirmMonthly',\n defaultMessage: 'I agree to enroll in a recurring monthly subscription for ${promoAmount}/mo for {promoMonths} months then ${regularAmount}/mo USD.',\n description: 'Monthly checkbox confirming recurring charge with promo then regular price',\n },\n recurringConfirmAnnual: {\n id: 'checkout.paymentForm.recurringConfirmAnnual',\n defaultMessage: 'I agree to enroll in a recurring monthly subscription for ${amount}/yr USD.',\n description: 'Annual checkbox confirming recurring charge',\n },\n ukCaResidentConfirm: {\n id: 'checkout.paymentForm.ukCaResidentConfirm',\n defaultMessage: 'Second checkbox only for UK and CA residents',\n description: 'Second checkbox for UK and CA residents',\n },\n\n // Submit button\n confirmSubscription: {\n id: 'checkout.paymentForm.confirmSubscription',\n defaultMessage: 'Confirm subscription',\n description: 'Submit button label',\n },\n processing: {\n id: 'checkout.paymentForm.processing',\n defaultMessage: 'Confirming Subscription',\n description: 'Submit button label while the subscription is being confirmed',\n },\n loadingPayment: {\n id: 'checkout.paymentForm.loadingPayment',\n defaultMessage: 'Loading payment…',\n description: 'Submit button label while Stripe loads',\n },\n country: {\n id: 'checkout.paymentForm.country',\n defaultMessage: 'Country',\n description: 'Country selector label',\n },\n spinnerScreenReaderText: {\n id: 'checkout.paymentForm.spinnerScreenReaderText',\n defaultMessage: 'Working',\n description: 'Screen reader text for the loading spinner',\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 // Payment method options\n card: {\n id: 'checkout.paymentForm.card',\n defaultMessage: 'Card',\n description: 'Credit/debit card payment method label',\n },\n googlePay: {\n id: 'checkout.paymentForm.googlePay',\n defaultMessage: 'Google Pay',\n description: 'Google Pay payment method label',\n },\n paypal: {\n id: 'checkout.paymentForm.paypal',\n defaultMessage: 'PayPal',\n description: 'PayPal payment method label',\n },\n\n // Terms\n termsNotice: {\n id: 'checkout.paymentForm.termsNotice',\n defaultMessage: 'Your edX Plus subscription will start today with a {trialDays}-day free trial. Your subscription automatically renews on {date} for {pricing} (inclusive of applicable taxes). Cancel your subscription anytime from your dashboard. By clicking “Confirm subscription” you agree to our {termsLink}, and {refundLink}.',\n description: 'Monthly subscription terms notice with free trial, shown before the confirm button',\n },\n termsNoticeAnnual: {\n id: 'checkout.paymentForm.termsNoticeAnnual',\n defaultMessage: 'Your edX Plus subscription will start today. You will be charged {pricing} today (inclusive of applicable taxes). Your subscription automatically renews annually. Cancel your subscription anytime from your dashboard. By clicking “Confirm subscription” you agree to our {termsLink}, and {refundLink}.',\n description: 'Annual subscription terms notice without free trial, shown before the confirm button',\n },\n termsPricingMonthly: {\n id: 'checkout.paymentForm.termsPricingMonthly',\n defaultMessage: '${promoAmount}/mo for {promoMonths} months then ${regularAmount}/mo',\n description: 'Monthly promotional pricing in the terms notice e.g. $39/mo for 3 months then $59/mo',\n },\n termsPricingAnnual: {\n id: 'checkout.paymentForm.termsPricingAnnual',\n defaultMessage: '${amount}/{frequency}',\n description: 'Annual plan pricing in the terms notice e.g. $299/year — no promotional pricing',\n },\n termsOfUse: {\n id: 'checkout.paymentForm.termsOfUse',\n defaultMessage: 'Terms of Use',\n description: 'Terms of Use link text',\n },\n refundPolicy: {\n id: 'checkout.paymentForm.refundPolicy',\n defaultMessage: 'Refund Policy',\n description: 'Refund Policy link text',\n },\n\n // Checkboxes\n recurringConfirmMonthly: {\n id: 'checkout.paymentForm.recurringConfirmMonthly',\n defaultMessage: 'I agree to enroll in a recurring monthly subscription for ${promoAmount}/mo for {promoMonths} months then ${regularAmount}/mo USD.',\n description: 'Monthly checkbox confirming recurring charge with promo then regular price',\n },\n recurringConfirmAnnual: {\n id: 'checkout.paymentForm.recurringConfirmAnnual',\n defaultMessage: 'I agree to enroll in a recurring monthly subscription for ${amount}/yr USD.',\n description: 'Annual checkbox confirming recurring charge',\n },\n ukCaResidentConfirm: {\n id: 'checkout.paymentForm.ukCaResidentConfirm',\n defaultMessage: 'Second checkbox only for UK and CA residents',\n description: 'Second checkbox for UK and CA residents',\n },\n\n // Submit button\n confirmSubscription: {\n id: 'checkout.paymentForm.confirmSubscription',\n defaultMessage: 'Confirm subscription',\n description: 'Submit button label',\n },\n continueWithPaypal: {\n id: 'checkout.paymentForm.continueWithPaypal',\n defaultMessage: 'Continue with PayPal',\n description: 'Submit button label when PayPal is the selected payment method',\n },\n processing: {\n id: 'checkout.paymentForm.processing',\n defaultMessage: 'Confirming Subscription',\n description: 'Submit button label while the subscription is being confirmed',\n },\n loadingPayment: {\n id: 'checkout.paymentForm.loadingPayment',\n defaultMessage: 'Loading payment…',\n description: 'Submit button label while Stripe loads',\n },\n country: {\n id: 'checkout.paymentForm.country',\n defaultMessage: 'Country',\n description: 'Country selector label',\n },\n spinnerScreenReaderText: {\n id: 'checkout.paymentForm.spinnerScreenReaderText',\n defaultMessage: 'Working',\n description: 'Screen reader text for the loading spinner',\n },\n});\n\nexport default messages;\n"]}
@@ -1,5 +1,5 @@
1
1
  import type { Ref } from 'react';
2
- import type { BillingInfo, CheckoutConsent } from '../../types';
2
+ import type { BillingInfo, CheckoutConsent, PaymentMethod } from '../../types';
3
3
  import type { PlanPrice } from '../../utils/billingDetails';
4
4
  export interface PaymentFormProps {
5
5
  billing: BillingInfo;
@@ -11,7 +11,7 @@ export interface PaymentFormProps {
11
11
  stripeReady: boolean;
12
12
  sessionReady: boolean;
13
13
  submitting: boolean;
14
- onPlaceOrder: (consent: CheckoutConsent) => void;
14
+ onPlaceOrder: (consent: CheckoutConsent, paymentMethod: PaymentMethod) => void;
15
15
  priceToShow: PlanPrice | null;
16
16
  cycleToShow: string;
17
17
  trialEnd?: string;
@@ -17,13 +17,14 @@ const PaymentForm = ({ billing, setField, numberMountRef, expiryMountRef, cvcMou
17
17
  console.log('userCountry', userCountry);
18
18
  console.log('COUNTRIES_REQUIRING_EXTRA_CONFIRM', COUNTRIES_REQUIRING_EXTRA_CONFIRM);
19
19
  const showExtraConfirm = !!userCountry && COUNTRIES_REQUIRING_EXTRA_CONFIRM.includes(userCountry);
20
- const busy = submitting || !stripeReady || !sessionReady;
21
- let buttonLabel = formatMessage(messages.confirmSubscription);
20
+ const needsStripe = paymentMethod !== 'paypal';
21
+ const busy = submitting || (needsStripe && !stripeReady) || !sessionReady;
22
+ let buttonLabel = paymentMethod === 'paypal'
23
+ ? formatMessage(messages.continueWithPaypal)
24
+ : formatMessage(messages.confirmSubscription);
22
25
  if (submitting)
23
26
  buttonLabel = formatMessage(messages.processing);
24
- else if (!sessionReady)
25
- buttonLabel = formatMessage(messages.loadingPayment);
26
- else if (!stripeReady)
27
+ else if (!sessionReady || (needsStripe && !stripeReady))
27
28
  buttonLabel = formatMessage(messages.loadingPayment);
28
29
  const isMonthly = cycleToShow === 'monthly';
29
30
  const frequency = cycleToShow === 'annual' ? 'year' : 'month';
@@ -83,10 +84,9 @@ const PaymentForm = ({ billing, setField, numberMountRef, expiryMountRef, cvcMou
83
84
  pricing,
84
85
  termsLink: (_jsx("a", { className: "text-gray-700", href: tosUrl, children: formatMessage(messages.termsOfUse) })),
85
86
  refundLink: (_jsx("a", { className: "text-gray-700", href: refundPolicyUrl, children: formatMessage(messages.refundPolicy) })),
86
- }) }), _jsx(Form.Group, { controlId: "recurringConfirm", className: "mb-3 payment-form__confirm", children: _jsx(Form.Checkbox, { id: "recurringConfirm", checked: confirmed, onChange: (e) => setConfirmed(e.target.checked), children: _jsx("span", { className: "text-gray-700 payment-form__confirm-label", children: recurringConfirmText }) }) }), showExtraConfirm && (_jsx(Form.Group, { controlId: "ukCaConfirm", className: "mb-4 payment-form__confirm", children: _jsx(Form.Checkbox, { id: "ukCaConfirm", checked: ukCaConfirmed, onChange: (e) => setUkCaConfirmed(e.target.checked), children: _jsx("span", { className: "text-gray-700 payment-form__confirm-label", children: formatMessage(messages.ukCaResidentConfirm) }) }) })), _jsxs(Button, { variant: "brand", className: "payment-form__submit", onClick: () => onPlaceOrder(buildConsent()), disabled: !stripeReady
87
+ }) }), _jsx(Form.Group, { controlId: "recurringConfirm", className: "mb-3 payment-form__confirm", children: _jsx(Form.Checkbox, { id: "recurringConfirm", checked: confirmed, onChange: (e) => setConfirmed(e.target.checked), children: _jsx("span", { className: "text-gray-700 payment-form__confirm-label", children: recurringConfirmText }) }) }), showExtraConfirm && (_jsx(Form.Group, { controlId: "ukCaConfirm", className: "mb-4 payment-form__confirm", children: _jsx(Form.Checkbox, { id: "ukCaConfirm", checked: ukCaConfirmed, onChange: (e) => setUkCaConfirmed(e.target.checked), children: _jsx("span", { className: "text-gray-700 payment-form__confirm-label", children: formatMessage(messages.ukCaResidentConfirm) }) }) })), _jsxs(Button, { variant: "brand", className: "payment-form__submit", onClick: () => onPlaceOrder(buildConsent(), paymentMethod), disabled: (needsStripe && !stripeReady)
87
88
  || submitting
88
89
  || !confirmed
89
- || paymentMethod === 'paypal'
90
90
  || (showExtraConfirm && !ukCaConfirmed), children: [busy && (_jsx(Spinner, { animation: "border", size: "sm", className: "mr-2", screenReaderText: formatMessage(messages.spinnerScreenReaderText) })), buttonLabel] })] }));
91
91
  };
92
92
  export default PaymentForm;
@@ -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 } from 'react';\nimport type { Ref } from 'react';\nimport { Button, Form, Spinner } from '@openedx/paragon';\nimport { useIntl, getAppConfig } from '@openedx/frontend-base';\nimport PaymentFields from '../PaymentFields/PaymentFields';\nimport type { BillingInfo, CheckoutConsent } from '../../types';\nimport type { PlanPrice } from '../../utils/billingDetails';\nimport { appId, COUNTRIES_REQUIRING_EXTRA_CONFIRM, BILLING_COUNTRIES } from '../../constants';\nimport messages from './Messages';\n\ntype PaymentMethod = 'card' | 'googlepay' | 'paypal';\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) => void,\n priceToShow: PlanPrice | 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}\n\nconst PaymentForm = ({\n billing,\n setField,\n numberMountRef,\n expiryMountRef,\n cvcMountRef,\n cardError,\n stripeReady,\n sessionReady,\n submitting,\n onPlaceOrder,\n priceToShow,\n cycleToShow,\n trialEnd,\n userCountry,\n promoAmount = '39',\n promoMonths = 3,\n monthlyRegularAmount = '59',\n}: PaymentFormProps) => {\n const { formatMessage } = useIntl();\n const config = getAppConfig(appId);\n const tosUrl = config.TOS_URL as string;\n const refundPolicyUrl = config.REFUND_POLICY_URL as string;\n const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>('card');\n const [confirmed, setConfirmed] = useState(false);\n const [ukCaConfirmed, setUkCaConfirmed] = useState(false);\n\n console.log('userCountry', userCountry);\n console.log('COUNTRIES_REQUIRING_EXTRA_CONFIRM', COUNTRIES_REQUIRING_EXTRA_CONFIRM);\n\n const showExtraConfirm\n = !!userCountry && COUNTRIES_REQUIRING_EXTRA_CONFIRM.includes(userCountry);\n\n const busy = submitting || !stripeReady || !sessionReady;\n\n let buttonLabel = formatMessage(messages.confirmSubscription);\n if (submitting) buttonLabel = formatMessage(messages.processing);\n else if (!sessionReady) buttonLabel = formatMessage(messages.loadingPayment);\n else if (!stripeReady) buttonLabel = formatMessage(messages.loadingPayment);\n\n const isMonthly = cycleToShow === 'monthly';\n const frequency = cycleToShow === 'annual' ? 'year' : 'month';\n const trialDays = 7;\n const pricing = isMonthly\n ? formatMessage(messages.termsPricingMonthly, {\n promoAmount,\n promoMonths,\n regularAmount: monthlyRegularAmount,\n })\n : formatMessage(messages.termsPricingAnnual, {\n amount: priceToShow?.amount ?? '',\n frequency,\n });\n const recurringConfirmText = isMonthly\n ? formatMessage(messages.recurringConfirmMonthly, {\n promoAmount,\n promoMonths,\n regularAmount: monthlyRegularAmount,\n })\n : formatMessage(messages.recurringConfirmAnnual, {\n amount: priceToShow?.amount ?? '',\n });\n\n const termsNoticeValues = {\n pricing,\n termsLink: formatMessage(messages.termsOfUse),\n refundLink: formatMessage(messages.refundPolicy),\n };\n\n const termsText = isMonthly\n ? formatMessage(messages.termsNotice, {\n ...termsNoticeValues,\n trialDays,\n date: trialEnd ?? '{date}',\n })\n : formatMessage(messages.termsNoticeAnnual, termsNoticeValues);\n\n const buildConsent = (): CheckoutConsent => {\n const consentParts = [termsText, recurringConfirmText];\n if (showExtraConfirm) {\n consentParts.push(formatMessage(messages.ukCaResidentConfirm));\n }\n return {\n tConsentRecurring: confirmed,\n tConsentUkCa: showExtraConfirm ? ukCaConfirmed : null,\n tConsentAt: new Date().toISOString(),\n tConsentText: consentParts.join('\\n\\n'),\n };\n };\n\n return (\n <div className=\"payment-form bg-white\">\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-4\">\n <strong className=\"d-block text-primary-500\">\n {formatMessage(messages.needAssistance)}\n </strong>{' '}\n <span className=\"text-gray-700\">\n {formatMessage(messages.needAssistanceBody, {\n link: (\n <a\n href=\"https://support.edx.org\"\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\n {/* ── Payment method accordion ── */}\n <div className=\"payment-form__methods mb-4\">\n {/* ── Card ── */}\n <div\n className={`payment-form__method${paymentMethod === 'card' ? ' payment-form__method--selected' : ''}`}\n >\n {/* Header row — always visible */}\n <label\n htmlFor=\"paymentMethod-card\"\n className=\"payment-form__method-header\"\n >\n <input\n id=\"paymentMethod-card\"\n type=\"radio\"\n name=\"paymentMethod\"\n value=\"card\"\n checked={paymentMethod === 'card'}\n onChange={() => setPaymentMethod('card')}\n className=\"payment-form__radio\"\n />\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M0 4C0 3.46957 0.210714 2.96086 0.585786 2.58579C0.960859 2.21071 1.46957 2 2 2H14C14.5304 2 15.0391 2.21071 15.4142 2.58579C15.7893 2.96086 16 3.46957 16 4H0ZM0 6V12C0 12.5304 0.210714 13.0391 0.585786 13.4142C0.960859 13.7893 1.46957 14 2 14H14C14.5304 14 15.0391 13.7893 15.4142 13.4142C15.7893 13.0391 16 12.5304 16 12V6H0ZM3 11C3 10.7348 3.10536 10.4804 3.29289 10.2929C3.48043 10.1054 3.73478 10 4 10H5C5.26522 10 5.51957 10.1054 5.70711 10.2929C5.89464 10.4804 6 10.7348 6 11C6 11.2652 5.89464 11.5196 5.70711 11.7071C5.51957 11.8946 5.26522 12 5 12H4C3.73478 12 3.48043 11.8946 3.29289 11.7071C3.10536 11.5196 3 11.2652 3 11Z\"\n fill=\"#0270DE\"\n />\n </svg>\n <span className=\"payment-form__method-name text-info-500\">\n {formatMessage(messages.card)}\n </span>\n </label>\n\n {/* Keep Stripe host nodes mounted so Elements stay interactive after toggling methods. */}\n <div\n className={`payment-form__card-panel${paymentMethod === 'card' ? '' : ' payment-form__card-panel--collapsed'}`}\n aria-hidden={paymentMethod !== 'card'}\n >\n <PaymentFields\n numberMountRef={numberMountRef}\n expiryMountRef={expiryMountRef}\n cvcMountRef={cvcMountRef}\n cardError={cardError}\n />\n <Form.Group\n controlId=\"country\"\n className=\"mt-3 mb-0 payment__field--country\"\n >\n <Form.Label className=\"small m-0 x-small\">\n {formatMessage(messages.country)}\n </Form.Label>\n <Form.Control\n as=\"select\"\n value={billing.country}\n onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>\n setField('country', e.target.value)}\n >\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 </Form.Group>\n </div>\n </div>\n\n {/* ── Google Pay (hidden for now) ── */}\n <div\n className={`my-3 payment-form__method payment-form__method--hidden${paymentMethod === 'googlepay' ? ' payment-form__method--selected' : ''}`}\n >\n <label\n htmlFor=\"paymentMethod-googlepay\"\n className=\"payment-form__method-header\"\n >\n <input\n id=\"paymentMethod-googlepay\"\n type=\"radio\"\n name=\"paymentMethod\"\n value=\"googlepay\"\n checked={paymentMethod === 'googlepay'}\n onChange={() => setPaymentMethod('googlepay')}\n className=\"payment-form__radio\"\n />\n <span\n className=\"payment-form__method-icon payment-form__method-icon--gpay\"\n aria-hidden=\"true\"\n >\n G\n </span>\n <span className=\"payment-form__method-name\">\n {formatMessage(messages.googlePay)}\n </span>\n </label>\n </div>\n\n {/* ── PayPal (UI only; backend wiring pending) ── */}\n <div\n className={`my-3 payment-form__method${paymentMethod === 'paypal' ? ' payment-form__method--selected' : ''}`}\n >\n <label\n htmlFor=\"paymentMethod-paypal\"\n className=\"payment-form__method-header\"\n >\n <input\n id=\"paymentMethod-paypal\"\n type=\"radio\"\n name=\"paymentMethod\"\n value=\"paypal\"\n checked={paymentMethod === 'paypal'}\n onChange={() => setPaymentMethod('paypal')}\n className=\"payment-form__radio\"\n />\n <span\n className=\"payment-form__method-icon payment-form__method-icon--paypal\"\n aria-hidden=\"true\"\n >\n P\n </span>\n <span className=\"payment-form__method-name\">\n {formatMessage(messages.paypal)}\n </span>\n </label>\n </div>\n </div>\n\n {/* ── Terms ── */}\n <p className=\"payment-form__terms text-gray-700 mb-3\">\n {isMonthly\n ? formatMessage(messages.termsNotice, {\n trialDays,\n date: trialEnd ?? '{date}',\n pricing,\n termsLink: (\n <a\n className=\"text-gray-700\"\n href={tosUrl}\n >\n {formatMessage(messages.termsOfUse)}\n </a>\n ),\n refundLink: (\n <a\n className=\"text-gray-700\"\n href={refundPolicyUrl}\n >\n {formatMessage(messages.refundPolicy)}\n </a>\n ),\n })\n : formatMessage(messages.termsNoticeAnnual, {\n pricing,\n termsLink: (\n <a\n className=\"text-gray-700\"\n href={tosUrl}\n >\n {formatMessage(messages.termsOfUse)}\n </a>\n ),\n refundLink: (\n <a\n className=\"text-gray-700\"\n href={refundPolicyUrl}\n >\n {formatMessage(messages.refundPolicy)}\n </a>\n ),\n })}\n </p>\n\n {/* ── Checkboxes ── */}\n <Form.Group controlId=\"recurringConfirm\" className=\"mb-3 payment-form__confirm\">\n <Form.Checkbox\n id=\"recurringConfirm\"\n checked={confirmed}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n setConfirmed(e.target.checked)}\n >\n <span className=\"text-gray-700 payment-form__confirm-label\">\n {recurringConfirmText}\n </span>\n </Form.Checkbox>\n </Form.Group>\n\n {showExtraConfirm && (\n <Form.Group controlId=\"ukCaConfirm\" className=\"mb-4 payment-form__confirm\">\n <Form.Checkbox\n id=\"ukCaConfirm\"\n checked={ukCaConfirmed}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n setUkCaConfirmed(e.target.checked)}\n >\n <span className=\"text-gray-700 payment-form__confirm-label\">\n {formatMessage(messages.ukCaResidentConfirm)}\n </span>\n </Form.Checkbox>\n </Form.Group>\n )}\n\n {/* ── Submit ── */}\n <Button\n variant=\"brand\"\n className=\"payment-form__submit\"\n onClick={() => onPlaceOrder(buildConsent())}\n disabled={\n !stripeReady\n || submitting\n || !confirmed\n || paymentMethod === 'paypal'\n || (showExtraConfirm && !ukCaConfirmed)\n }\n >\n {busy && (\n <Spinner\n animation=\"border\"\n size=\"sm\"\n className=\"mr-2\"\n screenReaderText={formatMessage(messages.spinnerScreenReaderText)}\n />\n )}\n {buttonLabel}\n </Button>\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 } from 'react';\nimport type { Ref } from 'react';\nimport { Button, Form, Spinner } from '@openedx/paragon';\nimport { useIntl, getAppConfig } from '@openedx/frontend-base';\nimport PaymentFields from '../PaymentFields/PaymentFields';\nimport type { BillingInfo, CheckoutConsent, PaymentMethod } from '../../types';\nimport type { PlanPrice } from '../../utils/billingDetails';\nimport { appId, COUNTRIES_REQUIRING_EXTRA_CONFIRM, BILLING_COUNTRIES } from '../../constants';\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: PaymentMethod) => void,\n priceToShow: PlanPrice | 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}\n\nconst PaymentForm = ({\n billing,\n setField,\n numberMountRef,\n expiryMountRef,\n cvcMountRef,\n cardError,\n stripeReady,\n sessionReady,\n submitting,\n onPlaceOrder,\n priceToShow,\n cycleToShow,\n trialEnd,\n userCountry,\n promoAmount = '39',\n promoMonths = 3,\n monthlyRegularAmount = '59',\n}: PaymentFormProps) => {\n const { formatMessage } = useIntl();\n const config = getAppConfig(appId);\n const tosUrl = config.TOS_URL as string;\n const refundPolicyUrl = config.REFUND_POLICY_URL as string;\n const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>('card');\n const [confirmed, setConfirmed] = useState(false);\n const [ukCaConfirmed, setUkCaConfirmed] = useState(false);\n\n console.log('userCountry', userCountry);\n console.log('COUNTRIES_REQUIRING_EXTRA_CONFIRM', COUNTRIES_REQUIRING_EXTRA_CONFIRM);\n\n const showExtraConfirm\n = !!userCountry && COUNTRIES_REQUIRING_EXTRA_CONFIRM.includes(userCountry);\n\n const needsStripe = paymentMethod !== 'paypal';\n const busy = submitting || (needsStripe && !stripeReady) || !sessionReady;\n\n let buttonLabel = paymentMethod === 'paypal'\n ? formatMessage(messages.continueWithPaypal)\n : formatMessage(messages.confirmSubscription);\n if (submitting) buttonLabel = formatMessage(messages.processing);\n else if (!sessionReady || (needsStripe && !stripeReady)) buttonLabel = formatMessage(messages.loadingPayment);\n\n const isMonthly = cycleToShow === 'monthly';\n const frequency = cycleToShow === 'annual' ? 'year' : 'month';\n const trialDays = 7;\n const pricing = isMonthly\n ? formatMessage(messages.termsPricingMonthly, {\n promoAmount,\n promoMonths,\n regularAmount: monthlyRegularAmount,\n })\n : formatMessage(messages.termsPricingAnnual, {\n amount: priceToShow?.amount ?? '',\n frequency,\n });\n const recurringConfirmText = isMonthly\n ? formatMessage(messages.recurringConfirmMonthly, {\n promoAmount,\n promoMonths,\n regularAmount: monthlyRegularAmount,\n })\n : formatMessage(messages.recurringConfirmAnnual, {\n amount: priceToShow?.amount ?? '',\n });\n\n const termsNoticeValues = {\n pricing,\n termsLink: formatMessage(messages.termsOfUse),\n refundLink: formatMessage(messages.refundPolicy),\n };\n\n const termsText = isMonthly\n ? formatMessage(messages.termsNotice, {\n ...termsNoticeValues,\n trialDays,\n date: trialEnd ?? '{date}',\n })\n : formatMessage(messages.termsNoticeAnnual, termsNoticeValues);\n\n const buildConsent = (): CheckoutConsent => {\n const consentParts = [termsText, recurringConfirmText];\n if (showExtraConfirm) {\n consentParts.push(formatMessage(messages.ukCaResidentConfirm));\n }\n return {\n tConsentRecurring: confirmed,\n tConsentUkCa: showExtraConfirm ? ukCaConfirmed : null,\n tConsentAt: new Date().toISOString(),\n tConsentText: consentParts.join('\\n\\n'),\n };\n };\n\n return (\n <div className=\"payment-form bg-white\">\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-4\">\n <strong className=\"d-block text-primary-500\">\n {formatMessage(messages.needAssistance)}\n </strong>{' '}\n <span className=\"text-gray-700\">\n {formatMessage(messages.needAssistanceBody, {\n link: (\n <a\n href=\"https://support.edx.org\"\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\n {/* ── Payment method accordion ── */}\n <div className=\"payment-form__methods mb-4\">\n {/* ── Card ── */}\n <div\n className={`payment-form__method${paymentMethod === 'card' ? ' payment-form__method--selected' : ''}`}\n >\n {/* Header row — always visible */}\n <label\n htmlFor=\"paymentMethod-card\"\n className=\"payment-form__method-header\"\n >\n <input\n id=\"paymentMethod-card\"\n type=\"radio\"\n name=\"paymentMethod\"\n value=\"card\"\n checked={paymentMethod === 'card'}\n onChange={() => setPaymentMethod('card')}\n className=\"payment-form__radio\"\n />\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n >\n <path\n fillRule=\"evenodd\"\n clipRule=\"evenodd\"\n d=\"M0 4C0 3.46957 0.210714 2.96086 0.585786 2.58579C0.960859 2.21071 1.46957 2 2 2H14C14.5304 2 15.0391 2.21071 15.4142 2.58579C15.7893 2.96086 16 3.46957 16 4H0ZM0 6V12C0 12.5304 0.210714 13.0391 0.585786 13.4142C0.960859 13.7893 1.46957 14 2 14H14C14.5304 14 15.0391 13.7893 15.4142 13.4142C15.7893 13.0391 16 12.5304 16 12V6H0ZM3 11C3 10.7348 3.10536 10.4804 3.29289 10.2929C3.48043 10.1054 3.73478 10 4 10H5C5.26522 10 5.51957 10.1054 5.70711 10.2929C5.89464 10.4804 6 10.7348 6 11C6 11.2652 5.89464 11.5196 5.70711 11.7071C5.51957 11.8946 5.26522 12 5 12H4C3.73478 12 3.48043 11.8946 3.29289 11.7071C3.10536 11.5196 3 11.2652 3 11Z\"\n fill=\"#0270DE\"\n />\n </svg>\n <span className=\"payment-form__method-name text-info-500\">\n {formatMessage(messages.card)}\n </span>\n </label>\n\n {/* Keep Stripe host nodes mounted so Elements stay interactive after toggling methods. */}\n <div\n className={`payment-form__card-panel${paymentMethod === 'card' ? '' : ' payment-form__card-panel--collapsed'}`}\n aria-hidden={paymentMethod !== 'card'}\n >\n <PaymentFields\n numberMountRef={numberMountRef}\n expiryMountRef={expiryMountRef}\n cvcMountRef={cvcMountRef}\n cardError={cardError}\n />\n <Form.Group\n controlId=\"country\"\n className=\"mt-3 mb-0 payment__field--country\"\n >\n <Form.Label className=\"small m-0 x-small\">\n {formatMessage(messages.country)}\n </Form.Label>\n <Form.Control\n as=\"select\"\n value={billing.country}\n onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>\n setField('country', e.target.value)}\n >\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 </Form.Group>\n </div>\n </div>\n\n {/* ── Google Pay (hidden for now) ── */}\n <div\n className={`my-3 payment-form__method payment-form__method--hidden${paymentMethod === 'googlepay' ? ' payment-form__method--selected' : ''}`}\n >\n <label\n htmlFor=\"paymentMethod-googlepay\"\n className=\"payment-form__method-header\"\n >\n <input\n id=\"paymentMethod-googlepay\"\n type=\"radio\"\n name=\"paymentMethod\"\n value=\"googlepay\"\n checked={paymentMethod === 'googlepay'}\n onChange={() => setPaymentMethod('googlepay')}\n className=\"payment-form__radio\"\n />\n <span\n className=\"payment-form__method-icon payment-form__method-icon--gpay\"\n aria-hidden=\"true\"\n >\n G\n </span>\n <span className=\"payment-form__method-name\">\n {formatMessage(messages.googlePay)}\n </span>\n </label>\n </div>\n\n {/* ── PayPal ── */}\n <div\n className={`my-3 payment-form__method${paymentMethod === 'paypal' ? ' payment-form__method--selected' : ''}`}\n >\n <label\n htmlFor=\"paymentMethod-paypal\"\n className=\"payment-form__method-header\"\n >\n <input\n id=\"paymentMethod-paypal\"\n type=\"radio\"\n name=\"paymentMethod\"\n value=\"paypal\"\n checked={paymentMethod === 'paypal'}\n onChange={() => setPaymentMethod('paypal')}\n className=\"payment-form__radio\"\n />\n <span\n className=\"payment-form__method-icon payment-form__method-icon--paypal\"\n aria-hidden=\"true\"\n >\n P\n </span>\n <span className=\"payment-form__method-name\">\n {formatMessage(messages.paypal)}\n </span>\n </label>\n </div>\n </div>\n\n {/* ── Terms ── */}\n <p className=\"payment-form__terms text-gray-700 mb-3\">\n {isMonthly\n ? formatMessage(messages.termsNotice, {\n trialDays,\n date: trialEnd ?? '{date}',\n pricing,\n termsLink: (\n <a\n className=\"text-gray-700\"\n href={tosUrl}\n >\n {formatMessage(messages.termsOfUse)}\n </a>\n ),\n refundLink: (\n <a\n className=\"text-gray-700\"\n href={refundPolicyUrl}\n >\n {formatMessage(messages.refundPolicy)}\n </a>\n ),\n })\n : formatMessage(messages.termsNoticeAnnual, {\n pricing,\n termsLink: (\n <a\n className=\"text-gray-700\"\n href={tosUrl}\n >\n {formatMessage(messages.termsOfUse)}\n </a>\n ),\n refundLink: (\n <a\n className=\"text-gray-700\"\n href={refundPolicyUrl}\n >\n {formatMessage(messages.refundPolicy)}\n </a>\n ),\n })}\n </p>\n\n {/* ── Checkboxes ── */}\n <Form.Group controlId=\"recurringConfirm\" className=\"mb-3 payment-form__confirm\">\n <Form.Checkbox\n id=\"recurringConfirm\"\n checked={confirmed}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n setConfirmed(e.target.checked)}\n >\n <span className=\"text-gray-700 payment-form__confirm-label\">\n {recurringConfirmText}\n </span>\n </Form.Checkbox>\n </Form.Group>\n\n {showExtraConfirm && (\n <Form.Group controlId=\"ukCaConfirm\" className=\"mb-4 payment-form__confirm\">\n <Form.Checkbox\n id=\"ukCaConfirm\"\n checked={ukCaConfirmed}\n onChange={(e: React.ChangeEvent<HTMLInputElement>) =>\n setUkCaConfirmed(e.target.checked)}\n >\n <span className=\"text-gray-700 payment-form__confirm-label\">\n {formatMessage(messages.ukCaResidentConfirm)}\n </span>\n </Form.Checkbox>\n </Form.Group>\n )}\n\n {/* ── Submit ── */}\n <Button\n variant=\"brand\"\n className=\"payment-form__submit\"\n onClick={() => onPlaceOrder(buildConsent(), paymentMethod)}\n disabled={\n (needsStripe && !stripeReady)\n || submitting\n || !confirmed\n || (showExtraConfirm && !ukCaConfirmed)\n }\n >\n {busy && (\n <Spinner\n animation=\"border\"\n size=\"sm\"\n className=\"mr-2\"\n screenReaderText={formatMessage(messages.spinnerScreenReaderText)}\n />\n )}\n {buttonLabel}\n </Button>\n </div>\n );\n};\n\nexport default PaymentForm;\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { Ref } from 'react';
2
2
  import type { BillingCycle } from '../../types';
3
- import type { Status, Phase, BillingInfo, BillingPlan, CheckoutConsent } from '../../types';
3
+ import type { Status, Phase, BillingInfo, BillingPlan, CheckoutConsent, PaymentMethod } from '../../types';
4
4
  export interface UseCheckoutOptions {
5
5
  apiUrl?: string;
6
6
  edxSessionUrl?: string;
@@ -42,7 +42,7 @@ export interface UseCheckoutResult {
42
42
  processingSubscription: boolean;
43
43
  /** True when Stripe sent the learner back with ?cart_bi_id=. */
44
44
  returningFromStripe: boolean;
45
- placeOrder: (consent: CheckoutConsent) => void;
45
+ placeOrder: (consent: CheckoutConsent, paymentMethod?: PaymentMethod) => void;
46
46
  /** Billing plans fetched on mount — includes prices for display in CartSummary. */
47
47
  billingPlans: BillingPlan[];
48
48
  /** Live active subscriber count for display in CartSummary. */
@@ -37,7 +37,7 @@ const redirectBrowser = (url) => {
37
37
  window.location.assign(url);
38
38
  };
39
39
  export default function useCheckout(options = {}) {
40
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
40
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
41
41
  const { apiUrl = DEFAULTS.apiUrl, edxSessionUrl, stripePublishableKey, onComplete, userCountry = null, courseRunKey: courseRunKeyProp, redirectToUrl = redirectBrowser, } = options;
42
42
  const courseRunKey = useMemo(() => courseRunKeyProp !== null && courseRunKeyProp !== void 0 ? courseRunKeyProp : getCourseRunKeyFromUrl(), [courseRunKeyProp]);
43
43
  const client = useMemo(() => {
@@ -258,21 +258,27 @@ export default function useCheckout(options = {}) {
258
258
  if (onComplete)
259
259
  onComplete((_b = (_a = subscriptionIdQuery.data) === null || _a === void 0 ? void 0 : _a.biId) !== null && _b !== void 0 ? _b : null);
260
260
  }, [status, subscriptionIdQuery.data, onComplete]);
261
- // Stripe supplies a hosted challenge URL on the cart. This must be a
262
- // full-page navigation (not an API request) so Stripe can authenticate the
263
- // cardholder and return the browser to checkout.
261
+ // Stripe and PayPal both authenticate the buyer on a hosted page before
262
+ // returning the browser to checkout, so this must be a full-page
263
+ // navigation (not an API request) for either flow.
264
264
  useEffect(() => {
265
- var _a;
266
- const redirectUrl = (_a = statusQuery.data) === null || _a === void 0 ? void 0 : _a.stripeConfirmationRedirectUrl;
265
+ var _a, _b;
266
+ const redirectUrl = ((_a = statusQuery.data) === null || _a === void 0 ? void 0 : _a.stripeConfirmationRedirectUrl)
267
+ || ((_b = statusQuery.data) === null || _b === void 0 ? void 0 : _b.paypalApprovalUrl);
267
268
  if (status !== STATUS.STRIPE_CONFIRMATION_REQUIRED
268
269
  || !redirectUrl
269
270
  || redirectedUrlRef.current === redirectUrl)
270
271
  return;
271
272
  redirectedUrlRef.current = redirectUrl;
272
273
  redirectToUrl(redirectUrl);
273
- }, [status, (_f = statusQuery.data) === null || _f === void 0 ? void 0 : _f.stripeConfirmationRedirectUrl, redirectToUrl]);
274
+ }, [
275
+ status,
276
+ (_f = statusQuery.data) === null || _f === void 0 ? void 0 : _f.stripeConfirmationRedirectUrl,
277
+ (_g = statusQuery.data) === null || _g === void 0 ? void 0 : _g.paypalApprovalUrl,
278
+ redirectToUrl,
279
+ ]);
274
280
  // --- actions ---
275
- const placeOrder = useCallback((consent) => {
281
+ const placeOrder = useCallback((consent, paymentMethod = 'card') => {
276
282
  setError('');
277
283
  setCardError('');
278
284
  if (phase === 'failed')
@@ -280,7 +286,7 @@ export default function useCheckout(options = {}) {
280
286
  // Each attempt creates its own cart, so stop reporting on the returned one.
281
287
  setReturnedCartBiId(null);
282
288
  setProcessingSubscription(true);
283
- placeMutation.mutate(consent);
289
+ placeMutation.mutate({ consent, paymentMethod });
284
290
  }, [placeMutation, phase]);
285
291
  // Turn off processingSubscription once we reach a terminal status.
286
292
  useEffect(() => {
@@ -308,7 +314,7 @@ export default function useCheckout(options = {}) {
308
314
  clearError: () => setError(''),
309
315
  cardError,
310
316
  lastComment,
311
- subscriptionDetails: (_g = subscriptionIdQuery.data) !== null && _g !== void 0 ? _g : null,
317
+ subscriptionDetails: (_h = subscriptionIdQuery.data) !== null && _h !== void 0 ? _h : null,
312
318
  billing,
313
319
  setField,
314
320
  numberMountRef,
@@ -318,13 +324,13 @@ export default function useCheckout(options = {}) {
318
324
  processingSubscription,
319
325
  returningFromStripe: returnedCartBiId !== null,
320
326
  placeOrder,
321
- billingPlans: (_j = (_h = billingPlansQuery.data) === null || _h === void 0 ? void 0 : _h.plans) !== null && _j !== void 0 ? _j : [],
322
- activeSubscriberCount: (_l = (_k = activeSubscriptionsQuery.data) === null || _k === void 0 ? void 0 : _k.active) !== null && _l !== void 0 ? _l : null,
327
+ billingPlans: (_k = (_j = billingPlansQuery.data) === null || _j === void 0 ? void 0 : _j.plans) !== null && _k !== void 0 ? _k : [],
328
+ activeSubscriberCount: (_m = (_l = activeSubscriptionsQuery.data) === null || _l === void 0 ? void 0 : _l.active) !== null && _m !== void 0 ? _m : null,
323
329
  selectedCycle,
324
330
  setCycle,
325
331
  sessionReady,
326
332
  userCountry,
327
- course: (_m = courseDetailQuery.data) !== null && _m !== void 0 ? _m : null,
333
+ course: (_o = courseDetailQuery.data) !== null && _o !== void 0 ? _o : null,
328
334
  };
329
335
  }
330
336
  //# sourceMappingURL=useCheckout.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useCheckout.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckout.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n useCallback, useEffect, useMemo, useRef, useState,\n} from 'react';\nimport type { Ref } from 'react';\nimport type { BillingCycle } from '../../types';\nimport { createCheckoutClient } from '../../utils/checkout';\nimport { getPlanFromUrl, writePlanQueryParam } from '../../utils/planQuery';\nimport { getReturnedCartBiIdFromUrl, loadStripe } from '../../utils/stripe';\nimport {\n STATUS, STATUS_LABELS, STRIPE_ELEMENT_OPTS, phaseFor, DEFAULTS,\n} from '../../constants';\nimport type {\n Status, Phase, Cart, BillingInfo, CheckoutClient, StripeLike, StripeElementLike,\n StripeElementsLike, BillingPlan, CheckoutConsent,\n} from '../../types';\nimport {\n useCartStatusQuery, useSubscriptionIdQuery, useBillingPlansQuery,\n useActiveSubscriptionsQuery, useViaxSessionQuery, useCourseDetailQuery,\n} from './useCheckoutQueries';\nimport type { EdxSessionResult } from '../../utils/provision';\nimport { useCheckoutMutations } from './useCheckoutMutations';\n\n/**\n * Read ?course_run_key= from the current URL.\n * Avoid URLSearchParams — it treats bare `+` as a space, which would turn\n * HarvardX+CS50+X into \"HarvardX CS50 X\". decodeURIComponent keeps `+` intact.\n */\nfunction getCourseRunKeyFromUrl(): string | null {\n if (typeof window === 'undefined') return null;\n const match = /[?&]course_run_key=([^&]*)/.exec(window.location.search);\n if (!match) return null;\n try {\n // Preserve literal `+` (treat as %2B) then decode %XX sequences.\n return decodeURIComponent(match[1].replace(/\\+/g, '%2B')).trim() || null;\n } catch {\n return match[1].trim() || null;\n }\n}\n\nconst redirectBrowser = (url: string): void => {\n window.location.assign(url);\n};\n\nexport interface UseCheckoutOptions {\n apiUrl?: string,\n edxSessionUrl?: string,\n stripePublishableKey?: string,\n onComplete?: (subscriptionBiId: string | null) => void,\n /** Country code resolved by CheckoutGate — used to show/hide the UK/CA extra checkbox. */\n userCountry?: string | null,\n /**\n * Course run key from the URL (e.g. course-v1:HarvardX+CS50+X).\n * When omitted, read from ?course_run_key=.\n */\n courseRunKey?: string | null,\n /** Override only for tests; production performs a full-page browser redirect. */\n redirectToUrl?: (url: string) => void,\n}\n\nexport interface UseCheckoutResult {\n phase: Phase,\n status: Status | null,\n statusLabel: string,\n stripeReady: boolean,\n /** True once the Stripe card Element iframe is fully initialised and interactive. */\n cardReady: boolean,\n notConfigured: boolean,\n showBanner: boolean,\n dismissBanner: () => void,\n error: string,\n clearError: () => void,\n cardError: string,\n lastComment: string,\n /** Full subscription details including card last 4 — available after SUBSCRIPTION_CREATED. */\n subscriptionDetails: import('../../types').SubscriptionDetails | null,\n billing: BillingInfo,\n setField: (field: keyof BillingInfo, value: string) => void,\n numberMountRef: Ref<HTMLDivElement>,\n expiryMountRef: Ref<HTMLDivElement>,\n cvcMountRef: Ref<HTMLDivElement>,\n submitting: boolean,\n /** True from Place order click until a terminal status is reached. */\n processingSubscription: boolean,\n /** True when Stripe sent the learner back with ?cart_bi_id=. */\n returningFromStripe: boolean,\n placeOrder: (consent: CheckoutConsent) => void,\n /** Billing plans fetched on mount — includes prices for display in CartSummary. */\n billingPlans: BillingPlan[],\n /** Live active subscriber count for display in CartSummary. */\n activeSubscriberCount: number | null,\n /** Currently selected billing cycle. */\n selectedCycle: BillingCycle,\n /** Switch between monthly and annual billing. */\n setCycle: (cycle: BillingCycle) => void,\n /** True once GET /edx-session has returned and the viax token is set. */\n sessionReady: boolean,\n /** ISO 3166-1 alpha-2 country code — passed in from CheckoutGate for the UK/CA checkbox. */\n userCountry: string | null,\n /** Course details when arrived via ?course_run_key=; null for standard checkout. */\n course: import('../../types').CourseDetail | null,\n}\n\nexport default function useCheckout(options: UseCheckoutOptions = {}): UseCheckoutResult {\n const {\n apiUrl = DEFAULTS.apiUrl,\n edxSessionUrl,\n stripePublishableKey,\n onComplete,\n userCountry = null,\n courseRunKey: courseRunKeyProp,\n redirectToUrl = redirectBrowser,\n } = options;\n\n const courseRunKey = useMemo(\n () => courseRunKeyProp ?? getCourseRunKeyFromUrl(),\n [courseRunKeyProp],\n );\n\n const client = useMemo<CheckoutClient | null>(() => {\n if (!apiUrl) return null;\n return createCheckoutClient({ apiUrl });\n }, [apiUrl]);\n\n const notConfigured = !client || !stripePublishableKey || !edxSessionUrl;\n\n // Exchange edX session for viax token on page load.\n const sessionQuery = useViaxSessionQuery(notConfigured ? null : edxSessionUrl!);\n const sessionReady = !!sessionQuery.data;\n\n useEffect(() => {\n if (sessionQuery.data && client) {\n client.setToken(sessionQuery.data.viaxToken);\n }\n }, [sessionQuery.data, client]);\n\n // Fetch billing plans, subscriber count, and optional course once the viax session is ready.\n const billingPlansQuery = useBillingPlansQuery(client, sessionReady);\n const activeSubscriptionsQuery = useActiveSubscriptionsQuery(client, sessionReady);\n const courseDetailQuery = useCourseDetailQuery(client, sessionReady, courseRunKey);\n\n // --- state ---\n const [showBanner, setShowBanner] = useState(true);\n const [cart, setCart] = useState<Cart | null>(null);\n const [stripe, setStripe] = useState<StripeLike | null>(null);\n const [stripeReady, setStripeReady] = useState(false);\n const [cardReady, setCardReady] = useState(false);\n const [error, setError] = useState('');\n const [cardError, setCardError] = useState('');\n const [billing, setBilling] = useState<BillingInfo>({\n firstName: '', lastName: '', city: '', zip: '', country: 'US', line1: '', state: '',\n });\n const [selectedCycle, setSelectedCycle] = useState<BillingCycle>(getPlanFromUrl);\n // Set when Stripe returns from a 3DS challenge; cleared once a new attempt starts.\n const [returnedCartBiId, setReturnedCartBiId] = useState<string | null>(getReturnedCartBiIdFromUrl);\n const [processingSubscription, setProcessingSubscription] = useState(\n () => getReturnedCartBiIdFromUrl() !== null,\n );\n\n const setCycle = useCallback((cycle: BillingCycle) => {\n setSelectedCycle(cycle);\n writePlanQueryParam(cycle);\n }, []);\n\n useEffect(() => {\n if (sessionQuery.error) {\n setError(sessionQuery.error.message);\n }\n }, [sessionQuery.error]);\n\n const setField = useCallback(\n (field: keyof BillingInfo, value: string) => setBilling((p: BillingInfo) => ({ ...p, [field]: value })),\n [],\n );\n\n // --- refs ---\n // Stripe instances — written inside useEffect and mutations.\n const stripeInstanceRef = useRef<StripeLike | null>(null);\n const cardNumberElementRef = useRef<StripeElementLike | null>(null);\n const cardExpiryElementRef = useRef<StripeElementLike | null>(null);\n const cardCvcElementRef = useRef<StripeElementLike | null>(null);\n const elementsRef = useRef<StripeElementsLike | null>(null);\n // DOM mount targets — callback refs so we remount when hosts leave and return.\n const numberMountNodeRef = useRef<HTMLDivElement | null>(null);\n const expiryMountNodeRef = useRef<HTMLDivElement | null>(null);\n const cvcMountNodeRef = useRef<HTMLDivElement | null>(null);\n const [cardHostsEpoch, setCardHostsEpoch] = useState(0);\n\n const assignCardHost = useCallback((\n store: { current: HTMLDivElement | null },\n node: HTMLDivElement | null,\n ) => {\n store.current = node;\n if (\n numberMountNodeRef.current\n && expiryMountNodeRef.current\n && cvcMountNodeRef.current\n ) {\n setCardHostsEpoch((n) => n + 1);\n return;\n }\n // Hosts are leaving the DOM — detach so Stripe is not bound to dead nodes.\n cardNumberElementRef.current?.unmount?.();\n cardExpiryElementRef.current?.unmount?.();\n cardCvcElementRef.current?.unmount?.();\n }, []);\n\n const numberMountRef = useCallback((node: HTMLDivElement | null) => {\n assignCardHost(numberMountNodeRef, node);\n }, [assignCardHost]);\n const expiryMountRef = useCallback((node: HTMLDivElement | null) => {\n assignCardHost(expiryMountNodeRef, node);\n }, [assignCardHost]);\n const cvcMountRef = useCallback((node: HTMLDivElement | null) => {\n assignCardHost(cvcMountNodeRef, node);\n }, [assignCardHost]);\n const mountedRef = useRef(true);\n const completedRef = useRef(false);\n const redirectedUrlRef = useRef<string | null>(null);\n const sessionRef = useRef<EdxSessionResult | null>(null);\n\n useEffect(() => {\n sessionRef.current = sessionQuery.data ?? null;\n }, [sessionQuery.data]);\n\n // Mirror state into refs so mutations can read current values without stale closures.\n const cartRef = useRef<Cart | null>(null);\n const billingRef = useRef<BillingInfo>(billing);\n const selectedCycleRef = useRef<BillingCycle>(selectedCycle);\n\n useEffect(() => {\n cartRef.current = cart;\n }, [cart]);\n useEffect(() => {\n billingRef.current = billing;\n }, [billing]);\n useEffect(() => {\n selectedCycleRef.current = selectedCycle;\n }, [selectedCycle]);\n useEffect(() => {\n stripeInstanceRef.current = stripe;\n }, [stripe]);\n\n // --- server state ---\n // Poll the cart created in this session, or the one Stripe just returned\n // from. The returned cart waits for the viax session so the status request\n // carries a valid token.\n const cartBiId = cart?.biId ?? (sessionReady ? returnedCartBiId : null);\n const statusQuery = useCartStatusQuery(client, cartBiId);\n const status: Status | null = statusQuery.data?.status ?? null;\n const lastComment = statusQuery.data?.lastComment ?? '';\n\n // Phase is 'ready' until a cart exists, then driven by bpStatus.\n const phase: Phase = cartBiId ? phaseFor(status, status === STATUS.IN_CART) : 'ready';\n const subscriptionIdQuery = useSubscriptionIdQuery(\n client, cartBiId, status === STATUS.SUBSCRIPTION_CREATED,\n );\n\n // --- mutations ---\n const { placeOrder: placeMutation } = useCheckoutMutations({\n client,\n sessionRef,\n stripeRef: stripeInstanceRef,\n cardNumberElRef: cardNumberElementRef,\n billingRef,\n selectedCycleRef,\n setError: (msg: string) => {\n setProcessingSubscription(false);\n setError(msg);\n },\n setCardError: (msg: string) => {\n setProcessingSubscription(false);\n setCardError(msg);\n },\n onCartCreated: (created: Cart) => {\n // Sync the ref immediately so any same-tick readers see the cart\n // before the setCart → useEffect mirror runs.\n cartRef.current = created;\n if (mountedRef.current) setCart(created);\n },\n });\n\n // --- load Stripe.js on mount ---\n useEffect(() => {\n mountedRef.current = true;\n if (!stripePublishableKey) return undefined;\n\n (async () => {\n try {\n const instance = await loadStripe(stripePublishableKey);\n if (mountedRef.current) {\n setStripe(instance);\n setStripeReady(true);\n }\n } catch (e: unknown) {\n if (mountedRef.current) setError((e as Error).message);\n }\n })();\n\n return () => {\n mountedRef.current = false;\n };\n }, [stripePublishableKey]);\n\n // --- mount Stripe Elements once Stripe is loaded; remount if hosts return ---\n useEffect(() => {\n if (!stripe) return;\n const numberNode = numberMountNodeRef.current;\n const expiryNode = expiryMountNodeRef.current;\n const cvcNode = cvcMountNodeRef.current;\n if (!numberNode || !expiryNode || !cvcNode) return;\n\n if (cardNumberElementRef.current && cardExpiryElementRef.current && cardCvcElementRef.current) {\n try {\n cardNumberElementRef.current.unmount?.();\n cardExpiryElementRef.current.unmount?.();\n cardCvcElementRef.current.unmount?.();\n cardNumberElementRef.current.mount(numberNode);\n cardExpiryElementRef.current.mount(expiryNode);\n cardCvcElementRef.current.mount(cvcNode);\n return;\n } catch {\n // Element is bound to a destroyed host — create a fresh Elements group.\n cardNumberElementRef.current = null;\n cardExpiryElementRef.current = null;\n cardCvcElementRef.current = null;\n elementsRef.current = null;\n }\n }\n\n setCardReady(false);\n\n const elements = stripe.elements();\n elementsRef.current = elements;\n\n const number = elements.create('cardNumber', { ...STRIPE_ELEMENT_OPTS, showIcon: true });\n const expiry = elements.create('cardExpiry', STRIPE_ELEMENT_OPTS);\n const cvc = elements.create('cardCvc', STRIPE_ELEMENT_OPTS);\n\n number.mount(numberNode);\n expiry.mount(expiryNode);\n cvc.mount(cvcNode);\n\n number.on('ready', () => {\n if (mountedRef.current) setCardReady(true);\n });\n number.on('change', (e: { error?: { message?: string } }) => {\n if (mountedRef.current) setCardError(e.error?.message || '');\n });\n\n cardNumberElementRef.current = number;\n cardExpiryElementRef.current = expiry;\n cardCvcElementRef.current = cvc;\n }, [stripe, cardHostsEpoch]);\n\n // --- on success ---\n useEffect(() => {\n if (status !== STATUS.SUBSCRIPTION_CREATED || completedRef.current) return;\n completedRef.current = true;\n if (onComplete) onComplete(subscriptionIdQuery.data?.biId ?? null);\n }, [status, subscriptionIdQuery.data, onComplete]);\n\n // Stripe supplies a hosted challenge URL on the cart. This must be a\n // full-page navigation (not an API request) so Stripe can authenticate the\n // cardholder and return the browser to checkout.\n useEffect(() => {\n const redirectUrl = statusQuery.data?.stripeConfirmationRedirectUrl;\n if (\n status !== STATUS.STRIPE_CONFIRMATION_REQUIRED\n || !redirectUrl\n || redirectedUrlRef.current === redirectUrl\n ) return;\n\n redirectedUrlRef.current = redirectUrl;\n redirectToUrl(redirectUrl);\n }, [status, statusQuery.data?.stripeConfirmationRedirectUrl, redirectToUrl]);\n\n // --- actions ---\n const placeOrder = useCallback((consent: CheckoutConsent) => {\n setError('');\n setCardError('');\n if (phase === 'failed') setCart(null);\n // Each attempt creates its own cart, so stop reporting on the returned one.\n setReturnedCartBiId(null);\n setProcessingSubscription(true);\n placeMutation.mutate(consent);\n }, [placeMutation, phase]);\n\n // Turn off processingSubscription once we reach a terminal status.\n useEffect(() => {\n if (!status) return;\n const terminalStatuses: Status[] = [\n STATUS.SUBSCRIPTION_CREATED,\n STATUS.STRIPE_FAIL,\n STATUS.SANCTION_BLOCK,\n ];\n if (terminalStatuses.includes(status)) {\n setProcessingSubscription(false);\n }\n }, [status]);\n\n return {\n phase,\n status,\n statusLabel: (status && STATUS_LABELS[status]) || 'Processing your order…',\n stripeReady,\n cardReady,\n notConfigured,\n showBanner,\n dismissBanner: () => setShowBanner(false),\n error,\n clearError: () => setError(''),\n cardError,\n lastComment,\n subscriptionDetails: subscriptionIdQuery.data ?? null,\n billing,\n setField,\n numberMountRef,\n expiryMountRef,\n cvcMountRef,\n submitting: placeMutation.isPending,\n processingSubscription,\n returningFromStripe: returnedCartBiId !== null,\n placeOrder,\n billingPlans: billingPlansQuery.data?.plans ?? [],\n activeSubscriberCount: activeSubscriptionsQuery.data?.active ?? null,\n selectedCycle,\n setCycle,\n sessionReady,\n userCountry,\n course: courseDetailQuery.data ?? null,\n };\n}\n"]}
1
+ {"version":3,"file":"useCheckout.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckout.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n useCallback, useEffect, useMemo, useRef, useState,\n} from 'react';\nimport type { Ref } from 'react';\nimport type { BillingCycle } from '../../types';\nimport { createCheckoutClient } from '../../utils/checkout';\nimport { getPlanFromUrl, writePlanQueryParam } from '../../utils/planQuery';\nimport { getReturnedCartBiIdFromUrl, loadStripe } from '../../utils/stripe';\nimport {\n STATUS, STATUS_LABELS, STRIPE_ELEMENT_OPTS, phaseFor, DEFAULTS,\n} from '../../constants';\nimport type {\n Status, Phase, Cart, BillingInfo, CheckoutClient, StripeLike, StripeElementLike,\n StripeElementsLike, BillingPlan, CheckoutConsent, PaymentMethod,\n} from '../../types';\nimport {\n useCartStatusQuery, useSubscriptionIdQuery, useBillingPlansQuery,\n useActiveSubscriptionsQuery, useViaxSessionQuery, useCourseDetailQuery,\n} from './useCheckoutQueries';\nimport type { EdxSessionResult } from '../../utils/provision';\nimport { useCheckoutMutations } from './useCheckoutMutations';\n\n/**\n * Read ?course_run_key= from the current URL.\n * Avoid URLSearchParams — it treats bare `+` as a space, which would turn\n * HarvardX+CS50+X into \"HarvardX CS50 X\". decodeURIComponent keeps `+` intact.\n */\nfunction getCourseRunKeyFromUrl(): string | null {\n if (typeof window === 'undefined') return null;\n const match = /[?&]course_run_key=([^&]*)/.exec(window.location.search);\n if (!match) return null;\n try {\n // Preserve literal `+` (treat as %2B) then decode %XX sequences.\n return decodeURIComponent(match[1].replace(/\\+/g, '%2B')).trim() || null;\n } catch {\n return match[1].trim() || null;\n }\n}\n\nconst redirectBrowser = (url: string): void => {\n window.location.assign(url);\n};\n\nexport interface UseCheckoutOptions {\n apiUrl?: string,\n edxSessionUrl?: string,\n stripePublishableKey?: string,\n onComplete?: (subscriptionBiId: string | null) => void,\n /** Country code resolved by CheckoutGate — used to show/hide the UK/CA extra checkbox. */\n userCountry?: string | null,\n /**\n * Course run key from the URL (e.g. course-v1:HarvardX+CS50+X).\n * When omitted, read from ?course_run_key=.\n */\n courseRunKey?: string | null,\n /** Override only for tests; production performs a full-page browser redirect. */\n redirectToUrl?: (url: string) => void,\n}\n\nexport interface UseCheckoutResult {\n phase: Phase,\n status: Status | null,\n statusLabel: string,\n stripeReady: boolean,\n /** True once the Stripe card Element iframe is fully initialised and interactive. */\n cardReady: boolean,\n notConfigured: boolean,\n showBanner: boolean,\n dismissBanner: () => void,\n error: string,\n clearError: () => void,\n cardError: string,\n lastComment: string,\n /** Full subscription details including card last 4 — available after SUBSCRIPTION_CREATED. */\n subscriptionDetails: import('../../types').SubscriptionDetails | null,\n billing: BillingInfo,\n setField: (field: keyof BillingInfo, value: string) => void,\n numberMountRef: Ref<HTMLDivElement>,\n expiryMountRef: Ref<HTMLDivElement>,\n cvcMountRef: Ref<HTMLDivElement>,\n submitting: boolean,\n /** True from Place order click until a terminal status is reached. */\n processingSubscription: boolean,\n /** True when Stripe sent the learner back with ?cart_bi_id=. */\n returningFromStripe: boolean,\n placeOrder: (consent: CheckoutConsent, paymentMethod?: PaymentMethod) => void,\n /** Billing plans fetched on mount — includes prices for display in CartSummary. */\n billingPlans: BillingPlan[],\n /** Live active subscriber count for display in CartSummary. */\n activeSubscriberCount: number | null,\n /** Currently selected billing cycle. */\n selectedCycle: BillingCycle,\n /** Switch between monthly and annual billing. */\n setCycle: (cycle: BillingCycle) => void,\n /** True once GET /edx-session has returned and the viax token is set. */\n sessionReady: boolean,\n /** ISO 3166-1 alpha-2 country code — passed in from CheckoutGate for the UK/CA checkbox. */\n userCountry: string | null,\n /** Course details when arrived via ?course_run_key=; null for standard checkout. */\n course: import('../../types').CourseDetail | null,\n}\n\nexport default function useCheckout(options: UseCheckoutOptions = {}): UseCheckoutResult {\n const {\n apiUrl = DEFAULTS.apiUrl,\n edxSessionUrl,\n stripePublishableKey,\n onComplete,\n userCountry = null,\n courseRunKey: courseRunKeyProp,\n redirectToUrl = redirectBrowser,\n } = options;\n\n const courseRunKey = useMemo(\n () => courseRunKeyProp ?? getCourseRunKeyFromUrl(),\n [courseRunKeyProp],\n );\n\n const client = useMemo<CheckoutClient | null>(() => {\n if (!apiUrl) return null;\n return createCheckoutClient({ apiUrl });\n }, [apiUrl]);\n\n const notConfigured = !client || !stripePublishableKey || !edxSessionUrl;\n\n // Exchange edX session for viax token on page load.\n const sessionQuery = useViaxSessionQuery(notConfigured ? null : edxSessionUrl!);\n const sessionReady = !!sessionQuery.data;\n\n useEffect(() => {\n if (sessionQuery.data && client) {\n client.setToken(sessionQuery.data.viaxToken);\n }\n }, [sessionQuery.data, client]);\n\n // Fetch billing plans, subscriber count, and optional course once the viax session is ready.\n const billingPlansQuery = useBillingPlansQuery(client, sessionReady);\n const activeSubscriptionsQuery = useActiveSubscriptionsQuery(client, sessionReady);\n const courseDetailQuery = useCourseDetailQuery(client, sessionReady, courseRunKey);\n\n // --- state ---\n const [showBanner, setShowBanner] = useState(true);\n const [cart, setCart] = useState<Cart | null>(null);\n const [stripe, setStripe] = useState<StripeLike | null>(null);\n const [stripeReady, setStripeReady] = useState(false);\n const [cardReady, setCardReady] = useState(false);\n const [error, setError] = useState('');\n const [cardError, setCardError] = useState('');\n const [billing, setBilling] = useState<BillingInfo>({\n firstName: '', lastName: '', city: '', zip: '', country: 'US', line1: '', state: '',\n });\n const [selectedCycle, setSelectedCycle] = useState<BillingCycle>(getPlanFromUrl);\n // Set when Stripe returns from a 3DS challenge; cleared once a new attempt starts.\n const [returnedCartBiId, setReturnedCartBiId] = useState<string | null>(getReturnedCartBiIdFromUrl);\n const [processingSubscription, setProcessingSubscription] = useState(\n () => getReturnedCartBiIdFromUrl() !== null,\n );\n\n const setCycle = useCallback((cycle: BillingCycle) => {\n setSelectedCycle(cycle);\n writePlanQueryParam(cycle);\n }, []);\n\n useEffect(() => {\n if (sessionQuery.error) {\n setError(sessionQuery.error.message);\n }\n }, [sessionQuery.error]);\n\n const setField = useCallback(\n (field: keyof BillingInfo, value: string) => setBilling((p: BillingInfo) => ({ ...p, [field]: value })),\n [],\n );\n\n // --- refs ---\n // Stripe instances — written inside useEffect and mutations.\n const stripeInstanceRef = useRef<StripeLike | null>(null);\n const cardNumberElementRef = useRef<StripeElementLike | null>(null);\n const cardExpiryElementRef = useRef<StripeElementLike | null>(null);\n const cardCvcElementRef = useRef<StripeElementLike | null>(null);\n const elementsRef = useRef<StripeElementsLike | null>(null);\n // DOM mount targets — callback refs so we remount when hosts leave and return.\n const numberMountNodeRef = useRef<HTMLDivElement | null>(null);\n const expiryMountNodeRef = useRef<HTMLDivElement | null>(null);\n const cvcMountNodeRef = useRef<HTMLDivElement | null>(null);\n const [cardHostsEpoch, setCardHostsEpoch] = useState(0);\n\n const assignCardHost = useCallback((\n store: { current: HTMLDivElement | null },\n node: HTMLDivElement | null,\n ) => {\n store.current = node;\n if (\n numberMountNodeRef.current\n && expiryMountNodeRef.current\n && cvcMountNodeRef.current\n ) {\n setCardHostsEpoch((n) => n + 1);\n return;\n }\n // Hosts are leaving the DOM — detach so Stripe is not bound to dead nodes.\n cardNumberElementRef.current?.unmount?.();\n cardExpiryElementRef.current?.unmount?.();\n cardCvcElementRef.current?.unmount?.();\n }, []);\n\n const numberMountRef = useCallback((node: HTMLDivElement | null) => {\n assignCardHost(numberMountNodeRef, node);\n }, [assignCardHost]);\n const expiryMountRef = useCallback((node: HTMLDivElement | null) => {\n assignCardHost(expiryMountNodeRef, node);\n }, [assignCardHost]);\n const cvcMountRef = useCallback((node: HTMLDivElement | null) => {\n assignCardHost(cvcMountNodeRef, node);\n }, [assignCardHost]);\n const mountedRef = useRef(true);\n const completedRef = useRef(false);\n const redirectedUrlRef = useRef<string | null>(null);\n const sessionRef = useRef<EdxSessionResult | null>(null);\n\n useEffect(() => {\n sessionRef.current = sessionQuery.data ?? null;\n }, [sessionQuery.data]);\n\n // Mirror state into refs so mutations can read current values without stale closures.\n const cartRef = useRef<Cart | null>(null);\n const billingRef = useRef<BillingInfo>(billing);\n const selectedCycleRef = useRef<BillingCycle>(selectedCycle);\n\n useEffect(() => {\n cartRef.current = cart;\n }, [cart]);\n useEffect(() => {\n billingRef.current = billing;\n }, [billing]);\n useEffect(() => {\n selectedCycleRef.current = selectedCycle;\n }, [selectedCycle]);\n useEffect(() => {\n stripeInstanceRef.current = stripe;\n }, [stripe]);\n\n // --- server state ---\n // Poll the cart created in this session, or the one Stripe just returned\n // from. The returned cart waits for the viax session so the status request\n // carries a valid token.\n const cartBiId = cart?.biId ?? (sessionReady ? returnedCartBiId : null);\n const statusQuery = useCartStatusQuery(client, cartBiId);\n const status: Status | null = statusQuery.data?.status ?? null;\n const lastComment = statusQuery.data?.lastComment ?? '';\n\n // Phase is 'ready' until a cart exists, then driven by bpStatus.\n const phase: Phase = cartBiId ? phaseFor(status, status === STATUS.IN_CART) : 'ready';\n const subscriptionIdQuery = useSubscriptionIdQuery(\n client, cartBiId, status === STATUS.SUBSCRIPTION_CREATED,\n );\n\n // --- mutations ---\n const { placeOrder: placeMutation } = useCheckoutMutations({\n client,\n sessionRef,\n stripeRef: stripeInstanceRef,\n cardNumberElRef: cardNumberElementRef,\n billingRef,\n selectedCycleRef,\n setError: (msg: string) => {\n setProcessingSubscription(false);\n setError(msg);\n },\n setCardError: (msg: string) => {\n setProcessingSubscription(false);\n setCardError(msg);\n },\n onCartCreated: (created: Cart) => {\n // Sync the ref immediately so any same-tick readers see the cart\n // before the setCart → useEffect mirror runs.\n cartRef.current = created;\n if (mountedRef.current) setCart(created);\n },\n });\n\n // --- load Stripe.js on mount ---\n useEffect(() => {\n mountedRef.current = true;\n if (!stripePublishableKey) return undefined;\n\n (async () => {\n try {\n const instance = await loadStripe(stripePublishableKey);\n if (mountedRef.current) {\n setStripe(instance);\n setStripeReady(true);\n }\n } catch (e: unknown) {\n if (mountedRef.current) setError((e as Error).message);\n }\n })();\n\n return () => {\n mountedRef.current = false;\n };\n }, [stripePublishableKey]);\n\n // --- mount Stripe Elements once Stripe is loaded; remount if hosts return ---\n useEffect(() => {\n if (!stripe) return;\n const numberNode = numberMountNodeRef.current;\n const expiryNode = expiryMountNodeRef.current;\n const cvcNode = cvcMountNodeRef.current;\n if (!numberNode || !expiryNode || !cvcNode) return;\n\n if (cardNumberElementRef.current && cardExpiryElementRef.current && cardCvcElementRef.current) {\n try {\n cardNumberElementRef.current.unmount?.();\n cardExpiryElementRef.current.unmount?.();\n cardCvcElementRef.current.unmount?.();\n cardNumberElementRef.current.mount(numberNode);\n cardExpiryElementRef.current.mount(expiryNode);\n cardCvcElementRef.current.mount(cvcNode);\n return;\n } catch {\n // Element is bound to a destroyed host — create a fresh Elements group.\n cardNumberElementRef.current = null;\n cardExpiryElementRef.current = null;\n cardCvcElementRef.current = null;\n elementsRef.current = null;\n }\n }\n\n setCardReady(false);\n\n const elements = stripe.elements();\n elementsRef.current = elements;\n\n const number = elements.create('cardNumber', { ...STRIPE_ELEMENT_OPTS, showIcon: true });\n const expiry = elements.create('cardExpiry', STRIPE_ELEMENT_OPTS);\n const cvc = elements.create('cardCvc', STRIPE_ELEMENT_OPTS);\n\n number.mount(numberNode);\n expiry.mount(expiryNode);\n cvc.mount(cvcNode);\n\n number.on('ready', () => {\n if (mountedRef.current) setCardReady(true);\n });\n number.on('change', (e: { error?: { message?: string } }) => {\n if (mountedRef.current) setCardError(e.error?.message || '');\n });\n\n cardNumberElementRef.current = number;\n cardExpiryElementRef.current = expiry;\n cardCvcElementRef.current = cvc;\n }, [stripe, cardHostsEpoch]);\n\n // --- on success ---\n useEffect(() => {\n if (status !== STATUS.SUBSCRIPTION_CREATED || completedRef.current) return;\n completedRef.current = true;\n if (onComplete) onComplete(subscriptionIdQuery.data?.biId ?? null);\n }, [status, subscriptionIdQuery.data, onComplete]);\n\n // Stripe and PayPal both authenticate the buyer on a hosted page before\n // returning the browser to checkout, so this must be a full-page\n // navigation (not an API request) for either flow.\n useEffect(() => {\n const redirectUrl = statusQuery.data?.stripeConfirmationRedirectUrl\n || statusQuery.data?.paypalApprovalUrl;\n if (\n status !== STATUS.STRIPE_CONFIRMATION_REQUIRED\n || !redirectUrl\n || redirectedUrlRef.current === redirectUrl\n ) return;\n\n redirectedUrlRef.current = redirectUrl;\n redirectToUrl(redirectUrl);\n }, [\n status,\n statusQuery.data?.stripeConfirmationRedirectUrl,\n statusQuery.data?.paypalApprovalUrl,\n redirectToUrl,\n ]);\n\n // --- actions ---\n const placeOrder = useCallback((consent: CheckoutConsent, paymentMethod: PaymentMethod = 'card') => {\n setError('');\n setCardError('');\n if (phase === 'failed') setCart(null);\n // Each attempt creates its own cart, so stop reporting on the returned one.\n setReturnedCartBiId(null);\n setProcessingSubscription(true);\n placeMutation.mutate({ consent, paymentMethod });\n }, [placeMutation, phase]);\n\n // Turn off processingSubscription once we reach a terminal status.\n useEffect(() => {\n if (!status) return;\n const terminalStatuses: Status[] = [\n STATUS.SUBSCRIPTION_CREATED,\n STATUS.STRIPE_FAIL,\n STATUS.SANCTION_BLOCK,\n ];\n if (terminalStatuses.includes(status)) {\n setProcessingSubscription(false);\n }\n }, [status]);\n\n return {\n phase,\n status,\n statusLabel: (status && STATUS_LABELS[status]) || 'Processing your order…',\n stripeReady,\n cardReady,\n notConfigured,\n showBanner,\n dismissBanner: () => setShowBanner(false),\n error,\n clearError: () => setError(''),\n cardError,\n lastComment,\n subscriptionDetails: subscriptionIdQuery.data ?? null,\n billing,\n setField,\n numberMountRef,\n expiryMountRef,\n cvcMountRef,\n submitting: placeMutation.isPending,\n processingSubscription,\n returningFromStripe: returnedCartBiId !== null,\n placeOrder,\n billingPlans: billingPlansQuery.data?.plans ?? [],\n activeSubscriberCount: activeSubscriptionsQuery.data?.active ?? null,\n selectedCycle,\n setCycle,\n sessionReady,\n userCountry,\n course: courseDetailQuery.data ?? null,\n };\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { RefObject } from 'react';
2
2
  import type { EdxSessionResult } from '../../utils/provision';
3
- import type { CheckoutClient, StripeLike, StripeElementLike, Cart, BillingInfo, CheckoutConsent, BillingCycle } from '../../types';
3
+ import type { CheckoutClient, StripeLike, StripeElementLike, Cart, BillingInfo, CheckoutConsent, BillingCycle, PaymentMethod } from '../../types';
4
4
  export interface CheckoutMutationsCtx {
5
5
  client: CheckoutClient | null;
6
6
  sessionRef: RefObject<EdxSessionResult | null>;
@@ -13,6 +13,10 @@ export interface CheckoutMutationsCtx {
13
13
  setCardError: (msg: string) => void;
14
14
  onCartCreated: (cart: Cart) => void;
15
15
  }
16
+ export interface PlaceOrderArgs {
17
+ consent: CheckoutConsent;
18
+ paymentMethod: PaymentMethod;
19
+ }
16
20
  export declare function useCheckoutMutations(ctx: CheckoutMutationsCtx): {
17
- placeOrder: import("@tanstack/react-query").UseMutationResult<Cart, Error, CheckoutConsent, unknown>;
21
+ placeOrder: import("@tanstack/react-query").UseMutationResult<Cart, Error, PlaceOrderArgs, unknown>;
18
22
  };
@@ -14,12 +14,23 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
14
14
  * The viax token and customerUid are established on page load via
15
15
  * GET /edx-session and passed in through sessionRef.
16
16
  *
17
+ * Card:
17
18
  * 1. getSalableUid + getBillingPlans (parallel)
18
19
  * 2. createTwouCart
19
20
  * 3. stripe.createPaymentMethod → pm_xxx
20
21
  * 4. updateTwouCartItem → billing address + pm_xxx
21
22
  * 5. updateTwouCart → consent fields (SUBS-228)
22
23
  * 6. startBusinessProcessableTransition('Sanction_Check')
24
+ *
25
+ * PayPal (mirrors the card sequence, skipping card tokenization):
26
+ * 1. getSalableUid + getBillingPlans (parallel)
27
+ * 2. createTwouCart
28
+ * 3. updateTwouCartItem → billing address + TwouPaypalPayment
29
+ * 4. updateTwouCart → consent fields + return URL (SUBS-228)
30
+ * 5. startBusinessProcessableTransition('Sanction_Check')
31
+ * The backend then drives bpStatus to STRIPE_CONFIRMATION_REQUIRED and
32
+ * populates tPaypalApprovalUrl, which useCheckout redirects to as the
33
+ * hosted confirmation step for PayPal.
23
34
  */
24
35
  import { useMutation, useQueryClient } from '@tanstack/react-query';
25
36
  import { STATUS } from '../../constants';
@@ -52,25 +63,27 @@ export function useCheckoutMutations(ctx) {
52
63
  const queryClient = useQueryClient();
53
64
  const optimisticConfirm = (cartBiId) => {
54
65
  queryClient.setQueryData(checkoutKeys.status(cartBiId), (old) => {
55
- var _a, _b, _c;
66
+ var _a, _b, _c, _d;
56
67
  console.log('old optimisticConfirm', old);
57
68
  return {
58
69
  status: STATUS.CONFIRM,
59
70
  stripeSubscriptionId: (_a = old === null || old === void 0 ? void 0 : old.stripeSubscriptionId) !== null && _a !== void 0 ? _a : null,
60
71
  stripeConfirmationRedirectUrl: (_b = old === null || old === void 0 ? void 0 : old.stripeConfirmationRedirectUrl) !== null && _b !== void 0 ? _b : null,
61
- lastComment: (_c = old === null || old === void 0 ? void 0 : old.lastComment) !== null && _c !== void 0 ? _c : '',
72
+ paypalApprovalUrl: (_c = old === null || old === void 0 ? void 0 : old.paypalApprovalUrl) !== null && _c !== void 0 ? _c : null,
73
+ lastComment: (_d = old === null || old === void 0 ? void 0 : old.lastComment) !== null && _d !== void 0 ? _d : '',
62
74
  };
63
75
  });
64
76
  queryClient.invalidateQueries({ queryKey: checkoutKeys.status(cartBiId) });
65
77
  };
66
78
  const placeOrder = useMutation({
67
- mutationFn: (consent) => __awaiter(this, void 0, void 0, function* () {
68
- var _a;
69
- const { client, stripeRef, cardNumberElRef, billingRef } = ctx;
70
- const stripe = stripeRef.current;
71
- const card = cardNumberElRef.current;
72
- if (!client || !stripe || !card)
79
+ mutationFn: (_a) => __awaiter(this, [_a], void 0, function* ({ consent, paymentMethod }) {
80
+ var _b, _c;
81
+ const { client, billingRef } = ctx;
82
+ if (!client)
73
83
  throw new Error('Checkout is not ready yet.');
84
+ if (paymentMethod === 'googlepay') {
85
+ throw new Error('Google Pay is not supported yet. Please choose Card or PayPal.');
86
+ }
74
87
  const billing = billingRef.current;
75
88
  if (!billing)
76
89
  throw new Error('Billing information is not available. Please refresh and try again.');
@@ -85,7 +98,7 @@ export function useCheckoutMutations(ctx) {
85
98
  client.getBillingPlans(),
86
99
  ]);
87
100
  const salableUid = productInfo.uid;
88
- const selectedCycle = (_a = ctx.selectedCycleRef.current) !== null && _a !== void 0 ? _a : 'monthly';
101
+ const selectedCycle = (_b = ctx.selectedCycleRef.current) !== null && _b !== void 0 ? _b : 'monthly';
89
102
  const selectedPlan = getPlanPrice(plansResult.plans, selectedCycle);
90
103
  if (!selectedPlan) {
91
104
  throw new Error(`No billing plan found for ${selectedCycle} billing.`);
@@ -94,32 +107,46 @@ export function useCheckoutMutations(ctx) {
94
107
  // ── Step 2: Create the cart ───────────────────────────────────────────
95
108
  const createdCart = yield client.createCart({ customerUid, salableUid, billingPlanUid });
96
109
  ctx.onCartCreated(createdCart);
97
- // ── Step 3: Tokenize the card (browser → Stripe) ──────────────────────
98
- const { paymentMethod, error } = yield stripe.createPaymentMethod({
99
- type: 'card',
100
- card,
101
- billing_details: {
102
- name: `${billing.firstName} ${billing.lastName}`.trim(),
103
- address: toStripeAddress(billing),
104
- },
105
- });
106
- if (error || !paymentMethod) {
107
- throw new CardValidationError((error === null || error === void 0 ? void 0 : error.message) || 'There was a problem with your card.');
108
- }
109
- // ── Step 4: Attach billing address + payment method to cart item ──────
110
+ // ── Step 3: Attach billing address + payment method to cart item ──────
110
111
  const { cartItemUid } = yield client.getCartItem(createdCart.biId);
111
- yield client.updateCartItem({
112
- cartItemUid,
113
- billingAddress: toCartAddress(billing),
114
- paymentMethodId: paymentMethod.id,
115
- });
116
- // ── Step 5: Persist consent + 3DS return URL on the TwouCart BI ───────
112
+ if (paymentMethod === 'paypal') {
113
+ // No card tokenization — PayPal issues a hosted approval redirect instead.
114
+ yield client.updateCartItem({
115
+ cartItemUid,
116
+ billingAddress: toCartAddress(billing),
117
+ paypalEmail: (_c = session.email) !== null && _c !== void 0 ? _c : null,
118
+ });
119
+ }
120
+ else {
121
+ const stripe = ctx.stripeRef.current;
122
+ const card = ctx.cardNumberElRef.current;
123
+ if (!stripe || !card)
124
+ throw new Error('Checkout is not ready yet.');
125
+ // Tokenize the card (browser → Stripe).
126
+ const { paymentMethod: stripePaymentMethod, error } = yield stripe.createPaymentMethod({
127
+ type: 'card',
128
+ card,
129
+ billing_details: {
130
+ name: `${billing.firstName} ${billing.lastName}`.trim(),
131
+ address: toStripeAddress(billing),
132
+ },
133
+ });
134
+ if (error || !stripePaymentMethod) {
135
+ throw new CardValidationError((error === null || error === void 0 ? void 0 : error.message) || 'There was a problem with your card.');
136
+ }
137
+ yield client.updateCartItem({
138
+ cartItemUid,
139
+ billingAddress: toCartAddress(billing),
140
+ paymentMethodId: stripePaymentMethod.id,
141
+ });
142
+ }
143
+ // ── Step 4: Persist consent + hosted-payment return URL on the TwouCart BI ────
117
144
  yield client.updateCartConsent({
118
145
  cartUid: createdCart.uid,
119
146
  consent,
120
- stripeReturnUrl: buildStripeReturnUrl(createdCart.biId),
147
+ returnUrl: buildStripeReturnUrl(createdCart.biId),
121
148
  });
122
- // ── Step 6: Fire the backend workflow ─────────────────────────────────
149
+ // ── Step 5: Fire the backend workflow ─────────────────────────────────
123
150
  yield client.placeOrder(createdCart.uid);
124
151
  return createdCart;
125
152
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"useCheckoutMutations.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutMutations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * query/useCheckoutMutations.ts — write-side TanStack mutations.\n *\n * The placeOrder mutation drives the checkout sequence on button click.\n * The viax token and customerUid are established on page load via\n * GET /edx-session and passed in through sessionRef.\n *\n * 1. getSalableUid + getBillingPlans (parallel)\n * 2. createTwouCart\n * 3. stripe.createPaymentMethod → pm_xxx\n * 4. updateTwouCartItem → billing address + pm_xxx\n * 5. updateTwouCart → consent fields (SUBS-228)\n * 6. startBusinessProcessableTransition('Sanction_Check')\n */\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport type { RefObject } from 'react';\nimport { STATUS } from '../../constants';\nimport { checkoutKeys } from './keys';\nimport type { EdxSessionResult } from '../../utils/provision';\nimport { getPlanPrice } from '../../utils/billingDetails';\nimport { buildStripeReturnUrl } from '../../utils/stripe';\nimport type {\n CheckoutClient, StripeLike, StripeElementLike, Cart, BillingInfo,\n CartBillingAddress, CartStatusResult, CheckoutConsent, BillingCycle,\n} from '../../types';\n\nclass CardValidationError extends Error {}\n\nfunction toCartAddress(b: BillingInfo): CartBillingAddress {\n return {\n tFirstName: b.firstName,\n tLastName: b.lastName,\n upaLine1: b.line1,\n upaCity: b.city,\n upaPostcode: b.zip,\n gadStateOrProvince: b.state,\n gadCountry: { iso2Code: b.country },\n };\n}\n\nfunction toStripeAddress(b: BillingInfo): Record<string, string | undefined> {\n return {\n line1: b.line1 || undefined,\n city: b.city || undefined,\n state: b.state || undefined,\n postal_code: b.zip || undefined,\n country: b.country || undefined,\n };\n}\n\nexport interface CheckoutMutationsCtx {\n client: CheckoutClient | null,\n sessionRef: RefObject<EdxSessionResult | null>,\n stripeRef: RefObject<StripeLike | null>,\n cardNumberElRef: RefObject<StripeElementLike | null>,\n billingRef: RefObject<BillingInfo | null>,\n /** Selected monthly/annual cycle at Place order time. */\n selectedCycleRef: RefObject<BillingCycle>,\n setError: (msg: string) => void,\n setCardError: (msg: string) => void,\n onCartCreated: (cart: Cart) => void,\n}\n\nexport function useCheckoutMutations(ctx: CheckoutMutationsCtx) {\n const queryClient = useQueryClient();\n\n const optimisticConfirm = (cartBiId: string) => {\n queryClient.setQueryData<CartStatusResult>(checkoutKeys.status(cartBiId), (old) => {\n console.log('old optimisticConfirm', old);\n return {\n status: STATUS.CONFIRM,\n stripeSubscriptionId: old?.stripeSubscriptionId ?? null,\n stripeConfirmationRedirectUrl: old?.stripeConfirmationRedirectUrl ?? null,\n lastComment: old?.lastComment ?? '',\n };\n });\n queryClient.invalidateQueries({ queryKey: checkoutKeys.status(cartBiId) });\n };\n\n const placeOrder = useMutation<Cart, Error, CheckoutConsent>({\n mutationFn: async (consent) => {\n const { client, stripeRef, cardNumberElRef, billingRef } = ctx;\n const stripe = stripeRef.current;\n const card = cardNumberElRef.current;\n if (!client || !stripe || !card) throw new Error('Checkout is not ready yet.');\n\n const billing = billingRef.current;\n if (!billing) throw new Error('Billing information is not available. Please refresh and try again.');\n\n const session = ctx.sessionRef.current;\n if (!session) {\n throw new Error('Checkout session is not ready yet. Please wait a moment and try again.');\n }\n const { customerUid } = session;\n\n // ── Step 1: Fetch salable(product) uid + billing plans in parallel ─────────────\n const [productInfo, plansResult] = await Promise.all([\n client.getSalableUid('TWOU-SUB-PLAN-001'),\n client.getBillingPlans(),\n ]);\n const salableUid = productInfo.uid;\n const selectedCycle = ctx.selectedCycleRef.current ?? 'monthly';\n const selectedPlan = getPlanPrice(plansResult.plans, selectedCycle);\n if (!selectedPlan) {\n throw new Error(`No billing plan found for ${selectedCycle} billing.`);\n }\n const billingPlanUid = selectedPlan.uid;\n\n // ── Step 2: Create the cart ───────────────────────────────────────────\n const createdCart = await client.createCart({ customerUid, salableUid, billingPlanUid });\n ctx.onCartCreated(createdCart);\n\n // ── Step 3: Tokenize the card (browser → Stripe) ──────────────────────\n const { paymentMethod, error } = await stripe.createPaymentMethod({\n type: 'card',\n card,\n billing_details: {\n name: `${billing.firstName} ${billing.lastName}`.trim(),\n address: toStripeAddress(billing),\n },\n });\n if (error || !paymentMethod) {\n throw new CardValidationError(error?.message || 'There was a problem with your card.');\n }\n\n // ── Step 4: Attach billing address + payment method to cart item ──────\n const { cartItemUid } = await client.getCartItem(createdCart.biId);\n await client.updateCartItem({\n cartItemUid,\n billingAddress: toCartAddress(billing),\n paymentMethodId: paymentMethod.id,\n });\n\n // ── Step 5: Persist consent + 3DS return URL on the TwouCart BI ───────\n await client.updateCartConsent({\n cartUid: createdCart.uid,\n consent,\n stripeReturnUrl: buildStripeReturnUrl(createdCart.biId),\n });\n\n // ── Step 6: Fire the backend workflow ─────────────────────────────────\n await client.placeOrder(createdCart.uid);\n\n return createdCart;\n },\n onSuccess: (cart) => {\n optimisticConfirm(cart.biId);\n },\n onError: (err) => {\n if (err instanceof CardValidationError) ctx.setCardError(err.message);\n else ctx.setError(err.message);\n },\n });\n\n return { placeOrder };\n}\n"]}
1
+ {"version":3,"file":"useCheckoutMutations.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutMutations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * query/useCheckoutMutations.ts — write-side TanStack mutations.\n *\n * The placeOrder mutation drives the checkout sequence on button click.\n * The viax token and customerUid are established on page load via\n * GET /edx-session and passed in through sessionRef.\n *\n * Card:\n * 1. getSalableUid + getBillingPlans (parallel)\n * 2. createTwouCart\n * 3. stripe.createPaymentMethod → pm_xxx\n * 4. updateTwouCartItem → billing address + pm_xxx\n * 5. updateTwouCart → consent fields (SUBS-228)\n * 6. startBusinessProcessableTransition('Sanction_Check')\n *\n * PayPal (mirrors the card sequence, skipping card tokenization):\n * 1. getSalableUid + getBillingPlans (parallel)\n * 2. createTwouCart\n * 3. updateTwouCartItem → billing address + TwouPaypalPayment\n * 4. updateTwouCart → consent fields + return URL (SUBS-228)\n * 5. startBusinessProcessableTransition('Sanction_Check')\n * The backend then drives bpStatus to STRIPE_CONFIRMATION_REQUIRED and\n * populates tPaypalApprovalUrl, which useCheckout redirects to as the\n * hosted confirmation step for PayPal.\n */\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport type { RefObject } from 'react';\nimport { STATUS } from '../../constants';\nimport { checkoutKeys } from './keys';\nimport type { EdxSessionResult } from '../../utils/provision';\nimport { getPlanPrice } from '../../utils/billingDetails';\nimport { buildStripeReturnUrl } from '../../utils/stripe';\nimport type {\n CheckoutClient, StripeLike, StripeElementLike, Cart, BillingInfo,\n CartBillingAddress, CartStatusResult, CheckoutConsent, BillingCycle, PaymentMethod,\n} from '../../types';\n\nclass CardValidationError extends Error {}\n\nfunction toCartAddress(b: BillingInfo): CartBillingAddress {\n return {\n tFirstName: b.firstName,\n tLastName: b.lastName,\n upaLine1: b.line1,\n upaCity: b.city,\n upaPostcode: b.zip,\n gadStateOrProvince: b.state,\n gadCountry: { iso2Code: b.country },\n };\n}\n\nfunction toStripeAddress(b: BillingInfo): Record<string, string | undefined> {\n return {\n line1: b.line1 || undefined,\n city: b.city || undefined,\n state: b.state || undefined,\n postal_code: b.zip || undefined,\n country: b.country || undefined,\n };\n}\n\nexport interface CheckoutMutationsCtx {\n client: CheckoutClient | null,\n sessionRef: RefObject<EdxSessionResult | null>,\n stripeRef: RefObject<StripeLike | null>,\n cardNumberElRef: RefObject<StripeElementLike | null>,\n billingRef: RefObject<BillingInfo | null>,\n /** Selected monthly/annual cycle at Place order time. */\n selectedCycleRef: RefObject<BillingCycle>,\n setError: (msg: string) => void,\n setCardError: (msg: string) => void,\n onCartCreated: (cart: Cart) => void,\n}\n\nexport interface PlaceOrderArgs {\n consent: CheckoutConsent,\n paymentMethod: PaymentMethod,\n}\n\nexport function useCheckoutMutations(ctx: CheckoutMutationsCtx) {\n const queryClient = useQueryClient();\n\n const optimisticConfirm = (cartBiId: string) => {\n queryClient.setQueryData<CartStatusResult>(checkoutKeys.status(cartBiId), (old) => {\n console.log('old optimisticConfirm', old);\n return {\n status: STATUS.CONFIRM,\n stripeSubscriptionId: old?.stripeSubscriptionId ?? null,\n stripeConfirmationRedirectUrl: old?.stripeConfirmationRedirectUrl ?? null,\n paypalApprovalUrl: old?.paypalApprovalUrl ?? null,\n lastComment: old?.lastComment ?? '',\n };\n });\n queryClient.invalidateQueries({ queryKey: checkoutKeys.status(cartBiId) });\n };\n\n const placeOrder = useMutation<Cart, Error, PlaceOrderArgs>({\n mutationFn: async ({ consent, paymentMethod }) => {\n const { client, billingRef } = ctx;\n if (!client) throw new Error('Checkout is not ready yet.');\n\n if (paymentMethod === 'googlepay') {\n throw new Error('Google Pay is not supported yet. Please choose Card or PayPal.');\n }\n\n const billing = billingRef.current;\n if (!billing) throw new Error('Billing information is not available. Please refresh and try again.');\n\n const session = ctx.sessionRef.current;\n if (!session) {\n throw new Error('Checkout session is not ready yet. Please wait a moment and try again.');\n }\n const { customerUid } = session;\n\n // ── Step 1: Fetch salable(product) uid + billing plans in parallel ─────────────\n const [productInfo, plansResult] = await Promise.all([\n client.getSalableUid('TWOU-SUB-PLAN-001'),\n client.getBillingPlans(),\n ]);\n const salableUid = productInfo.uid;\n const selectedCycle = ctx.selectedCycleRef.current ?? 'monthly';\n const selectedPlan = getPlanPrice(plansResult.plans, selectedCycle);\n if (!selectedPlan) {\n throw new Error(`No billing plan found for ${selectedCycle} billing.`);\n }\n const billingPlanUid = selectedPlan.uid;\n\n // ── Step 2: Create the cart ───────────────────────────────────────────\n const createdCart = await client.createCart({ customerUid, salableUid, billingPlanUid });\n ctx.onCartCreated(createdCart);\n\n // ── Step 3: Attach billing address + payment method to cart item ──────\n const { cartItemUid } = await client.getCartItem(createdCart.biId);\n\n if (paymentMethod === 'paypal') {\n // No card tokenization — PayPal issues a hosted approval redirect instead.\n await client.updateCartItem({\n cartItemUid,\n billingAddress: toCartAddress(billing),\n paypalEmail: session.email ?? null,\n });\n } else {\n const stripe = ctx.stripeRef.current;\n const card = ctx.cardNumberElRef.current;\n if (!stripe || !card) throw new Error('Checkout is not ready yet.');\n\n // Tokenize the card (browser → Stripe).\n const { paymentMethod: stripePaymentMethod, error } = await stripe.createPaymentMethod({\n type: 'card',\n card,\n billing_details: {\n name: `${billing.firstName} ${billing.lastName}`.trim(),\n address: toStripeAddress(billing),\n },\n });\n if (error || !stripePaymentMethod) {\n throw new CardValidationError(error?.message || 'There was a problem with your card.');\n }\n\n await client.updateCartItem({\n cartItemUid,\n billingAddress: toCartAddress(billing),\n paymentMethodId: stripePaymentMethod.id,\n });\n }\n\n // ── Step 4: Persist consent + hosted-payment return URL on the TwouCart BI ────\n await client.updateCartConsent({\n cartUid: createdCart.uid,\n consent,\n returnUrl: buildStripeReturnUrl(createdCart.biId),\n });\n\n // ── Step 5: Fire the backend workflow ─────────────────────────────────\n await client.placeOrder(createdCart.uid);\n\n return createdCart;\n },\n onSuccess: (cart) => {\n optimisticConfirm(cart.biId);\n },\n onError: (err) => {\n if (err instanceof CardValidationError) ctx.setCardError(err.message);\n else ctx.setError(err.message);\n },\n });\n\n return { placeOrder };\n}\n"]}
package/dist/routes.js CHANGED
@@ -16,7 +16,7 @@ const subscriptionAccessLoader = () => __awaiter(void 0, void 0, void 0, functio
16
16
  try {
17
17
  const { data } = yield getAuthenticatedHttpClient().get(getSubsInitApiUrl());
18
18
  if (data.userSubscription && data.userSubscription.isSubscriber) {
19
- throw redirect('/subscription-learner-dashboard'); // redirect to subscription learner dashboard if user have subscription access
19
+ throw redirect('/subscription-learner-dashboard');
20
20
  }
21
21
  return null;
22
22
  }
@@ -25,7 +25,7 @@ const subscriptionAccessLoader = () => __awaiter(void 0, void 0, void 0, functio
25
25
  if (error instanceof Response) {
26
26
  throw error;
27
27
  }
28
- throw redirect('/learner-dashboard'); // redirect to learner dashboard if an error occurs
28
+ throw redirect('/learner-dashboard');
29
29
  }
30
30
  });
31
31
  // composed loader that checks for authentication and subscription access
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.tsx"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EACL,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAEvD,sDAAsD;AACtD,MAAM,wBAAwB,GAAG,GAAS,EAAE;IAC1C,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GACV,MAAM,0BAA0B,EAAE,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC,CAAC;QAEhE,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,gBAAgB,CAAC,YAAY,EAAE,CAAC;YAChE,MAAM,QAAQ,CAAC,iCAAiC,CAAC,CAAC,CAAC,8EAA8E;QACnI,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,mCAAmC;QACnC,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;YAC9B,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAC,mDAAmD;IAC3F,CAAC;AACH,CAAC,CAAA,CAAC;AAEF,yEAAyE;AACzE,MAAM,eAAe,GAAG,CAAO,IAAI,EAAE,EAAE;IACrC,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,wBAAwB,EAAE,CAAC;IAEjC,OAAO,IAAI,CAAC;AACd,CAAC,CAAA,CAAC;AAEF,MAAM,MAAM,GAAG;IACb;QACE,EAAE,EAAE,0CAA0C;QAC9C,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,eAAe;QACvB,MAAM,EAAE;YACN,KAAK,EAAE,CAAC,wBAAwB,CAAC;SAClC;QACK,IAAI;;gBACR,MAAM,MAAM,GAAG,MAAM,MAAM;gBACzB,uCAAuC,CAAC,QAAQ,CACjD,CAAC;gBACF,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;YACvC,CAAC;SAAA;QACD,QAAQ,EAAE;YACR;gBACE,KAAK,EAAE,IAAI;gBACL,IAAI;;wBACR,MAAM,MAAM,GAAG,MAAM,MAAM;wBACzB,sCAAsC,CAAC,6BAA6B,CACrE,CAAC;wBACF,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;oBACvC,CAAC;iBAAA;aACF;SACF;KACF;CACF,CAAC;AAEF,eAAe,MAAM,CAAC","sourcesContent":["import {\n authenticatedLoader,\n getAuthenticatedHttpClient,\n} from '@openedx/frontend-base';\nimport { redirect } from 'react-router-dom';\n\nimport { getSubsInitApiUrl } from './data/services/subs/urls';\nimport { subscriptionCheckoutRole } from './constants';\n\n// loader to check if the user has subscription access\nconst subscriptionAccessLoader = async () => {\n try {\n const { data }\n = await getAuthenticatedHttpClient().get(getSubsInitApiUrl());\n\n if (data.userSubscription && data.userSubscription.isSubscriber) {\n throw redirect('/subscription-learner-dashboard'); // redirect to subscription learner dashboard if user have subscription access\n }\n\n return null;\n } catch (error) {\n // Let redirect responses bubble up\n if (error instanceof Response) {\n throw error;\n }\n\n throw redirect('/learner-dashboard'); // redirect to learner dashboard if an error occurs\n }\n};\n\n// composed loader that checks for authentication and subscription access\nconst protectedLoader = async (args) => {\n await authenticatedLoader(args);\n await subscriptionAccessLoader();\n\n return null;\n};\n\nconst routes = [\n {\n id: 'org.openedx.frontend.route.template.main',\n path: 'checkout',\n loader: protectedLoader,\n handle: {\n roles: [subscriptionCheckoutRole],\n },\n async lazy() {\n const module = await import(\n /* webpackChunkName: \"template-main\" */ './Main'\n );\n return { Component: module.default };\n },\n children: [\n {\n index: true,\n async lazy() {\n const module = await import(\n /* webpackChunkName: \"CheckoutPage\" */ './checkoutPage/CheckoutPage'\n );\n return { Component: module.default };\n },\n },\n ],\n },\n];\n\nexport default routes;\n"]}
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.tsx"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EACL,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAEvD,sDAAsD;AACtD,MAAM,wBAAwB,GAAG,GAAS,EAAE;IAC1C,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GACV,MAAM,0BAA0B,EAAE,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC,CAAC;QAEhE,IAAI,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,gBAAgB,CAAC,YAAY,EAAE,CAAC;YAChE,MAAM,QAAQ,CAAC,iCAAiC,CAAC,CAAC;QACpD,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,mCAAmC;QACnC,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;YAC9B,MAAM,KAAK,CAAC;QACd,CAAC;QAED,MAAM,QAAQ,CAAC,oBAAoB,CAAC,CAAC;IACvC,CAAC;AACH,CAAC,CAAA,CAAC;AAEF,yEAAyE;AACzE,MAAM,eAAe,GAAG,CAAO,IAAI,EAAE,EAAE;IACrC,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,wBAAwB,EAAE,CAAC;IAEjC,OAAO,IAAI,CAAC;AACd,CAAC,CAAA,CAAC;AAEF,MAAM,MAAM,GAAG;IACb;QACE,EAAE,EAAE,0CAA0C;QAC9C,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,eAAe;QACvB,MAAM,EAAE;YACN,KAAK,EAAE,CAAC,wBAAwB,CAAC;SAClC;QACK,IAAI;;gBACR,MAAM,MAAM,GAAG,MAAM,MAAM;gBACzB,uCAAuC,CAAC,QAAQ,CACjD,CAAC;gBACF,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;YACvC,CAAC;SAAA;QACD,QAAQ,EAAE;YACR;gBACE,KAAK,EAAE,IAAI;gBACL,IAAI;;wBACR,MAAM,MAAM,GAAG,MAAM,MAAM;wBACzB,sCAAsC,CAAC,6BAA6B,CACrE,CAAC;wBACF,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;oBACvC,CAAC;iBAAA;aACF;SACF;KACF;CACF,CAAC;AAEF,eAAe,MAAM,CAAC","sourcesContent":["import {\n authenticatedLoader,\n getAuthenticatedHttpClient,\n} from '@openedx/frontend-base';\nimport { redirect } from 'react-router-dom';\n\nimport { getSubsInitApiUrl } from './data/services/subs/urls';\nimport { subscriptionCheckoutRole } from './constants';\n\n// loader to check if the user has subscription access\nconst subscriptionAccessLoader = async () => {\n try {\n const { data }\n = await getAuthenticatedHttpClient().get(getSubsInitApiUrl());\n\n if (data.userSubscription && data.userSubscription.isSubscriber) {\n throw redirect('/subscription-learner-dashboard');\n }\n\n return null;\n } catch (error) {\n // Let redirect responses bubble up\n if (error instanceof Response) {\n throw error;\n }\n\n throw redirect('/learner-dashboard');\n }\n};\n\n// composed loader that checks for authentication and subscription access\nconst protectedLoader = async (args) => {\n await authenticatedLoader(args);\n await subscriptionAccessLoader();\n\n return null;\n};\n\nconst routes = [\n {\n id: 'org.openedx.frontend.route.template.main',\n path: 'checkout',\n loader: protectedLoader,\n handle: {\n roles: [subscriptionCheckoutRole],\n },\n async lazy() {\n const module = await import(\n /* webpackChunkName: \"template-main\" */ './Main'\n );\n return { Component: module.default };\n },\n children: [\n {\n index: true,\n async lazy() {\n const module = await import(\n /* webpackChunkName: \"CheckoutPage\" */ './checkoutPage/CheckoutPage'\n );\n return { Component: module.default };\n },\n },\n ],\n },\n];\n\nexport default routes;\n"]}
package/dist/slots.js CHANGED
@@ -1,7 +1,3 @@
1
- var _a;
2
- import { exampleHeaderApp } from './widgets/ExampleHeader';
3
- const slots = [
4
- ...((_a = exampleHeaderApp.slots) !== null && _a !== void 0 ? _a : []),
5
- ];
1
+ const slots = [];
6
2
  export default slots;
7
3
  //# sourceMappingURL=slots.js.map
package/dist/slots.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"slots.js","sourceRoot":"","sources":["../src/slots.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,MAAM,KAAK,GAAoB;IAC7B,GAAG,CAAC,MAAA,gBAAgB,CAAC,KAAK,mCAAI,EAAE,CAAC;CAClC,CAAC;AAEF,eAAe,KAAK,CAAC","sourcesContent":["import { SlotOperation } from '@openedx/frontend-base';\n\nimport { exampleHeaderApp } from './widgets/ExampleHeader';\n\nconst slots: SlotOperation[] = [\n ...(exampleHeaderApp.slots ?? []),\n];\n\nexport default slots;\n"]}
1
+ {"version":3,"file":"slots.js","sourceRoot":"","sources":["../src/slots.tsx"],"names":[],"mappings":"AAEA,MAAM,KAAK,GAAoB,EAAE,CAAC;AAElC,eAAe,KAAK,CAAC","sourcesContent":["import { SlotOperation } from '@openedx/frontend-base';\n\nconst slots: SlotOperation[] = [];\n\nexport default slots;\n"]}
package/dist/types.d.ts CHANGED
@@ -29,6 +29,7 @@ export interface CartIds {
29
29
  billingPlanUid: string;
30
30
  }
31
31
  export type BillingCycle = 'monthly' | 'annual';
32
+ export type PaymentMethod = 'card' | 'googlepay' | 'paypal';
32
33
  export type BillingPeriod = 'MONTHLY' | 'ANNUAL' | 'QUARTERLY' | string;
33
34
  export interface BillingPlanOwnershipModel {
34
35
  uid: string;
@@ -101,8 +102,10 @@ export interface ProductInfo {
101
102
  export interface CartStatusResult {
102
103
  status: Status | null;
103
104
  stripeSubscriptionId: string | null;
104
- /** Stripe-hosted 3DS challenge URL when bpStatus is STRIPE_CONFIRMATION_REQUIRED. */
105
+ /** Stripe-hosted challenge URL for the checkout confirmation step. */
105
106
  stripeConfirmationRedirectUrl: string | null;
107
+ /** PayPal-hosted approval URL for the same checkout confirmation step. */
108
+ paypalApprovalUrl: string | null;
106
109
  lastComment: string;
107
110
  }
108
111
  export interface CartBillingAddress {
@@ -120,6 +123,8 @@ export interface UpdateCartItemArgs {
120
123
  cartItemUid: string;
121
124
  billingAddress?: CartBillingAddress;
122
125
  paymentMethodId?: string;
126
+ /** Payer email attached when the payment type is TwouPaypalPayment. */
127
+ paypalEmail?: string | null;
123
128
  }
124
129
  /** Auto-renewal consent captured on TwouCart at checkout confirmation (SUBS-228). */
125
130
  export interface CheckoutConsent {
@@ -135,8 +140,8 @@ export interface CheckoutConsent {
135
140
  export interface UpdateCartConsentArgs {
136
141
  cartUid: string;
137
142
  consent: CheckoutConsent;
138
- /** Absolute URL Stripe returns to after a 3DS challenge (tStripeReturnUrl). */
139
- stripeReturnUrl?: string | null;
143
+ /** Absolute checkout return URL stored in the backend's tStripeReturnUrl field. */
144
+ returnUrl?: string | null;
140
145
  }
141
146
  export interface BillingInfo {
142
147
  firstName: string;
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Status, Phase } from './constants';\n\nexport type { Status, Phase };\n\n// --- Bootstrap domain --------------------------------------------------------\nexport interface TwouCustomerContact {\n email?: string,\n}\n\nexport interface TwouCustomerParty {\n paName: string,\n indFirstName?: string,\n indLastName?: string,\n paContacts: { edges: { node: TwouCustomerContact }[] },\n}\n\nexport interface TwouCustomer {\n uid: string,\n parId: string,\n parParty: TwouCustomerParty,\n}\n\nexport type GqlRunner = (query: string, variables?: Record<string, unknown>, retry?: boolean) => Promise<any>;\n\n// --- Cart domain -------------------------------------------------------------\nexport interface Cart {\n biId: string,\n uid: string,\n}\n\nexport interface CartIds {\n customerUid: string,\n salableUid: string,\n billingPlanUid: string,\n}\n\nexport type BillingCycle = 'monthly' | 'annual';\n\nexport type BillingPeriod = 'MONTHLY' | 'ANNUAL' | 'QUARTERLY' | string;\n\nexport interface BillingPlanOwnershipModel {\n uid: string,\n omName: string,\n}\n\nexport interface BillingPlan {\n uid: string,\n stripePriceId: string,\n /** Price amount e.g. \"39.99\" */\n amount: string,\n /** Currency code e.g. \"USD\" */\n currency: string,\n isTemplate: boolean,\n ownershipModel: BillingPlanOwnershipModel,\n}\n\nexport interface SubscriptionDetails {\n biId: string, // \"BI301ZDAD6CMICG2\"\n trialStart: string | null,\n trialEnd: string | null,\n renewalType: string | null, // \"Annual\" | \"Monthly\"\n cardLast4: string | null, // \"4242\"\n cardBrand: string | null, // \"visa\" | \"mastercard\" etc\n stripePaymentMethodId: string | null,\n planName: string | null, // \"Monthly Subscription\"\n}\n\nexport interface CurrentUserSubscriberResult {\n isSubscriber: boolean,\n subscriptionBiId: string | null,\n status: string | null,\n subscriptionStartDate: string | null,\n currentPeriodEnd: string | null,\n}\n\n/** Course shown on checkout / post-checkout when arriving via ?course_run_key=. */\nexport interface CourseDetail {\n maId: string,\n maName: string,\n maDescription: string | null,\n tShortDescription: string | null,\n imageUrl: string | null,\n tCourseKey: string | null,\n tBrandText: string | null,\n tPrimarySubjectArea: string | null,\n tLevelType: string | null,\n tLanguage: 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 tUrlCourse: string | null,\n tB2cSubscriptionInclusion: boolean | null,\n tHasOfacRestrictions: boolean | null,\n tCatalogActive: boolean | null,\n}\n\n/** Returned by getBillingPlans — all available plans for the product. */\nexport interface BillingPlansResult {\n plans: BillingPlan[],\n}\n\n/** Returned by getSalableUid — the product uid and its ownership models. */\nexport interface ProductInfo {\n uid: string,\n maName: string,\n ownershipModels: { uid: string, omName: string }[],\n}\n\nexport interface CartStatusResult {\n status: Status | null,\n stripeSubscriptionId: string | null,\n /** Stripe-hosted 3DS challenge URL when bpStatus is STRIPE_CONFIRMATION_REQUIRED. */\n stripeConfirmationRedirectUrl: string | null,\n lastComment: string,\n}\n\nexport interface CartBillingAddress {\n tFirstName: string,\n tLastName: string,\n upaLine1: string,\n upaCity: string,\n upaPostcode: string,\n gadStateOrProvince: string,\n gadCountry: { iso2Code: string },\n}\n\nexport interface UpdateCartItemArgs {\n cartItemUid: string,\n billingAddress?: CartBillingAddress,\n paymentMethodId?: string,\n}\n\n/** Auto-renewal consent captured on TwouCart at checkout confirmation (SUBS-228). */\nexport interface CheckoutConsent {\n /** Main auto-renewal checkbox — always required at submit. */\n tConsentRecurring: boolean,\n /** UK/CA resident checkbox; null when that checkbox is not shown. */\n tConsentUkCa: boolean | null,\n /** ISO-8601 timestamp when consent was captured. */\n tConsentAt: string,\n /** Exact disclosure text shown to the learner (audit trail). */\n tConsentText: string,\n}\n\nexport interface UpdateCartConsentArgs {\n cartUid: string,\n consent: CheckoutConsent,\n /** Absolute URL Stripe returns to after a 3DS challenge (tStripeReturnUrl). */\n stripeReturnUrl?: string | null,\n}\n\nexport interface BillingInfo {\n firstName: string,\n lastName: string,\n city: string,\n zip: string,\n country: string,\n line1: string,\n state: string,\n}\n\n// --- Checkout client surface -------------------------------------------------\nexport interface CheckoutClient {\n gql: GqlRunner,\n setToken: (token: string) => void,\n createCart: (vars: CartIds) => Promise<Cart>,\n getCartItem: (biId: string) => Promise<{ cartUid: string, cartItemUid: string }>,\n updateCartItem: (args: UpdateCartItemArgs) => Promise<void>,\n updateCartConsent: (args: UpdateCartConsentArgs) => Promise<void>,\n placeOrder: (cartUid: string) => Promise<unknown>,\n getCartStatus: (biId: string) => Promise<CartStatusResult>,\n findSubscriptionBiId: (biId: string) => Promise<SubscriptionDetails | null>,\n getSalableUid: (maId: string) => Promise<ProductInfo>,\n getBillingPlans: () => Promise<BillingPlansResult>,\n getActiveSubscriptions: () => Promise<{ active: number }>,\n isCurrentUserSubscriber: () => Promise<CurrentUserSubscriberResult | null>,\n getCourseDetail: (courseKey: string) => Promise<CourseDetail | null>,\n}\n\nexport interface DevAuth {\n iamUrl: string,\n clientId: string,\n clientSecret: string,\n}\n\n// --- Minimal Stripe.js types -------------------------------------------------\n// Just the surface this module uses. If you depend on `@stripe/stripe-js`,\n// you can replace these with its richer types.\nexport interface StripeElementChangeEvent {\n error?: { message?: string },\n [key: string]: unknown,\n}\n\nexport interface StripeElementLike {\n mount: (node: HTMLElement) => void,\n on: (event: string, handler: (e: StripeElementChangeEvent) => void) => void,\n unmount?: () => void,\n}\n\nexport interface StripeElementsLike {\n create: (type: string, options?: Record<string, unknown>) => StripeElementLike,\n}\n\nexport interface StripeError {\n message?: string,\n code?: string,\n}\n\nexport interface PaymentMethodResult {\n paymentMethod?: { id: string },\n error?: StripeError,\n}\n\nexport interface ConfirmResult {\n error?: StripeError,\n setupIntent?: unknown,\n paymentIntent?: unknown,\n}\n\nexport interface CreatePaymentMethodParams {\n type: 'card',\n card: StripeElementLike,\n billing_details?: Record<string, unknown>,\n}\n\nexport interface StripeLike {\n elements: () => StripeElementsLike,\n createPaymentMethod: (params: CreatePaymentMethodParams) => Promise<PaymentMethodResult>,\n confirmCardSetup: (clientSecret: string) => Promise<ConfirmResult>,\n confirmCardPayment: (clientSecret: string) => Promise<ConfirmResult>,\n}\n\ndeclare global {\n interface Window {\n Stripe?: (publishableKey: string) => StripeLike,\n }\n}\n\nexport interface TrialDateRange {\n /** e.g. \"June 22, 2026\" */\n displayStart: string,\n /** e.g. \"June 29, 2026\" */\n displayEnd: string,\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Status, Phase } from './constants';\n\nexport type { Status, Phase };\n\n// --- Bootstrap domain --------------------------------------------------------\nexport interface TwouCustomerContact {\n email?: string,\n}\n\nexport interface TwouCustomerParty {\n paName: string,\n indFirstName?: string,\n indLastName?: string,\n paContacts: { edges: { node: TwouCustomerContact }[] },\n}\n\nexport interface TwouCustomer {\n uid: string,\n parId: string,\n parParty: TwouCustomerParty,\n}\n\nexport type GqlRunner = (query: string, variables?: Record<string, unknown>, retry?: boolean) => Promise<any>;\n\n// --- Cart domain -------------------------------------------------------------\nexport interface Cart {\n biId: string,\n uid: string,\n}\n\nexport interface CartIds {\n customerUid: string,\n salableUid: string,\n billingPlanUid: string,\n}\n\nexport type BillingCycle = 'monthly' | 'annual';\n\nexport type PaymentMethod = 'card' | 'googlepay' | 'paypal';\n\nexport type BillingPeriod = 'MONTHLY' | 'ANNUAL' | 'QUARTERLY' | string;\n\nexport interface BillingPlanOwnershipModel {\n uid: string,\n omName: string,\n}\n\nexport interface BillingPlan {\n uid: string,\n stripePriceId: string,\n /** Price amount e.g. \"39.99\" */\n amount: string,\n /** Currency code e.g. \"USD\" */\n currency: string,\n isTemplate: boolean,\n ownershipModel: BillingPlanOwnershipModel,\n}\n\nexport interface SubscriptionDetails {\n biId: string, // \"BI301ZDAD6CMICG2\"\n trialStart: string | null,\n trialEnd: string | null,\n renewalType: string | null, // \"Annual\" | \"Monthly\"\n cardLast4: string | null, // \"4242\"\n cardBrand: string | null, // \"visa\" | \"mastercard\" etc\n stripePaymentMethodId: string | null,\n planName: string | null, // \"Monthly Subscription\"\n}\n\nexport interface CurrentUserSubscriberResult {\n isSubscriber: boolean,\n subscriptionBiId: string | null,\n status: string | null,\n subscriptionStartDate: string | null,\n currentPeriodEnd: string | null,\n}\n\n/** Course shown on checkout / post-checkout when arriving via ?course_run_key=. */\nexport interface CourseDetail {\n maId: string,\n maName: string,\n maDescription: string | null,\n tShortDescription: string | null,\n imageUrl: string | null,\n tCourseKey: string | null,\n tBrandText: string | null,\n tPrimarySubjectArea: string | null,\n tLevelType: string | null,\n tLanguage: 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 tUrlCourse: string | null,\n tB2cSubscriptionInclusion: boolean | null,\n tHasOfacRestrictions: boolean | null,\n tCatalogActive: boolean | null,\n}\n\n/** Returned by getBillingPlans — all available plans for the product. */\nexport interface BillingPlansResult {\n plans: BillingPlan[],\n}\n\n/** Returned by getSalableUid — the product uid and its ownership models. */\nexport interface ProductInfo {\n uid: string,\n maName: string,\n ownershipModels: { uid: string, omName: string }[],\n}\n\nexport interface CartStatusResult {\n status: Status | null,\n stripeSubscriptionId: string | null,\n /** Stripe-hosted challenge URL for the checkout confirmation step. */\n stripeConfirmationRedirectUrl: string | null,\n /** PayPal-hosted approval URL for the same checkout confirmation step. */\n paypalApprovalUrl: string | null,\n lastComment: string,\n}\n\nexport interface CartBillingAddress {\n tFirstName: string,\n tLastName: string,\n upaLine1: string,\n upaCity: string,\n upaPostcode: string,\n gadStateOrProvince: string,\n gadCountry: { iso2Code: string },\n}\n\nexport interface UpdateCartItemArgs {\n cartItemUid: string,\n billingAddress?: CartBillingAddress,\n paymentMethodId?: string,\n /** Payer email attached when the payment type is TwouPaypalPayment. */\n paypalEmail?: string | null,\n}\n\n/** Auto-renewal consent captured on TwouCart at checkout confirmation (SUBS-228). */\nexport interface CheckoutConsent {\n /** Main auto-renewal checkbox — always required at submit. */\n tConsentRecurring: boolean,\n /** UK/CA resident checkbox; null when that checkbox is not shown. */\n tConsentUkCa: boolean | null,\n /** ISO-8601 timestamp when consent was captured. */\n tConsentAt: string,\n /** Exact disclosure text shown to the learner (audit trail). */\n tConsentText: string,\n}\n\nexport interface UpdateCartConsentArgs {\n cartUid: string,\n consent: CheckoutConsent,\n /** Absolute checkout return URL stored in the backend's tStripeReturnUrl field. */\n returnUrl?: string | null,\n}\n\nexport interface BillingInfo {\n firstName: string,\n lastName: string,\n city: string,\n zip: string,\n country: string,\n line1: string,\n state: string,\n}\n\n// --- Checkout client surface -------------------------------------------------\nexport interface CheckoutClient {\n gql: GqlRunner,\n setToken: (token: string) => void,\n createCart: (vars: CartIds) => Promise<Cart>,\n getCartItem: (biId: string) => Promise<{ cartUid: string, cartItemUid: string }>,\n updateCartItem: (args: UpdateCartItemArgs) => Promise<void>,\n updateCartConsent: (args: UpdateCartConsentArgs) => Promise<void>,\n placeOrder: (cartUid: string) => Promise<unknown>,\n getCartStatus: (biId: string) => Promise<CartStatusResult>,\n findSubscriptionBiId: (biId: string) => Promise<SubscriptionDetails | null>,\n getSalableUid: (maId: string) => Promise<ProductInfo>,\n getBillingPlans: () => Promise<BillingPlansResult>,\n getActiveSubscriptions: () => Promise<{ active: number }>,\n isCurrentUserSubscriber: () => Promise<CurrentUserSubscriberResult | null>,\n getCourseDetail: (courseKey: string) => Promise<CourseDetail | null>,\n}\n\nexport interface DevAuth {\n iamUrl: string,\n clientId: string,\n clientSecret: string,\n}\n\n// --- Minimal Stripe.js types -------------------------------------------------\n// Just the surface this module uses. If you depend on `@stripe/stripe-js`,\n// you can replace these with its richer types.\nexport interface StripeElementChangeEvent {\n error?: { message?: string },\n [key: string]: unknown,\n}\n\nexport interface StripeElementLike {\n mount: (node: HTMLElement) => void,\n on: (event: string, handler: (e: StripeElementChangeEvent) => void) => void,\n unmount?: () => void,\n}\n\nexport interface StripeElementsLike {\n create: (type: string, options?: Record<string, unknown>) => StripeElementLike,\n}\n\nexport interface StripeError {\n message?: string,\n code?: string,\n}\n\nexport interface PaymentMethodResult {\n paymentMethod?: { id: string },\n error?: StripeError,\n}\n\nexport interface ConfirmResult {\n error?: StripeError,\n setupIntent?: unknown,\n paymentIntent?: unknown,\n}\n\nexport interface CreatePaymentMethodParams {\n type: 'card',\n card: StripeElementLike,\n billing_details?: Record<string, unknown>,\n}\n\nexport interface StripeLike {\n elements: () => StripeElementsLike,\n createPaymentMethod: (params: CreatePaymentMethodParams) => Promise<PaymentMethodResult>,\n confirmCardSetup: (clientSecret: string) => Promise<ConfirmResult>,\n confirmCardPayment: (clientSecret: string) => Promise<ConfirmResult>,\n}\n\ndeclare global {\n interface Window {\n Stripe?: (publishableKey: string) => StripeLike,\n }\n}\n\nexport interface TrialDateRange {\n /** e.g. \"June 22, 2026\" */\n displayStart: string,\n /** e.g. \"June 29, 2026\" */\n displayEnd: string,\n}\n"]}
@@ -6,9 +6,9 @@ export declare function getCartItem(gql: GqlRunner, biId: string): Promise<{
6
6
  cartUid: string;
7
7
  cartItemUid: string;
8
8
  }>;
9
- export declare function updateCartItem(gql: GqlRunner, { cartItemUid, billingAddress, paymentMethodId }: UpdateCartItemArgs): Promise<void>;
10
- /** Persist checkout consent checkboxes and the Stripe 3DS return URL onto the TwouCart BI. */
11
- export declare function updateCartConsent(gql: GqlRunner, { cartUid, consent, stripeReturnUrl }: UpdateCartConsentArgs): Promise<void>;
9
+ export declare function updateCartItem(gql: GqlRunner, { cartItemUid, billingAddress, paymentMethodId, paypalEmail }: UpdateCartItemArgs): Promise<void>;
10
+ /** Persist checkout consent checkboxes and the hosted-payment return URL onto the TwouCart BI. */
11
+ export declare function updateCartConsent(gql: GqlRunner, { cartUid, consent, returnUrl }: UpdateCartConsentArgs): Promise<void>;
12
12
  /** "Place order" — triggers the server-side workflow. */
13
13
  export declare function placeOrder(gql: GqlRunner, cartUid: string): Promise<unknown>;
14
14
  /** → current cart workflow status, Stripe details, and latest comment. */
@@ -40,7 +40,7 @@ export function getCartItem(gql, biId) {
40
40
  });
41
41
  }
42
42
  export function updateCartItem(gql_1, _a) {
43
- return __awaiter(this, arguments, void 0, function* (gql, { cartItemUid, billingAddress, paymentMethodId }) {
43
+ return __awaiter(this, arguments, void 0, function* (gql, { cartItemUid, billingAddress, paymentMethodId, paypalEmail }) {
44
44
  const input = { uid: cartItemUid };
45
45
  if (billingAddress)
46
46
  input.paybBillingAddress = Object.assign({ typeName: 'ViaxUrbanPropertyAddress' }, billingAddress);
@@ -53,12 +53,18 @@ export function updateCartItem(gql_1, _a) {
53
53
  _twouStripePayment: { tStripePaymentMethodId: paymentMethodId },
54
54
  };
55
55
  }
56
+ else if (paypalEmail !== undefined) {
57
+ input.paybPayment = {
58
+ typeName: 'TwouPaypalPayment',
59
+ _twouPaypalPayment: paypalEmail ? { tPaypalEmail: paypalEmail } : {},
60
+ };
61
+ }
56
62
  yield gql(UPDATE_CART_ITEM, { input });
57
63
  });
58
64
  }
59
- /** Persist checkout consent checkboxes and the Stripe 3DS return URL onto the TwouCart BI. */
65
+ /** Persist checkout consent checkboxes and the hosted-payment return URL onto the TwouCart BI. */
60
66
  export function updateCartConsent(gql_1, _a) {
61
- return __awaiter(this, arguments, void 0, function* (gql, { cartUid, consent, stripeReturnUrl }) {
67
+ return __awaiter(this, arguments, void 0, function* (gql, { cartUid, consent, returnUrl }) {
62
68
  const input = {
63
69
  uid: cartUid,
64
70
  tConsentRecurring: consent.tConsentRecurring,
@@ -66,8 +72,8 @@ export function updateCartConsent(gql_1, _a) {
66
72
  tConsentAt: consent.tConsentAt,
67
73
  tConsentText: consent.tConsentText,
68
74
  };
69
- if (stripeReturnUrl)
70
- input.tStripeReturnUrl = stripeReturnUrl;
75
+ if (returnUrl)
76
+ input.tStripeReturnUrl = returnUrl;
71
77
  yield gql(UPDATE_CART, { input });
72
78
  });
73
79
  }
@@ -86,7 +92,7 @@ export function placeOrder(gql, cartUid) {
86
92
  /** → current cart workflow status, Stripe details, and latest comment. */
87
93
  export function getCartStatus(gql, biId) {
88
94
  return __awaiter(this, void 0, void 0, function* () {
89
- var _a, _b, _c, _d, _e, _f, _g, _h;
95
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
90
96
  const data = yield gql(GET_CART_STATUS, { biId });
91
97
  const bi = (data === null || data === void 0 ? void 0 : data.getBusinessInteraction) || {};
92
98
  const comments = ((_b = (_a = bi.cmComments) === null || _a === void 0 ? void 0 : _a.edges) === null || _b === void 0 ? void 0 : _b.map((e) => e.node.comText)) || [];
@@ -94,6 +100,7 @@ export function getCartStatus(gql, biId) {
94
100
  status: ((_d = (_c = bi.bpStatus) === null || _c === void 0 ? void 0 : _c.code) !== null && _d !== void 0 ? _d : null),
95
101
  stripeSubscriptionId: (_g = (_f = (_e = bi.hiConsistsOf) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.tStripeSubscriptionId) !== null && _g !== void 0 ? _g : null,
96
102
  stripeConfirmationRedirectUrl: (_h = bi.tStripeConfirmationRedirectUrl) !== null && _h !== void 0 ? _h : null,
103
+ paypalApprovalUrl: (_j = bi.tPaypalApprovalUrl) !== null && _j !== void 0 ? _j : null,
97
104
  lastComment: comments.length ? comments[comments.length - 1] : '',
98
105
  };
99
106
  });
@@ -1 +1 @@
1
- {"version":3,"file":"cartApi.js","sourceRoot":"","sources":["../../src/utils/cartApi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/cartApi.ts — one function per checkout operation.\n * Each takes the `gql` runner as its first argument so they stay pure and\n * independently testable (mock `gql`, assert the variables).\n */\nimport {\n CREATE_CART, GET_CART_ITEM, UPDATE_CART_ITEM, UPDATE_CART,\n PLACE_ORDER, GET_CART_STATUS, FIND_SUBSCRIPTION,\n} from './queries';\nimport type {\n GqlRunner, Cart, CartIds, CartStatusResult, UpdateCartItemArgs,\n UpdateCartConsentArgs, Status, SubscriptionDetails,\n} from '../types';\n\n/** createTwouCart → { biId, uid } */\nexport async function createCart(gql: GqlRunner, { customerUid, salableUid, billingPlanUid }: CartIds): Promise<Cart> {\n const data = await gql(CREATE_CART, {\n input: {\n prPartyPrimary: { uid: customerUid },\n hiConsistsOf: [{ biiSalable: { uid: salableUid }, paybBillingPlan: { uid: billingPlanUid } }],\n },\n });\n return data.createTwouCart as Cart;\n}\n\n/** → { cartUid, cartItemUid } */\nexport async function getCartItem(gql: GqlRunner, biId: string): Promise<{ cartUid: string, cartItemUid: string }> {\n const data = await gql(GET_CART_ITEM, { biId });\n const bi = data.getBusinessInteraction;\n const cartUid = bi?.uid as string | undefined;\n const cartItemUid = bi?.hiConsistsOf?.[0]?.uid as string | undefined;\n\n if (!cartUid || !cartItemUid) {\n throw new Error(`Cart item not found for cart \"${biId}\".`);\n }\n\n return { cartUid, cartItemUid };\n}\n\nexport async function updateCartItem(\n gql: GqlRunner,\n { cartItemUid, billingAddress, paymentMethodId }: UpdateCartItemArgs,\n): Promise<void> {\n const input: Record<string, unknown> = { uid: cartItemUid };\n if (billingAddress) input.paybBillingAddress = { typeName: 'ViaxUrbanPropertyAddress', ...billingAddress };\n // paybPayment is the abstract CustomerPeriodicPaymentInput, which only declares\n // fields common to every payment type. Subtype fields such as\n // tStripePaymentMethodId go in the generated `_<subtype>` slot.\n if (paymentMethodId) {\n input.paybPayment = {\n typeName: 'TwouStripePayment',\n _twouStripePayment: { tStripePaymentMethodId: paymentMethodId },\n };\n }\n await gql(UPDATE_CART_ITEM, { input });\n}\n\n/** Persist checkout consent checkboxes and the Stripe 3DS return URL onto the TwouCart BI. */\nexport async function updateCartConsent(\n gql: GqlRunner,\n { cartUid, consent, stripeReturnUrl }: UpdateCartConsentArgs,\n): Promise<void> {\n const input: Record<string, unknown> = {\n uid: cartUid,\n tConsentRecurring: consent.tConsentRecurring,\n tConsentUkCa: consent.tConsentUkCa,\n tConsentAt: consent.tConsentAt,\n tConsentText: consent.tConsentText,\n };\n if (stripeReturnUrl) input.tStripeReturnUrl = stripeReturnUrl;\n await gql(UPDATE_CART, { input });\n}\n\n/** \"Place order\" — triggers the server-side workflow. */\nexport async function placeOrder(gql: GqlRunner, cartUid: string): Promise<unknown> {\n const data = await gql(PLACE_ORDER, {\n input: {\n businessProcessable: { uid: cartUid, typeName: 'TwouCart' },\n transitionCode: 'Sanction_Check',\n },\n });\n return data.startBusinessProcessableTransition;\n}\n\n/** → current cart workflow status, Stripe details, and latest comment. */\nexport async function getCartStatus(gql: GqlRunner, biId: string): Promise<CartStatusResult> {\n const data = await gql(GET_CART_STATUS, { biId });\n const bi = data?.getBusinessInteraction || {};\n const comments: string[] = bi.cmComments?.edges?.map((e: { node: { comText: string } }) => e.node.comText) || [];\n return {\n status: (bi.bpStatus?.code ?? null) as Status | null,\n stripeSubscriptionId: bi.hiConsistsOf?.[0]?.tStripeSubscriptionId ?? null,\n stripeConfirmationRedirectUrl: bi.tStripeConfirmationRedirectUrl ?? null,\n lastComment: comments.length ? comments[comments.length - 1] : '',\n };\n}\n\nexport async function findSubscriptionBiId(gql: GqlRunner, cartBiId: string): Promise<SubscriptionDetails | null> {\n try {\n const data = await gql(FIND_SUBSCRIPTION, { filter: `biRelatesTo.biId = '${cartBiId}'` });\n const node = data?.filterTwouSubscription?.edges?.[0]?.node;\n if (!node?.biId) return null;\n\n const item = node.hiConsistsOf?.[0];\n const payment = item?.paybPayment;\n\n return {\n biId: node.biId,\n // TwouSubscription exposes tTrialGranted / tTrialPeriod, not start/end dates.\n // Trial display dates are computed client-side via getTrialDateRange().\n trialStart: null,\n trialEnd: null,\n renewalType: node.tRenewalType ?? null,\n cardLast4: payment?.tPaymentMethodLast4 ?? null, // ← last four digits\n cardBrand: payment?.tPaymentMethodBrand ?? null, // ← card type (Visa, Mastercard etc)\n stripePaymentMethodId: payment?.tStripePaymentMethodId ?? null,\n planName: item?.paybBillingPlan?.bipOwnershipModel?.omName ?? null,\n };\n } catch {\n return null;\n }\n}\n"]}
1
+ {"version":3,"file":"cartApi.js","sourceRoot":"","sources":["../../src/utils/cartApi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/cartApi.ts — one function per checkout operation.\n * Each takes the `gql` runner as its first argument so they stay pure and\n * independently testable (mock `gql`, assert the variables).\n */\nimport {\n CREATE_CART, GET_CART_ITEM, UPDATE_CART_ITEM, UPDATE_CART,\n PLACE_ORDER, GET_CART_STATUS, FIND_SUBSCRIPTION,\n} from './queries';\nimport type {\n GqlRunner, Cart, CartIds, CartStatusResult, UpdateCartItemArgs,\n UpdateCartConsentArgs, Status, SubscriptionDetails,\n} from '../types';\n\n/** createTwouCart → { biId, uid } */\nexport async function createCart(gql: GqlRunner, { customerUid, salableUid, billingPlanUid }: CartIds): Promise<Cart> {\n const data = await gql(CREATE_CART, {\n input: {\n prPartyPrimary: { uid: customerUid },\n hiConsistsOf: [{ biiSalable: { uid: salableUid }, paybBillingPlan: { uid: billingPlanUid } }],\n },\n });\n return data.createTwouCart as Cart;\n}\n\n/** → { cartUid, cartItemUid } */\nexport async function getCartItem(gql: GqlRunner, biId: string): Promise<{ cartUid: string, cartItemUid: string }> {\n const data = await gql(GET_CART_ITEM, { biId });\n const bi = data.getBusinessInteraction;\n const cartUid = bi?.uid as string | undefined;\n const cartItemUid = bi?.hiConsistsOf?.[0]?.uid as string | undefined;\n\n if (!cartUid || !cartItemUid) {\n throw new Error(`Cart item not found for cart \"${biId}\".`);\n }\n\n return { cartUid, cartItemUid };\n}\n\nexport async function updateCartItem(\n gql: GqlRunner,\n { cartItemUid, billingAddress, paymentMethodId, paypalEmail }: UpdateCartItemArgs,\n): Promise<void> {\n const input: Record<string, unknown> = { uid: cartItemUid };\n if (billingAddress) input.paybBillingAddress = { typeName: 'ViaxUrbanPropertyAddress', ...billingAddress };\n // paybPayment is the abstract CustomerPeriodicPaymentInput, which only declares\n // fields common to every payment type. Subtype fields such as\n // tStripePaymentMethodId go in the generated `_<subtype>` slot.\n if (paymentMethodId) {\n input.paybPayment = {\n typeName: 'TwouStripePayment',\n _twouStripePayment: { tStripePaymentMethodId: paymentMethodId },\n };\n } else if (paypalEmail !== undefined) {\n input.paybPayment = {\n typeName: 'TwouPaypalPayment',\n _twouPaypalPayment: paypalEmail ? { tPaypalEmail: paypalEmail } : {},\n };\n }\n await gql(UPDATE_CART_ITEM, { input });\n}\n\n/** Persist checkout consent checkboxes and the hosted-payment return URL onto the TwouCart BI. */\nexport async function updateCartConsent(\n gql: GqlRunner,\n { cartUid, consent, returnUrl }: UpdateCartConsentArgs,\n): Promise<void> {\n const input: Record<string, unknown> = {\n uid: cartUid,\n tConsentRecurring: consent.tConsentRecurring,\n tConsentUkCa: consent.tConsentUkCa,\n tConsentAt: consent.tConsentAt,\n tConsentText: consent.tConsentText,\n };\n if (returnUrl) input.tStripeReturnUrl = returnUrl;\n await gql(UPDATE_CART, { input });\n}\n\n/** \"Place order\" — triggers the server-side workflow. */\nexport async function placeOrder(gql: GqlRunner, cartUid: string): Promise<unknown> {\n const data = await gql(PLACE_ORDER, {\n input: {\n businessProcessable: { uid: cartUid, typeName: 'TwouCart' },\n transitionCode: 'Sanction_Check',\n },\n });\n return data.startBusinessProcessableTransition;\n}\n\n/** → current cart workflow status, Stripe details, and latest comment. */\nexport async function getCartStatus(gql: GqlRunner, biId: string): Promise<CartStatusResult> {\n const data = await gql(GET_CART_STATUS, { biId });\n const bi = data?.getBusinessInteraction || {};\n const comments: string[] = bi.cmComments?.edges?.map((e: { node: { comText: string } }) => e.node.comText) || [];\n return {\n status: (bi.bpStatus?.code ?? null) as Status | null,\n stripeSubscriptionId: bi.hiConsistsOf?.[0]?.tStripeSubscriptionId ?? null,\n stripeConfirmationRedirectUrl: bi.tStripeConfirmationRedirectUrl ?? null,\n paypalApprovalUrl: bi.tPaypalApprovalUrl ?? null,\n lastComment: comments.length ? comments[comments.length - 1] : '',\n };\n}\n\nexport async function findSubscriptionBiId(gql: GqlRunner, cartBiId: string): Promise<SubscriptionDetails | null> {\n try {\n const data = await gql(FIND_SUBSCRIPTION, { filter: `biRelatesTo.biId = '${cartBiId}'` });\n const node = data?.filterTwouSubscription?.edges?.[0]?.node;\n if (!node?.biId) return null;\n\n const item = node.hiConsistsOf?.[0];\n const payment = item?.paybPayment;\n\n return {\n biId: node.biId,\n // TwouSubscription exposes tTrialGranted / tTrialPeriod, not start/end dates.\n // Trial display dates are computed client-side via getTrialDateRange().\n trialStart: null,\n trialEnd: null,\n renewalType: node.tRenewalType ?? null,\n cardLast4: payment?.tPaymentMethodLast4 ?? null, // ← last four digits\n cardBrand: payment?.tPaymentMethodBrand ?? null, // ← card type (Visa, Mastercard etc)\n stripePaymentMethodId: payment?.tStripePaymentMethodId ?? null,\n planName: item?.paybBillingPlan?.bipOwnershipModel?.omName ?? null,\n };\n } catch {\n return null;\n }\n}\n"]}
@@ -20,8 +20,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
20
20
  import { EnvironmentTypes, getSiteConfig } from '@openedx/frontend-base';
21
21
  /** Hardcoded edX JWT used only for local/dev checkout against stage Viax. */
22
22
  const DEV_SESSION_BODY = {
23
- payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIiwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc4ODQ2ODgyNywiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzg4NDY1MjI3LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJBRF8yNjA5XzJQMlMiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiI3ZjVjNzlkMzgxODVjMjRkNzY4Mzc2NGY5MGZkMDViMCIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1MTkwLCJlbWFpbCI6InhlcXhwZmVmeXlnbGFma2J3YkBvbmxkbS5uZXQiLCJuYW1lIjoiQWthc2ggRGV2Z2FuIChObyBzdWJzY3JpcHRpb24pIiwiZmFtaWx5X25hbWUiOiIiLCJnaXZlbl9uYW1lIjoiIiwiYWRtaW5pc3RyYXRvciI6ZmFsc2UsInN1cGVydXNlciI6ZmFsc2V9',
24
- signature: 'Fli51TGZL_7Om-llJFKLXYopa9Lc0XsRCw61ycoi2-0q3gGTlYTYXrLA7CZh3wHoj7ja8nQU4SL9P00azxrgHRrvEYzIh_nTYgOkvmcyiYLTGtdxx9Kq0DKstD2DQzMqm6j82TXwekmJXn51Rk331kTwEaPXqpAyXsQWkOkbMF1v5eTsJIP3RkYJm-Mweq26dMZf5t-VOTvgN0oRv8st_vvfGVUrJkNeSomCCyWUwLzpzQHm5lxUigPawRYe-MJMaBatGaQedw5OySKLy7sUW-h-JG3Vlg-n-YepOCjBVXtDGRXO_5oOHGzNT71li-ujOg69rWoCSxhuvVVEHsQq1w'
23
+ payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIiwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc4OTU3MzAyNCwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzg5NTY5NDI0LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJBXzI2MDlfUVhPTSIsInNjb3BlcyI6WyJ1c2VyX2lkIiwiZW1haWwiLCJwcm9maWxlIl0sInZlcnNpb24iOiIxLjIuMCIsInN1YiI6ImI1ODlhMTE3NWE4MjA0OGFlYjBmM2M0MWVjZGEzMGRhIiwiZmlsdGVycyI6WyJ1c2VyOm1lIl0sImlzX3Jlc3RyaWN0ZWQiOmZhbHNlLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInVzZXJfaWQiOjUyODU1MjQsImVtYWlsIjoieWlzZW5hNDA2MEBibG9iYXBwcy5jb20iLCJuYW1lIjoiQWthc2giLCJmYW1pbHlfbmFtZSI6IiIsImdpdmVuX25hbWUiOiIiLCJhZG1pbmlzdHJhdG9yIjpmYWxzZSwic3VwZXJ1c2VyIjpmYWxzZX0',
24
+ signature: 'F8ojgpjuxKYGWSPcCdZIxNnGQSC041wf8yHa-etQoCPaoT3eVMOLFcsxaA32ubEX-gLWU5ZqRYDn6mjbgyi78NHj-DAGU8oKTyo0p4G5nHKfTi0RhUpIK19m_g9OY-nR_Mf8eDBIOsu87I55KYrTmIj7eDE9j8MVO_UVmIfOfzmqS9ieGGxvKZVnq2FBfQ-HPfEHeuv5YKURJZ_hfRz2kIh0SZhYOZzfLUz4rYB87RqgbbxBWY0g10ybFogX1ZPAVWe5oDULgefmYL-TWjoiqFQpVk4xi1tzhOM3P8tzZDFORRT9jxWNEMnzYPu7QYJliHlD71og1_gZPCWMSXUwRw'
25
25
  };
26
26
  function parseSessionResponse(data) {
27
27
  var _a, _b, _c, _d;
@@ -1 +1 @@
1
- {"version":3,"file":"provision.js","sourceRoot":"","sources":["../../src/utils/provision.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/provision.ts — exchange the edX session for a viax token.\n *\n * {LMS_BASE_URL}/api/edx-session\n * The LMS reads the edX JWT from browser cookies, exchanges it with viax,\n * and returns a per-user token for authenticated API calls.\n *\n * Dev uses POST with a hardcoded JWT payload (local LMS cookie exchange is\n * unavailable). Stage and prod use GET and rely on the browser session cookies.\n */\n\nimport { EnvironmentTypes, getSiteConfig } from '@openedx/frontend-base';\n\nexport interface EdxSessionResult {\n viaxToken: string, // raw JWT — add 'Bearer ' prefix before use\n customerUid: string,\n partyUid?: string,\n edxUserId?: string,\n username?: string,\n email?: string,\n}\n\n/** Hardcoded edX JWT used only for local/dev checkout against stage Viax. */\nconst DEV_SESSION_BODY = {\n payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIiwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc4ODQ2ODgyNywiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzg4NDY1MjI3LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJBRF8yNjA5XzJQMlMiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiI3ZjVjNzlkMzgxODVjMjRkNzY4Mzc2NGY5MGZkMDViMCIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1MTkwLCJlbWFpbCI6InhlcXhwZmVmeXlnbGFma2J3YkBvbmxkbS5uZXQiLCJuYW1lIjoiQWthc2ggRGV2Z2FuIChObyBzdWJzY3JpcHRpb24pIiwiZmFtaWx5X25hbWUiOiIiLCJnaXZlbl9uYW1lIjoiIiwiYWRtaW5pc3RyYXRvciI6ZmFsc2UsInN1cGVydXNlciI6ZmFsc2V9',\n signature: 'Fli51TGZL_7Om-llJFKLXYopa9Lc0XsRCw61ycoi2-0q3gGTlYTYXrLA7CZh3wHoj7ja8nQU4SL9P00azxrgHRrvEYzIh_nTYgOkvmcyiYLTGtdxx9Kq0DKstD2DQzMqm6j82TXwekmJXn51Rk331kTwEaPXqpAyXsQWkOkbMF1v5eTsJIP3RkYJm-Mweq26dMZf5t-VOTvgN0oRv8st_vvfGVUrJkNeSomCCyWUwLzpzQHm5lxUigPawRYe-MJMaBatGaQedw5OySKLy7sUW-h-JG3Vlg-n-YepOCjBVXtDGRXO_5oOHGzNT71li-ujOg69rWoCSxhuvVVEHsQq1w'\n};\nfunction parseSessionResponse(data: Record<string, unknown>): EdxSessionResult {\n const viaxToken = (\n data.viaxToken ?? data.token ?? data.access_token ?? data.accessToken\n ) as string | undefined;\n const customerUid = (data.customerUid ?? data.customer_uid) as string | undefined;\n\n if (!viaxToken) throw new Error('Session response missing viax token.');\n if (!customerUid) throw new Error('Session response missing customerUid.');\n\n return {\n viaxToken,\n customerUid,\n partyUid: data.partyUid as string | undefined,\n edxUserId: data.edxUserId as string | undefined,\n username: data.username as string | undefined,\n email: data.email as string | undefined,\n };\n}\n\n/**\n * Fetch the ISO 3166-1 alpha-2 country code from the user's edX account profile.\n * Returns null if the request fails or the field is absent.\n */\nexport async function fetchUserCountry(lmsBaseUrl: string, username: string): Promise<string | null> {\n try {\n const res = await fetch(`${lmsBaseUrl}/api/user/v1/accounts/${encodeURIComponent(username)}`, {\n credentials: 'include',\n });\n if (!res.ok) return null;\n const data = await res.json() as Record<string, unknown>;\n return typeof data.country === 'string' && data.country ? data.country : null;\n } catch {\n return null;\n }\n}\n\nfunction isDevelopment(): boolean {\n return getSiteConfig().environment === EnvironmentTypes.DEVELOPMENT;\n}\n\n/** Exchange the learner's edX session (cookies) for a viax token. */\nexport async function fetchEdxSession(sessionUrl: string): Promise<EdxSessionResult> {\n const headers: Record<string, string> = { Accept: 'application/json' };\n\n let res: Response;\n if (isDevelopment()) {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers: {\n ...headers,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(DEV_SESSION_BODY),\n });\n } else {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers,\n });\n }\n\n if (!res.ok) {\n throw new Error(\n `Failed to establish checkout session (${res.status}). Please log into edX and try again.`,\n );\n }\n\n const data = await res.json() as Record<string, unknown> & { error?: string };\n if (data.error) throw new Error(String(data.error));\n\n return parseSessionResponse(data);\n}\n"]}
1
+ {"version":3,"file":"provision.js","sourceRoot":"","sources":["../../src/utils/provision.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/provision.ts — exchange the edX session for a viax token.\n *\n * {LMS_BASE_URL}/api/edx-session\n * The LMS reads the edX JWT from browser cookies, exchanges it with viax,\n * and returns a per-user token for authenticated API calls.\n *\n * Dev uses POST with a hardcoded JWT payload (local LMS cookie exchange is\n * unavailable). Stage and prod use GET and rely on the browser session cookies.\n */\n\nimport { EnvironmentTypes, getSiteConfig } from '@openedx/frontend-base';\n\nexport interface EdxSessionResult {\n viaxToken: string, // raw JWT — add 'Bearer ' prefix before use\n customerUid: string,\n partyUid?: string,\n edxUserId?: string,\n username?: string,\n email?: string,\n}\n\n/** Hardcoded edX JWT used only for local/dev checkout against stage Viax. */\nconst DEV_SESSION_BODY = {\n payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIiwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc4OTU3MzAyNCwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzg5NTY5NDI0LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJBXzI2MDlfUVhPTSIsInNjb3BlcyI6WyJ1c2VyX2lkIiwiZW1haWwiLCJwcm9maWxlIl0sInZlcnNpb24iOiIxLjIuMCIsInN1YiI6ImI1ODlhMTE3NWE4MjA0OGFlYjBmM2M0MWVjZGEzMGRhIiwiZmlsdGVycyI6WyJ1c2VyOm1lIl0sImlzX3Jlc3RyaWN0ZWQiOmZhbHNlLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInVzZXJfaWQiOjUyODU1MjQsImVtYWlsIjoieWlzZW5hNDA2MEBibG9iYXBwcy5jb20iLCJuYW1lIjoiQWthc2giLCJmYW1pbHlfbmFtZSI6IiIsImdpdmVuX25hbWUiOiIiLCJhZG1pbmlzdHJhdG9yIjpmYWxzZSwic3VwZXJ1c2VyIjpmYWxzZX0',\n signature: 'F8ojgpjuxKYGWSPcCdZIxNnGQSC041wf8yHa-etQoCPaoT3eVMOLFcsxaA32ubEX-gLWU5ZqRYDn6mjbgyi78NHj-DAGU8oKTyo0p4G5nHKfTi0RhUpIK19m_g9OY-nR_Mf8eDBIOsu87I55KYrTmIj7eDE9j8MVO_UVmIfOfzmqS9ieGGxvKZVnq2FBfQ-HPfEHeuv5YKURJZ_hfRz2kIh0SZhYOZzfLUz4rYB87RqgbbxBWY0g10ybFogX1ZPAVWe5oDULgefmYL-TWjoiqFQpVk4xi1tzhOM3P8tzZDFORRT9jxWNEMnzYPu7QYJliHlD71og1_gZPCWMSXUwRw'\n};\nfunction parseSessionResponse(data: Record<string, unknown>): EdxSessionResult {\n const viaxToken = (\n data.viaxToken ?? data.token ?? data.access_token ?? data.accessToken\n ) as string | undefined;\n const customerUid = (data.customerUid ?? data.customer_uid) as string | undefined;\n\n if (!viaxToken) throw new Error('Session response missing viax token.');\n if (!customerUid) throw new Error('Session response missing customerUid.');\n\n return {\n viaxToken,\n customerUid,\n partyUid: data.partyUid as string | undefined,\n edxUserId: data.edxUserId as string | undefined,\n username: data.username as string | undefined,\n email: data.email as string | undefined,\n };\n}\n\n/**\n * Fetch the ISO 3166-1 alpha-2 country code from the user's edX account profile.\n * Returns null if the request fails or the field is absent.\n */\nexport async function fetchUserCountry(lmsBaseUrl: string, username: string): Promise<string | null> {\n try {\n const res = await fetch(`${lmsBaseUrl}/api/user/v1/accounts/${encodeURIComponent(username)}`, {\n credentials: 'include',\n });\n if (!res.ok) return null;\n const data = await res.json() as Record<string, unknown>;\n return typeof data.country === 'string' && data.country ? data.country : null;\n } catch {\n return null;\n }\n}\n\nfunction isDevelopment(): boolean {\n return getSiteConfig().environment === EnvironmentTypes.DEVELOPMENT;\n}\n\n/** Exchange the learner's edX session (cookies) for a viax token. */\nexport async function fetchEdxSession(sessionUrl: string): Promise<EdxSessionResult> {\n const headers: Record<string, string> = { Accept: 'application/json' };\n\n let res: Response;\n if (isDevelopment()) {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers: {\n ...headers,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(DEV_SESSION_BODY),\n });\n } else {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers,\n });\n }\n\n if (!res.ok) {\n throw new Error(\n `Failed to establish checkout session (${res.status}). Please log into edX and try again.`,\n );\n }\n\n const data = await res.json() as Record<string, unknown> & { error?: string };\n if (data.error) throw new Error(String(data.error));\n\n return parseSessionResponse(data);\n}\n"]}
@@ -19,7 +19,7 @@ export declare const UPDATE_CART = "mutation UpdateCart($input: TwouCartInput!)
19
19
  /** Triggers the backend workflow to place the order. */
20
20
  export declare const PLACE_ORDER = "mutation PlaceOrder($input: StartBusinessProcessableTransitionInput!) {\n startBusinessProcessableTransition(input: $input) { uid bpStatus { code } }\n}";
21
21
  export declare const GET_CART_ITEM = "query GetCartItem($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n uid\n hiConsistsOf { ... on TwouCartItem { uid } }\n }\n}";
22
- export declare const GET_CART_STATUS = "query GetCartStatus($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n bpStatus { code }\n hiConsistsOf { ... on TwouCartItem { tStripeSubscriptionId } }\n ... on TwouCart {\n tStripeConfirmationRedirectUrl\n cmComments { edges { node { comText } } }\n }\n }\n}";
22
+ export declare const GET_CART_STATUS = "query GetCartStatus($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n bpStatus { code }\n hiConsistsOf { ... on TwouCartItem { tStripeSubscriptionId } }\n ... on TwouCart {\n tStripeConfirmationRedirectUrl\n tPaypalApprovalUrl\n cmComments { edges { node { comText } } }\n }\n }\n}";
23
23
  export declare const FIND_SUBSCRIPTION = "query FindSubscription($filter: String!) {\n filterTwouSubscription(_first: 1, _filter: $filter) {\n edges {\n node {\n biId\n tRenewalType\n hiConsistsOf {\n ... on TwouSubscriptionItem {\n paybBillingPlan {\n tStripePriceId\n bipOwnershipModel { omName }\n }\n paybPayment {\n ... on TwouStripePayment {\n tStripePaymentMethodId\n tPaymentMethodLast4\n tPaymentMethodBrand\n }\n }\n }\n }\n }\n }\n }\n}";
24
24
  /** Fetch the viax customer uid by email using filterTwouCustomer. */
25
25
  export declare const GET_CUSTOMER = "query GetCustomer($filter: String!) {\n filterTwouCustomer(_first: 1, _filter: $filter) {\n edges {\n node {\n uid\n parId\n parParty {\n paName\n paContacts {\n edges {\n node {\n ... on EmailAddressContactMedium {\n email\n }\n }\n }\n }\n ... on Individual {\n indFirstName\n indLastName\n }\n }\n }\n }\n }\n}";
@@ -40,6 +40,7 @@ export const GET_CART_STATUS = `query GetCartStatus($biId: String!) {
40
40
  hiConsistsOf { ... on TwouCartItem { tStripeSubscriptionId } }
41
41
  ... on TwouCart {
42
42
  tStripeConfirmationRedirectUrl
43
+ tPaypalApprovalUrl
43
44
  cmComments { edges { node { comText } } }
44
45
  }
45
46
  }
@@ -1 +1 @@
1
- {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/utils/queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,iFAAiF;AAEjF,0CAA0C;AAC1C,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH,kEAAkE;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAG;;EAE9B,CAAC;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH,iFAAiF;AAEjF,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;EAK3B,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;EAS7B,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;EAwB/B,CAAC;AAEH,qEAAqE;AACrE,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;EAyB1B,CAAC;AAEH,iFAAiF;AACjF,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;EAWzB,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;EAe/B,CAAC;AAEH,kEAAkE;AAClE,MAAM,CAAC,MAAM,8BAA8B,GAAG;;EAE5C,CAAC;AAEH;0GAC0G;AAC1G,MAAM,CAAC,MAAM,8BAA8B,GAAG;;EAE5C,CAAC;AAEH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4B/B,CAAC","sourcesContent":["/**\n * api/queries.ts — GraphQL documents for the checkout flow.\n * Kept as plain strings so they can be reused, logged, or tested in isolation.\n *\n * Mutation reference (from API doc):\n * updateTwouCustomer — toggle Stripe test clock on/off\n * createTwouCart — user proceeds to payment\n * updateTwouCartItem — attach billing plan / payment method to cart\n * updateTwouCart — persist auto-renewal consent (SUBS-228)\n * startBusinessProcessableTransition — place order\n * updateTwouCartItem — retry: attach new payment method\n */\n\n// ─── Mutations ────────────────────────────────────────────────────────────────\n\n/** Creates a new cart during checkout. */\nexport const CREATE_CART = `mutation CreateCart($input: TwouCartInput!) {\n createTwouCart(input: $input) { biId uid }\n}`;\n\n/** Updates a cart item — attach billing plan / payment method. */\nexport const UPDATE_CART_ITEM = `mutation UpdateCartItem($input: TwouCartItemInput!) {\n updateTwouCartItem(input: $input) { uid }\n}`;\n\n/** Persists auto-renewal consent fields on the TwouCart BI (SUBS-228). */\nexport const UPDATE_CART = `mutation UpdateCart($input: TwouCartInput!) {\n updateTwouCart(input: $input) { uid }\n}`;\n\n/** Triggers the backend workflow to place the order. */\nexport const PLACE_ORDER = `mutation PlaceOrder($input: StartBusinessProcessableTransitionInput!) {\n startBusinessProcessableTransition(input: $input) { uid bpStatus { code } }\n}`;\n\n// ─── Queries ──────────────────────────────────────────────────────────────────\n\nexport const GET_CART_ITEM = `query GetCartItem($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n uid\n hiConsistsOf { ... on TwouCartItem { uid } }\n }\n}`;\n\nexport const GET_CART_STATUS = `query GetCartStatus($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n bpStatus { code }\n hiConsistsOf { ... on TwouCartItem { tStripeSubscriptionId } }\n ... on TwouCart {\n tStripeConfirmationRedirectUrl\n cmComments { edges { node { comText } } }\n }\n }\n}`;\n\nexport const FIND_SUBSCRIPTION = `query FindSubscription($filter: String!) {\n filterTwouSubscription(_first: 1, _filter: $filter) {\n edges {\n node {\n biId\n tRenewalType\n hiConsistsOf {\n ... on TwouSubscriptionItem {\n paybBillingPlan {\n tStripePriceId\n bipOwnershipModel { omName }\n }\n paybPayment {\n ... on TwouStripePayment {\n tStripePaymentMethodId\n tPaymentMethodLast4\n tPaymentMethodBrand\n }\n }\n }\n }\n }\n }\n }\n}`;\n\n/** Fetch the viax customer uid by email using filterTwouCustomer. */\nexport const GET_CUSTOMER = `query GetCustomer($filter: String!) {\n filterTwouCustomer(_first: 1, _filter: $filter) {\n edges {\n node {\n uid\n parId\n parParty {\n paName\n paContacts {\n edges {\n node {\n ... on EmailAddressContactMedium {\n email\n }\n }\n }\n }\n ... on Individual {\n indFirstName\n indLastName\n }\n }\n }\n }\n }\n}`;\n\n/** Fetch the TwouSubscriptionPlan uid to use as biiSalable in createTwouCart. */\nexport const GET_PRODUCT = `query GetProduct($maId: String!) {\n getProduct(maId: $maId) {\n maName\n uid\n ... on TwouSubscriptionPlan {\n saOwnershipModels {\n uid\n omName\n }\n }\n }\n}`;\n\nexport const GET_BILLING_PLANS = `query GetBillingPlans {\n filterCustomerBillingPlan(_first: 10) {\n edges {\n node {\n uid\n tStripePriceId\n tStripeAmount {\n amount\n units { code }\n }\n bipIsTemplate\n bipOwnershipModel { uid omName }\n }\n }\n }\n}`;\n\n/** Fetch the number of active subscriptions — unauthenticated. */\nexport const GET_ACTIVE_SUBSCRIPTIONS_STATE = `query activeSubscriptions {\n tActiveSubscriptionsState\n}`;\n\n/** Check whether the currently authenticated user already has an active subscription.\n * tIsCurrentUserSubscriber is a JSON scalar — returns the full object, no subfield selection allowed. */\nexport const GET_IS_CURRENT_USER_SUBSCRIBER = `query IsCurrentUserSubscriber {\n tIsCurrentUserSubscriber\n}`;\n\n/** Fetch a course for the enrollment card when ?course_run_key= is present. */\nexport const GET_COURSE_DETAIL = `query CourseDetail($filter: String!) {\n filterTwouCourse(_first: 1, _filter: $filter) {\n edges {\n node {\n maId\n maName\n maDescription\n tShortDescription\n imageUrl\n tCourseKey\n tBrandText\n tPrimarySubjectArea\n tLevelType\n tLanguage\n tPacingType { code }\n tDurationLow\n tDurationHigh\n tDurationUnit { code }\n tEffortLow\n tEffortHigh\n tEffortUnit { code }\n tUrlCourse\n tB2cSubscriptionInclusion\n tHasOfacRestrictions\n tCatalogActive\n }\n }\n }\n}`;\n"]}
1
+ {"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/utils/queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,iFAAiF;AAEjF,0CAA0C;AAC1C,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH,kEAAkE;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAG;;EAE9B,CAAC;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH,iFAAiF;AAEjF,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;EAK3B,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;EAU7B,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;EAwB/B,CAAC;AAEH,qEAAqE;AACrE,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;EAyB1B,CAAC;AAEH,iFAAiF;AACjF,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;EAWzB,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;EAe/B,CAAC;AAEH,kEAAkE;AAClE,MAAM,CAAC,MAAM,8BAA8B,GAAG;;EAE5C,CAAC;AAEH;0GAC0G;AAC1G,MAAM,CAAC,MAAM,8BAA8B,GAAG;;EAE5C,CAAC;AAEH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4B/B,CAAC","sourcesContent":["/**\n * api/queries.ts — GraphQL documents for the checkout flow.\n * Kept as plain strings so they can be reused, logged, or tested in isolation.\n *\n * Mutation reference (from API doc):\n * updateTwouCustomer — toggle Stripe test clock on/off\n * createTwouCart — user proceeds to payment\n * updateTwouCartItem — attach billing plan / payment method to cart\n * updateTwouCart — persist auto-renewal consent (SUBS-228)\n * startBusinessProcessableTransition — place order\n * updateTwouCartItem — retry: attach new payment method\n */\n\n// ─── Mutations ────────────────────────────────────────────────────────────────\n\n/** Creates a new cart during checkout. */\nexport const CREATE_CART = `mutation CreateCart($input: TwouCartInput!) {\n createTwouCart(input: $input) { biId uid }\n}`;\n\n/** Updates a cart item — attach billing plan / payment method. */\nexport const UPDATE_CART_ITEM = `mutation UpdateCartItem($input: TwouCartItemInput!) {\n updateTwouCartItem(input: $input) { uid }\n}`;\n\n/** Persists auto-renewal consent fields on the TwouCart BI (SUBS-228). */\nexport const UPDATE_CART = `mutation UpdateCart($input: TwouCartInput!) {\n updateTwouCart(input: $input) { uid }\n}`;\n\n/** Triggers the backend workflow to place the order. */\nexport const PLACE_ORDER = `mutation PlaceOrder($input: StartBusinessProcessableTransitionInput!) {\n startBusinessProcessableTransition(input: $input) { uid bpStatus { code } }\n}`;\n\n// ─── Queries ──────────────────────────────────────────────────────────────────\n\nexport const GET_CART_ITEM = `query GetCartItem($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n uid\n hiConsistsOf { ... on TwouCartItem { uid } }\n }\n}`;\n\nexport const GET_CART_STATUS = `query GetCartStatus($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n bpStatus { code }\n hiConsistsOf { ... on TwouCartItem { tStripeSubscriptionId } }\n ... on TwouCart {\n tStripeConfirmationRedirectUrl\n tPaypalApprovalUrl\n cmComments { edges { node { comText } } }\n }\n }\n}`;\n\nexport const FIND_SUBSCRIPTION = `query FindSubscription($filter: String!) {\n filterTwouSubscription(_first: 1, _filter: $filter) {\n edges {\n node {\n biId\n tRenewalType\n hiConsistsOf {\n ... on TwouSubscriptionItem {\n paybBillingPlan {\n tStripePriceId\n bipOwnershipModel { omName }\n }\n paybPayment {\n ... on TwouStripePayment {\n tStripePaymentMethodId\n tPaymentMethodLast4\n tPaymentMethodBrand\n }\n }\n }\n }\n }\n }\n }\n}`;\n\n/** Fetch the viax customer uid by email using filterTwouCustomer. */\nexport const GET_CUSTOMER = `query GetCustomer($filter: String!) {\n filterTwouCustomer(_first: 1, _filter: $filter) {\n edges {\n node {\n uid\n parId\n parParty {\n paName\n paContacts {\n edges {\n node {\n ... on EmailAddressContactMedium {\n email\n }\n }\n }\n }\n ... on Individual {\n indFirstName\n indLastName\n }\n }\n }\n }\n }\n}`;\n\n/** Fetch the TwouSubscriptionPlan uid to use as biiSalable in createTwouCart. */\nexport const GET_PRODUCT = `query GetProduct($maId: String!) {\n getProduct(maId: $maId) {\n maName\n uid\n ... on TwouSubscriptionPlan {\n saOwnershipModels {\n uid\n omName\n }\n }\n }\n}`;\n\nexport const GET_BILLING_PLANS = `query GetBillingPlans {\n filterCustomerBillingPlan(_first: 10) {\n edges {\n node {\n uid\n tStripePriceId\n tStripeAmount {\n amount\n units { code }\n }\n bipIsTemplate\n bipOwnershipModel { uid omName }\n }\n }\n }\n}`;\n\n/** Fetch the number of active subscriptions — unauthenticated. */\nexport const GET_ACTIVE_SUBSCRIPTIONS_STATE = `query activeSubscriptions {\n tActiveSubscriptionsState\n}`;\n\n/** Check whether the currently authenticated user already has an active subscription.\n * tIsCurrentUserSubscriber is a JSON scalar — returns the full object, no subfield selection allowed. */\nexport const GET_IS_CURRENT_USER_SUBSCRIBER = `query IsCurrentUserSubscriber {\n tIsCurrentUserSubscriber\n}`;\n\n/** Fetch a course for the enrollment card when ?course_run_key= is present. */\nexport const GET_COURSE_DETAIL = `query CourseDetail($filter: String!) {\n filterTwouCourse(_first: 1, _filter: $filter) {\n edges {\n node {\n maId\n maName\n maDescription\n tShortDescription\n imageUrl\n tCourseKey\n tBrandText\n tPrimarySubjectArea\n tLevelType\n tLanguage\n tPacingType { code }\n tDurationLow\n tDurationHigh\n tDurationUnit { code }\n tEffortLow\n tEffortHigh\n tEffortUnit { code }\n tUrlCourse\n tB2cSubscriptionInclusion\n tHasOfacRestrictions\n tCatalogActive\n }\n }\n }\n}`;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edx/frontend-app-subscription-checkout",
3
- "version": "1.12.0",
3
+ "version": "1.14.0",
4
4
  "description": "Subscription Checkout Frontend application",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,3 +0,0 @@
1
- import { App } from '@openedx/frontend-base';
2
- declare const app: App;
3
- export default app;
@@ -1,19 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { LinkMenuItem, WidgetOperationTypes, } from '@openedx/frontend-base';
3
- import { subscriptionCheckoutRole } from '../../constants';
4
- const app = {
5
- appId: 'org.openedx.frontend.app.template.exampleHeader',
6
- slots: [
7
- {
8
- slotId: 'org.openedx.frontend.slot.header.primaryLinks.v1',
9
- id: 'org.openedx.frontend.widget.template.exampleHeaderLink.v1',
10
- op: WidgetOperationTypes.APPEND,
11
- element: (_jsx(LinkMenuItem, { label: "Example Menu", role: subscriptionCheckoutRole, variant: "navLink" })),
12
- condition: {
13
- active: [subscriptionCheckoutRole],
14
- },
15
- },
16
- ],
17
- };
18
- export default app;
19
- //# sourceMappingURL=app.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../../../src/widgets/ExampleHeader/app.tsx"],"names":[],"mappings":";AAAA,OAAO,EAEL,YAAY,EACZ,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,MAAM,GAAG,GAAQ;IACf,KAAK,EAAE,iDAAiD;IACxD,KAAK,EAAE;QACL;YACE,MAAM,EAAE,kDAAkD;YAC1D,EAAE,EAAE,2DAA2D;YAC/D,EAAE,EAAE,oBAAoB,CAAC,MAAM;YAC/B,OAAO,EAAE,CACP,KAAC,YAAY,IACX,KAAK,EAAC,cAAc,EACpB,IAAI,EAAE,wBAAwB,EAC9B,OAAO,EAAC,SAAS,GACjB,CACH;YACD,SAAS,EAAE;gBACT,MAAM,EAAE,CAAC,wBAAwB,CAAC;aACnC;SACF;KACF;CACF,CAAC;AAEF,eAAe,GAAG,CAAC","sourcesContent":["import {\n App,\n LinkMenuItem,\n WidgetOperationTypes,\n} from '@openedx/frontend-base';\n\nimport { subscriptionCheckoutRole } from '../../constants';\n\nconst app: App = {\n appId: 'org.openedx.frontend.app.template.exampleHeader',\n slots: [\n {\n slotId: 'org.openedx.frontend.slot.header.primaryLinks.v1',\n id: 'org.openedx.frontend.widget.template.exampleHeaderLink.v1',\n op: WidgetOperationTypes.APPEND,\n element: (\n <LinkMenuItem\n label=\"Example Menu\"\n role={subscriptionCheckoutRole}\n variant=\"navLink\"\n />\n ),\n condition: {\n active: [subscriptionCheckoutRole],\n },\n },\n ],\n};\n\nexport default app;\n"]}
@@ -1 +0,0 @@
1
- export { default as exampleHeaderApp } from './app';
@@ -1,2 +0,0 @@
1
- export { default as exampleHeaderApp } from './app';
2
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/widgets/ExampleHeader/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,OAAO,CAAC","sourcesContent":["export { default as exampleHeaderApp } from './app';\n"]}