@edx/frontend-app-subscription-checkout 1.11.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CartSummary/CartSummary.js +7 -2
- package/dist/components/CartSummary/CartSummary.js.map +1 -1
- package/dist/components/CartSummary/Messages.d.ts +5 -0
- package/dist/components/CartSummary/Messages.js +6 -1
- package/dist/components/CartSummary/Messages.js.map +1 -1
- package/dist/components/Checkout/Checkout.js +17 -8
- package/dist/components/Checkout/Checkout.js.map +1 -1
- package/dist/components/EnrollmentSummary/EnrollmentSummary.js +7 -1
- package/dist/components/EnrollmentSummary/EnrollmentSummary.js.map +1 -1
- package/dist/components/EnrollmentSummary/Messages.d.ts +5 -0
- package/dist/components/EnrollmentSummary/Messages.js +8 -3
- package/dist/components/EnrollmentSummary/Messages.js.map +1 -1
- package/dist/components/PaymentForm/Messages.d.ts +10 -0
- package/dist/components/PaymentForm/Messages.js +13 -3
- package/dist/components/PaymentForm/Messages.js.map +1 -1
- package/dist/components/PaymentForm/PaymentForm.d.ts +2 -2
- package/dist/components/PaymentForm/PaymentForm.js +29 -20
- package/dist/components/PaymentForm/PaymentForm.js.map +1 -1
- package/dist/components/StatusScreens/Messages.d.ts +5 -0
- package/dist/components/StatusScreens/Messages.js +6 -0
- package/dist/components/StatusScreens/Messages.js.map +1 -1
- package/dist/components/StatusScreens/StatusScreens.d.ts +1 -0
- package/dist/components/StatusScreens/StatusScreens.js +8 -1
- package/dist/components/StatusScreens/StatusScreens.js.map +1 -1
- package/dist/data/hooks/useCheckout.d.ts +6 -2
- package/dist/data/hooks/useCheckout.js +45 -13
- package/dist/data/hooks/useCheckout.js.map +1 -1
- package/dist/data/hooks/useCheckoutMutations.d.ts +6 -2
- package/dist/data/hooks/useCheckoutMutations.js +62 -31
- package/dist/data/hooks/useCheckoutMutations.js.map +1 -1
- package/dist/routes.js +2 -2
- package/dist/routes.js.map +1 -1
- package/dist/styles/components/statusScreens.scss +10 -0
- package/dist/types.d.ts +9 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/cartApi.d.ts +4 -4
- package/dist/utils/cartApi.js +26 -14
- package/dist/utils/cartApi.js.map +1 -1
- package/dist/utils/provision.js +2 -2
- package/dist/utils/provision.js.map +1 -1
- package/dist/utils/queries.d.ts +1 -1
- package/dist/utils/queries.js +2 -0
- package/dist/utils/queries.js.map +1 -1
- package/dist/utils/stripe.d.ts +16 -0
- package/dist/utils/stripe.js +43 -0
- package/dist/utils/stripe.js.map +1 -1
- package/package.json +1 -1
|
@@ -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 { 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\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}\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 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 } = 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 const [processingSubscription, setProcessingSubscription] = useState(false);\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 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 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 const statusQuery = useCartStatusQuery(client, cart?.biId ?? null);\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 = cart ? phaseFor(status, status === STATUS.IN_CART) : 'ready';\n const subscriptionIdQuery = useSubscriptionIdQuery(\n client, cart?.biId ?? null, 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 // --- actions ---\n const placeOrder = useCallback((consent: CheckoutConsent) => {\n setError('');\n setCardError('');\n if (phase === 'failed') setCart(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 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":";;;;;;;;;AAAA,OAAO,EACL,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,GAClD,MAAM,OAAO,CAAC;AAGf,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,0BAA0B,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,MAAM,EAAE,aAAa,EAAE,mBAAmB,EAAE,QAAQ,EAAE,QAAQ,GAC/D,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EACL,kBAAkB,EAAE,sBAAsB,EAAE,oBAAoB,EAChE,2BAA2B,EAAE,mBAAmB,EAAE,oBAAoB,GACvE,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE9D;;;;GAIG;AACH,SAAS,sBAAsB;IAC7B,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAC/C,MAAM,KAAK,GAAG,4BAA4B,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,CAAC;QACH,iEAAiE;QACjE,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;IAC3E,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;IACjC,CAAC;AACH,CAAC;AAED,MAAM,eAAe,GAAG,CAAC,GAAW,EAAQ,EAAE;IAC5C,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC9B,CAAC,CAAC;AA6DF,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,UAA8B,EAAE;;IAClE,MAAM,EACJ,MAAM,GAAG,QAAQ,CAAC,MAAM,EACxB,aAAa,EACb,oBAAoB,EACpB,UAAU,EACV,WAAW,GAAG,IAAI,EAClB,YAAY,EAAE,gBAAgB,EAC9B,aAAa,GAAG,eAAe,GAChC,GAAG,OAAO,CAAC;IAEZ,MAAM,YAAY,GAAG,OAAO,CAC1B,GAAG,EAAE,CAAC,gBAAgB,aAAhB,gBAAgB,cAAhB,gBAAgB,GAAI,sBAAsB,EAAE,EAClD,CAAC,gBAAgB,CAAC,CACnB,CAAC;IAEF,MAAM,MAAM,GAAG,OAAO,CAAwB,GAAG,EAAE;QACjD,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACzB,OAAO,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1C,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,MAAM,aAAa,GAAG,CAAC,MAAM,IAAI,CAAC,oBAAoB,IAAI,CAAC,aAAa,CAAC;IAEzE,oDAAoD;IACpD,MAAM,YAAY,GAAG,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAc,CAAC,CAAC;IAChF,MAAM,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC;IAEzC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,YAAY,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC;YAChC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAEhC,6FAA6F;IAC7F,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACrE,MAAM,wBAAwB,GAAG,2BAA2B,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACnF,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;IAEnF,gBAAgB;IAChB,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAc,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAoB,IAAI,CAAC,CAAC;IAC9D,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC/C,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAc;QAClD,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE;KACpF,CAAC,CAAC;IACH,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAe,cAAc,CAAC,CAAC;IACjF,mFAAmF;IACnF,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAgB,0BAA0B,CAAC,CAAC;IACpG,MAAM,CAAC,sBAAsB,EAAE,yBAAyB,CAAC,GAAG,QAAQ,CAClE,GAAG,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,CAC5C,CAAC;IAEF,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,KAAmB,EAAE,EAAE;QACnD,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACxB,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,YAAY,CAAC,KAAK,EAAE,CAAC;YACvB,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACvC,CAAC;IACH,CAAC,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAEzB,MAAM,QAAQ,GAAG,WAAW,CAC1B,CAAC,KAAwB,EAAE,KAAa,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAc,EAAE,EAAE,CAAC,iCAAM,CAAC,KAAE,CAAC,KAAK,CAAC,EAAE,KAAK,IAAG,CAAC,EACvG,EAAE,CACH,CAAC;IAEF,eAAe;IACf,6DAA6D;IAC7D,MAAM,iBAAiB,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAC1D,MAAM,oBAAoB,GAAG,MAAM,CAA2B,IAAI,CAAC,CAAC;IACpE,MAAM,oBAAoB,GAAG,MAAM,CAA2B,IAAI,CAAC,CAAC;IACpE,MAAM,iBAAiB,GAAG,MAAM,CAA2B,IAAI,CAAC,CAAC;IACjE,MAAM,WAAW,GAAG,MAAM,CAA4B,IAAI,CAAC,CAAC;IAC5D,+EAA+E;IAC/E,MAAM,kBAAkB,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAC;IAC/D,MAAM,kBAAkB,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAC;IAC/D,MAAM,eAAe,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAExD,MAAM,cAAc,GAAG,WAAW,CAAC,CACjC,KAAyC,EACzC,IAA2B,EAC3B,EAAE;;QACF,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,IACE,kBAAkB,CAAC,OAAO;eACvB,kBAAkB,CAAC,OAAO;eAC1B,eAAe,CAAC,OAAO,EAC1B,CAAC;YACD,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAChC,OAAO;QACT,CAAC;QACD,2EAA2E;QAC3E,MAAA,MAAA,oBAAoB,CAAC,OAAO,0CAAE,OAAO,kDAAI,CAAC;QAC1C,MAAA,MAAA,oBAAoB,CAAC,OAAO,0CAAE,OAAO,kDAAI,CAAC;QAC1C,MAAA,MAAA,iBAAiB,CAAC,OAAO,0CAAE,OAAO,kDAAI,CAAC;IACzC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,IAA2B,EAAE,EAAE;QACjE,cAAc,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IACrB,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,IAA2B,EAAE,EAAE;QACjE,cAAc,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IACrB,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,IAA2B,EAAE,EAAE;QAC9D,cAAc,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;IACxC,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IACrB,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,gBAAgB,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IACrD,MAAM,UAAU,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAC;IAEzD,SAAS,CAAC,GAAG,EAAE;;QACb,UAAU,CAAC,OAAO,GAAG,MAAA,YAAY,CAAC,IAAI,mCAAI,IAAI,CAAC;IACjD,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IAExB,sFAAsF;IACtF,MAAM,OAAO,GAAG,MAAM,CAAc,IAAI,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,MAAM,CAAc,OAAO,CAAC,CAAC;IAChD,MAAM,gBAAgB,GAAG,MAAM,CAAe,aAAa,CAAC,CAAC;IAE7D,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IACzB,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACX,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAC/B,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACd,SAAS,CAAC,GAAG,EAAE;QACb,gBAAgB,CAAC,OAAO,GAAG,aAAa,CAAC;IAC3C,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IACpB,SAAS,CAAC,GAAG,EAAE;QACb,iBAAiB,CAAC,OAAO,GAAG,MAAM,CAAC;IACrC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,uBAAuB;IACvB,yEAAyE;IACzE,2EAA2E;IAC3E,yBAAyB;IACzB,MAAM,QAAQ,GAAG,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,IAAI,mCAAI,CAAC,YAAY,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACxE,MAAM,WAAW,GAAG,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzD,MAAM,MAAM,GAAkB,MAAA,MAAA,WAAW,CAAC,IAAI,0CAAE,MAAM,mCAAI,IAAI,CAAC;IAC/D,MAAM,WAAW,GAAG,MAAA,MAAA,WAAW,CAAC,IAAI,0CAAE,WAAW,mCAAI,EAAE,CAAC;IAExD,iEAAiE;IACjE,MAAM,KAAK,GAAU,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACtF,MAAM,mBAAmB,GAAG,sBAAsB,CAChD,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC,oBAAoB,CACzD,CAAC;IAEF,oBAAoB;IACpB,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,oBAAoB,CAAC;QACzD,MAAM;QACN,UAAU;QACV,SAAS,EAAE,iBAAiB;QAC5B,eAAe,EAAE,oBAAoB;QACrC,UAAU;QACV,gBAAgB;QAChB,QAAQ,EAAE,CAAC,GAAW,EAAE,EAAE;YACxB,yBAAyB,CAAC,KAAK,CAAC,CAAC;YACjC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;QACD,YAAY,EAAE,CAAC,GAAW,EAAE,EAAE;YAC5B,yBAAyB,CAAC,KAAK,CAAC,CAAC;YACjC,YAAY,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QACD,aAAa,EAAE,CAAC,OAAa,EAAE,EAAE;YAC/B,iEAAiE;YACjE,8CAA8C;YAC9C,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC;YAC1B,IAAI,UAAU,CAAC,OAAO;gBAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC3C,CAAC;KACF,CAAC,CAAC;IAEH,kCAAkC;IAClC,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,oBAAoB;YAAE,OAAO,SAAS,CAAC;QAE5C,CAAC,GAAS,EAAE;YACV,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,oBAAoB,CAAC,CAAC;gBACxD,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;oBACvB,SAAS,CAAC,QAAQ,CAAC,CAAC;oBACpB,cAAc,CAAC,IAAI,CAAC,CAAC;gBACvB,CAAC;YACH,CAAC;YAAC,OAAO,CAAU,EAAE,CAAC;gBACpB,IAAI,UAAU,CAAC,OAAO;oBAAE,QAAQ,CAAE,CAAW,CAAC,OAAO,CAAC,CAAC;YACzD,CAAC;QACH,CAAC,CAAA,CAAC,EAAE,CAAC;QAEL,OAAO,GAAG,EAAE;YACV,UAAU,CAAC,OAAO,GAAG,KAAK,CAAC;QAC7B,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAE3B,+EAA+E;IAC/E,SAAS,CAAC,GAAG,EAAE;;QACb,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,UAAU,GAAG,kBAAkB,CAAC,OAAO,CAAC;QAC9C,MAAM,UAAU,GAAG,kBAAkB,CAAC,OAAO,CAAC;QAC9C,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC;QACxC,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,IAAI,CAAC,OAAO;YAAE,OAAO;QAEnD,IAAI,oBAAoB,CAAC,OAAO,IAAI,oBAAoB,CAAC,OAAO,IAAI,iBAAiB,CAAC,OAAO,EAAE,CAAC;YAC9F,IAAI,CAAC;gBACH,MAAA,MAAA,oBAAoB,CAAC,OAAO,EAAC,OAAO,kDAAI,CAAC;gBACzC,MAAA,MAAA,oBAAoB,CAAC,OAAO,EAAC,OAAO,kDAAI,CAAC;gBACzC,MAAA,MAAA,iBAAiB,CAAC,OAAO,EAAC,OAAO,kDAAI,CAAC;gBACtC,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC/C,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC/C,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBACzC,OAAO;YACT,CAAC;YAAC,WAAM,CAAC;gBACP,wEAAwE;gBACxE,oBAAoB,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpC,oBAAoB,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpC,iBAAiB,CAAC,OAAO,GAAG,IAAI,CAAC;gBACjC,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;YAC7B,CAAC;QACH,CAAC;QAED,YAAY,CAAC,KAAK,CAAC,CAAC;QAEpB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;QACnC,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAC;QAE/B,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,YAAY,kCAAO,mBAAmB,KAAE,QAAQ,EAAE,IAAI,IAAG,CAAC;QACzF,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAAC;QAClE,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;QAE5D,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACzB,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACzB,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAEnB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACtB,IAAI,UAAU,CAAC,OAAO;gBAAE,YAAY,CAAC,IAAI,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAmC,EAAE,EAAE;;YAC1D,IAAI,UAAU,CAAC,OAAO;gBAAE,YAAY,CAAC,CAAA,MAAA,CAAC,CAAC,KAAK,0CAAE,OAAO,KAAI,EAAE,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QAEH,oBAAoB,CAAC,OAAO,GAAG,MAAM,CAAC;QACtC,oBAAoB,CAAC,OAAO,GAAG,MAAM,CAAC;QACtC,iBAAiB,CAAC,OAAO,GAAG,GAAG,CAAC;IAClC,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IAE7B,qBAAqB;IACrB,SAAS,CAAC,GAAG,EAAE;;QACb,IAAI,MAAM,KAAK,MAAM,CAAC,oBAAoB,IAAI,YAAY,CAAC,OAAO;YAAE,OAAO;QAC3E,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC;QAC5B,IAAI,UAAU;YAAE,UAAU,CAAC,MAAA,MAAA,mBAAmB,CAAC,IAAI,0CAAE,IAAI,mCAAI,IAAI,CAAC,CAAC;IACrE,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;IAEnD,wEAAwE;IACxE,iEAAiE;IACjE,mDAAmD;IACnD,SAAS,CAAC,GAAG,EAAE;;QACb,MAAM,WAAW,GAAG,CAAA,MAAA,WAAW,CAAC,IAAI,0CAAE,6BAA6B;gBAC9D,MAAA,WAAW,CAAC,IAAI,0CAAE,iBAAiB,CAAA,CAAC;QACzC,IACE,MAAM,KAAK,MAAM,CAAC,4BAA4B;eAC3C,CAAC,WAAW;eACZ,gBAAgB,CAAC,OAAO,KAAK,WAAW;YAC3C,OAAO;QAET,gBAAgB,CAAC,OAAO,GAAG,WAAW,CAAC;QACvC,aAAa,CAAC,WAAW,CAAC,CAAC;IAC7B,CAAC,EAAE;QACD,MAAM;QACN,MAAA,WAAW,CAAC,IAAI,0CAAE,6BAA6B;QAC/C,MAAA,WAAW,CAAC,IAAI,0CAAE,iBAAiB;QACnC,aAAa;KACd,CAAC,CAAC;IAEH,kBAAkB;IAClB,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,OAAwB,EAAE,gBAA+B,MAAM,EAAE,EAAE;QACjG,QAAQ,CAAC,EAAE,CAAC,CAAC;QACb,YAAY,CAAC,EAAE,CAAC,CAAC;QACjB,IAAI,KAAK,KAAK,QAAQ;YAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACtC,4EAA4E;QAC5E,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC1B,yBAAyB,CAAC,IAAI,CAAC,CAAC;QAChC,aAAa,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;IACnD,CAAC,EAAE,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC;IAE3B,mEAAmE;IACnE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,gBAAgB,GAAa;YACjC,MAAM,CAAC,oBAAoB;YAC3B,MAAM,CAAC,WAAW;YAClB,MAAM,CAAC,cAAc;SACtB,CAAC;QACF,IAAI,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACtC,yBAAyB,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,OAAO;QACL,KAAK;QACL,MAAM;QACN,WAAW,EAAE,CAAC,MAAM,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,wBAAwB;QAC1E,WAAW;QACX,SAAS;QACT,aAAa;QACb,UAAU;QACV,aAAa,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC;QACzC,KAAK;QACL,UAAU,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,SAAS;QACT,WAAW;QACX,mBAAmB,EAAE,MAAA,mBAAmB,CAAC,IAAI,mCAAI,IAAI;QACrD,OAAO;QACP,QAAQ;QACR,cAAc;QACd,cAAc;QACd,WAAW;QACX,UAAU,EAAE,aAAa,CAAC,SAAS;QACnC,sBAAsB;QACtB,mBAAmB,EAAE,gBAAgB,KAAK,IAAI;QAC9C,UAAU;QACV,YAAY,EAAE,MAAA,MAAA,iBAAiB,CAAC,IAAI,0CAAE,KAAK,mCAAI,EAAE;QACjD,qBAAqB,EAAE,MAAA,MAAA,wBAAwB,CAAC,IAAI,0CAAE,MAAM,mCAAI,IAAI;QACpE,aAAa;QACb,QAAQ;QACR,YAAY;QACZ,WAAW;QACX,MAAM,EAAE,MAAA,iBAAiB,CAAC,IAAI,mCAAI,IAAI;KACvC,CAAC;AACJ,CAAC","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,
|
|
21
|
+
placeOrder: import("@tanstack/react-query").UseMutationResult<Cart, Error, PlaceOrderArgs, unknown>;
|
|
18
22
|
};
|
|
@@ -14,17 +14,29 @@ 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';
|
|
26
37
|
import { checkoutKeys } from './keys';
|
|
27
38
|
import { getPlanPrice } from '../../utils/billingDetails';
|
|
39
|
+
import { buildStripeReturnUrl } from '../../utils/stripe';
|
|
28
40
|
class CardValidationError extends Error {
|
|
29
41
|
}
|
|
30
42
|
function toCartAddress(b) {
|
|
@@ -51,23 +63,27 @@ export function useCheckoutMutations(ctx) {
|
|
|
51
63
|
const queryClient = useQueryClient();
|
|
52
64
|
const optimisticConfirm = (cartBiId) => {
|
|
53
65
|
queryClient.setQueryData(checkoutKeys.status(cartBiId), (old) => {
|
|
54
|
-
var _a, _b;
|
|
55
|
-
|
|
66
|
+
var _a, _b, _c, _d;
|
|
67
|
+
console.log('old optimisticConfirm', old);
|
|
68
|
+
return {
|
|
56
69
|
status: STATUS.CONFIRM,
|
|
57
70
|
stripeSubscriptionId: (_a = old === null || old === void 0 ? void 0 : old.stripeSubscriptionId) !== null && _a !== void 0 ? _a : null,
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
stripeConfirmationRedirectUrl: (_b = old === null || old === void 0 ? void 0 : old.stripeConfirmationRedirectUrl) !== null && _b !== void 0 ? _b : null,
|
|
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 : '',
|
|
74
|
+
};
|
|
60
75
|
});
|
|
61
76
|
queryClient.invalidateQueries({ queryKey: checkoutKeys.status(cartBiId) });
|
|
62
77
|
};
|
|
63
78
|
const placeOrder = useMutation({
|
|
64
|
-
mutationFn: (
|
|
65
|
-
var
|
|
66
|
-
const { client,
|
|
67
|
-
|
|
68
|
-
const card = cardNumberElRef.current;
|
|
69
|
-
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)
|
|
70
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
|
+
}
|
|
71
87
|
const billing = billingRef.current;
|
|
72
88
|
if (!billing)
|
|
73
89
|
throw new Error('Billing information is not available. Please refresh and try again.');
|
|
@@ -82,7 +98,7 @@ export function useCheckoutMutations(ctx) {
|
|
|
82
98
|
client.getBillingPlans(),
|
|
83
99
|
]);
|
|
84
100
|
const salableUid = productInfo.uid;
|
|
85
|
-
const selectedCycle = (
|
|
101
|
+
const selectedCycle = (_b = ctx.selectedCycleRef.current) !== null && _b !== void 0 ? _b : 'monthly';
|
|
86
102
|
const selectedPlan = getPlanPrice(plansResult.plans, selectedCycle);
|
|
87
103
|
if (!selectedPlan) {
|
|
88
104
|
throw new Error(`No billing plan found for ${selectedCycle} billing.`);
|
|
@@ -91,31 +107,46 @@ export function useCheckoutMutations(ctx) {
|
|
|
91
107
|
// ── Step 2: Create the cart ───────────────────────────────────────────
|
|
92
108
|
const createdCart = yield client.createCart({ customerUid, salableUid, billingPlanUid });
|
|
93
109
|
ctx.onCartCreated(createdCart);
|
|
94
|
-
// ── Step 3:
|
|
95
|
-
const { paymentMethod, error } = yield stripe.createPaymentMethod({
|
|
96
|
-
type: 'card',
|
|
97
|
-
card,
|
|
98
|
-
billing_details: {
|
|
99
|
-
name: `${billing.firstName} ${billing.lastName}`.trim(),
|
|
100
|
-
address: toStripeAddress(billing),
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
if (error || !paymentMethod) {
|
|
104
|
-
throw new CardValidationError((error === null || error === void 0 ? void 0 : error.message) || 'There was a problem with your card.');
|
|
105
|
-
}
|
|
106
|
-
// ── Step 4: Attach billing address + payment method to cart item ──────
|
|
110
|
+
// ── Step 3: Attach billing address + payment method to cart item ──────
|
|
107
111
|
const { cartItemUid } = yield client.getCartItem(createdCart.biId);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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 ────
|
|
114
144
|
yield client.updateCartConsent({
|
|
115
145
|
cartUid: createdCart.uid,
|
|
116
146
|
consent,
|
|
147
|
+
returnUrl: buildStripeReturnUrl(createdCart.biId),
|
|
117
148
|
});
|
|
118
|
-
// ── Step
|
|
149
|
+
// ── Step 5: Fire the backend workflow ─────────────────────────────────
|
|
119
150
|
yield client.placeOrder(createdCart.uid);
|
|
120
151
|
return createdCart;
|
|
121
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 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 status: STATUS.CONFIRM,\n stripeSubscriptionId: old?.stripeSubscriptionId ?? null,\n lastComment: old?.lastComment ?? '',\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 auto-renewal consent on the TwouCart BI ───────────\n await client.updateCartConsent({\n cartUid: createdCart.uid,\n consent,\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');
|
|
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');
|
|
28
|
+
throw redirect('/learner-dashboard');
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
// composed loader that checks for authentication and subscription access
|
package/dist/routes.js.map
CHANGED
|
@@ -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
|
|
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"]}
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
// styles/components/successScreen.scss
|
|
2
2
|
|
|
3
|
+
.processing-screen {
|
|
4
|
+
position: fixed;
|
|
5
|
+
inset: 0;
|
|
6
|
+
z-index: 1050;
|
|
7
|
+
display: flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
background-color: var(--pgn-color-light-200, #f8f9fa);
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
.success-screen {
|
|
4
14
|
&__headline {
|
|
5
15
|
font-size: clamp(1.6rem, 3vw, 2.2rem);
|
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,6 +102,10 @@ export interface ProductInfo {
|
|
|
101
102
|
export interface CartStatusResult {
|
|
102
103
|
status: Status | null;
|
|
103
104
|
stripeSubscriptionId: string | null;
|
|
105
|
+
/** Stripe-hosted challenge URL for the checkout confirmation step. */
|
|
106
|
+
stripeConfirmationRedirectUrl: string | null;
|
|
107
|
+
/** PayPal-hosted approval URL for the same checkout confirmation step. */
|
|
108
|
+
paypalApprovalUrl: string | null;
|
|
104
109
|
lastComment: string;
|
|
105
110
|
}
|
|
106
111
|
export interface CartBillingAddress {
|
|
@@ -118,6 +123,8 @@ export interface UpdateCartItemArgs {
|
|
|
118
123
|
cartItemUid: string;
|
|
119
124
|
billingAddress?: CartBillingAddress;
|
|
120
125
|
paymentMethodId?: string;
|
|
126
|
+
/** Payer email attached when the payment type is TwouPaypalPayment. */
|
|
127
|
+
paypalEmail?: string | null;
|
|
121
128
|
}
|
|
122
129
|
/** Auto-renewal consent captured on TwouCart at checkout confirmation (SUBS-228). */
|
|
123
130
|
export interface CheckoutConsent {
|
|
@@ -133,6 +140,8 @@ export interface CheckoutConsent {
|
|
|
133
140
|
export interface UpdateCartConsentArgs {
|
|
134
141
|
cartUid: string;
|
|
135
142
|
consent: CheckoutConsent;
|
|
143
|
+
/** Absolute checkout return URL stored in the backend's tStripeReturnUrl field. */
|
|
144
|
+
returnUrl?: string | null;
|
|
136
145
|
}
|
|
137
146
|
export interface BillingInfo {
|
|
138
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 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}\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"]}
|
package/dist/utils/cartApi.d.ts
CHANGED
|
@@ -6,11 +6,11 @@ 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 onto the TwouCart BI. */
|
|
11
|
-
export declare function updateCartConsent(gql: GqlRunner, { cartUid, consent }: 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. */
|
|
15
15
|
export declare function getCartStatus(gql: GqlRunner, biId: string): Promise<CartStatusResult>;
|
|
16
16
|
export declare function findSubscriptionBiId(gql: GqlRunner, cartBiId: string): Promise<SubscriptionDetails | null>;
|
package/dist/utils/cartApi.js
CHANGED
|
@@ -40,31 +40,41 @@ 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);
|
|
47
|
+
// paybPayment is the abstract CustomerPeriodicPaymentInput, which only declares
|
|
48
|
+
// fields common to every payment type. Subtype fields such as
|
|
49
|
+
// tStripePaymentMethodId go in the generated `_<subtype>` slot.
|
|
47
50
|
if (paymentMethodId) {
|
|
48
51
|
input.paybPayment = {
|
|
49
52
|
typeName: 'TwouStripePayment',
|
|
50
53
|
_twouStripePayment: { tStripePaymentMethodId: paymentMethodId },
|
|
51
54
|
};
|
|
52
55
|
}
|
|
56
|
+
else if (paypalEmail !== undefined) {
|
|
57
|
+
input.paybPayment = {
|
|
58
|
+
typeName: 'TwouPaypalPayment',
|
|
59
|
+
_twouPaypalPayment: paypalEmail ? { tPaypalEmail: paypalEmail } : {},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
53
62
|
yield gql(UPDATE_CART_ITEM, { input });
|
|
54
63
|
});
|
|
55
64
|
}
|
|
56
|
-
/** Persist checkout consent checkboxes onto the TwouCart BI. */
|
|
65
|
+
/** Persist checkout consent checkboxes and the hosted-payment return URL onto the TwouCart BI. */
|
|
57
66
|
export function updateCartConsent(gql_1, _a) {
|
|
58
|
-
return __awaiter(this, arguments, void 0, function* (gql, { cartUid, consent }) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
return __awaiter(this, arguments, void 0, function* (gql, { cartUid, consent, returnUrl }) {
|
|
68
|
+
const input = {
|
|
69
|
+
uid: cartUid,
|
|
70
|
+
tConsentRecurring: consent.tConsentRecurring,
|
|
71
|
+
tConsentUkCa: consent.tConsentUkCa,
|
|
72
|
+
tConsentAt: consent.tConsentAt,
|
|
73
|
+
tConsentText: consent.tConsentText,
|
|
74
|
+
};
|
|
75
|
+
if (returnUrl)
|
|
76
|
+
input.tStripeReturnUrl = returnUrl;
|
|
77
|
+
yield gql(UPDATE_CART, { input });
|
|
68
78
|
});
|
|
69
79
|
}
|
|
70
80
|
/** "Place order" — triggers the server-side workflow. */
|
|
@@ -79,16 +89,18 @@ export function placeOrder(gql, cartUid) {
|
|
|
79
89
|
return data.startBusinessProcessableTransition;
|
|
80
90
|
});
|
|
81
91
|
}
|
|
82
|
-
/** →
|
|
92
|
+
/** → current cart workflow status, Stripe details, and latest comment. */
|
|
83
93
|
export function getCartStatus(gql, biId) {
|
|
84
94
|
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
95
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
86
96
|
const data = yield gql(GET_CART_STATUS, { biId });
|
|
87
97
|
const bi = (data === null || data === void 0 ? void 0 : data.getBusinessInteraction) || {};
|
|
88
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)) || [];
|
|
89
99
|
return {
|
|
90
100
|
status: ((_d = (_c = bi.bpStatus) === null || _c === void 0 ? void 0 : _c.code) !== null && _d !== void 0 ? _d : null),
|
|
91
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,
|
|
102
|
+
stripeConfirmationRedirectUrl: (_h = bi.tStripeConfirmationRedirectUrl) !== null && _h !== void 0 ? _h : null,
|
|
103
|
+
paypalApprovalUrl: (_j = bi.tPaypalApprovalUrl) !== null && _j !== void 0 ? _j : null,
|
|
92
104
|
lastComment: comments.length ? comments[comments.length - 1] : '',
|
|
93
105
|
};
|
|
94
106
|
});
|