@edx/frontend-app-subscription-checkout 1.24.0 → 1.24.2

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.
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
11
11
  import { createCheckoutClient } from '../../utils/checkout';
12
- import { getPlanFromUrl, writePlanQueryParam } from '../../utils/planQuery';
12
+ import { getPlanFromUrl, writeCourseRunKeyQueryParam, writePlanQueryParam } from '../../utils/planQuery';
13
13
  import { getReturnedCartBiIdFromUrl, loadStripe } from '../../utils/stripe';
14
14
  import { STATUS, STATUS_LABELS, STRIPE_ELEMENT_OPTS, phaseFor, DEFAULTS, BILLING_COUNTRIES, } from '../../constants';
15
15
  import { useCartStatusQuery, useSubscriptionIdQuery, useBillingPlansQuery, useActiveSubscriptionsQuery, useViaxSessionQuery, useCourseDetailQuery, } from './useCheckoutQueries';
@@ -44,7 +44,7 @@ const getInitialBillingCountry = (userCountry) => {
44
44
  return 'US';
45
45
  };
46
46
  export default function useCheckout(options = {}) {
47
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
47
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
48
48
  const { apiUrl = DEFAULTS.apiUrl, edxSessionUrl, stripePublishableKey, onComplete, userCountry = null, courseRunKey: courseRunKeyProp, redirectToUrl = redirectBrowser, } = options;
49
49
  const courseRunKey = useMemo(() => courseRunKeyProp !== null && courseRunKeyProp !== void 0 ? courseRunKeyProp : getCourseRunKeyFromUrl(), [courseRunKeyProp]);
50
50
  const client = useMemo(() => {
@@ -65,6 +65,13 @@ export default function useCheckout(options = {}) {
65
65
  const billingPlansQuery = useBillingPlansQuery(client, sessionReady);
66
66
  const activeSubscriptionsQuery = useActiveSubscriptionsQuery(client, sessionReady);
67
67
  const courseDetailQuery = useCourseDetailQuery(client, sessionReady, courseRunKey);
68
+ useEffect(() => {
69
+ var _a;
70
+ const resolvedCourseKey = (_a = courseDetailQuery.data) === null || _a === void 0 ? void 0 : _a.resolvedCourseKey;
71
+ if (!resolvedCourseKey || !courseRunKey || resolvedCourseKey === courseRunKey)
72
+ return;
73
+ writeCourseRunKeyQueryParam(resolvedCourseKey);
74
+ }, [(_a = courseDetailQuery.data) === null || _a === void 0 ? void 0 : _a.resolvedCourseKey, courseRunKey]);
68
75
  // --- state ---
69
76
  const [showBanner, setShowBanner] = useState(true);
70
77
  const [cart, setCart] = useState(null);
@@ -153,10 +160,10 @@ export default function useCheckout(options = {}) {
153
160
  // Poll the cart created in this session, or the one Stripe just returned
154
161
  // from. The returned cart waits for the viax session so the status request
155
162
  // carries a valid token.
156
- const cartBiId = (_a = cart === null || cart === void 0 ? void 0 : cart.biId) !== null && _a !== void 0 ? _a : (sessionReady ? returnedCartBiId : null);
163
+ const cartBiId = (_b = cart === null || cart === void 0 ? void 0 : cart.biId) !== null && _b !== void 0 ? _b : (sessionReady ? returnedCartBiId : null);
157
164
  const statusQuery = useCartStatusQuery(client, cartBiId, returnedCartBiId ? 3 : false);
158
- const status = (_c = (_b = statusQuery.data) === null || _b === void 0 ? void 0 : _b.status) !== null && _c !== void 0 ? _c : null;
159
- const lastComment = (_e = (_d = statusQuery.data) === null || _d === void 0 ? void 0 : _d.lastComment) !== null && _e !== void 0 ? _e : '';
165
+ const status = (_d = (_c = statusQuery.data) === null || _c === void 0 ? void 0 : _c.status) !== null && _d !== void 0 ? _d : null;
166
+ const lastComment = (_f = (_e = statusQuery.data) === null || _e === void 0 ? void 0 : _e.lastComment) !== null && _f !== void 0 ? _f : '';
160
167
  const { isSuccess: isStatusSuccess, refetch: refetchStatus } = statusQuery;
161
168
  const postReturnRefetchBiIdRef = useRef(null);
162
169
  // When returning from Stripe, immediately refetch status instead of waiting 1 second
@@ -297,8 +304,8 @@ export default function useCheckout(options = {}) {
297
304
  redirectToUrl(redirectUrl);
298
305
  }, [
299
306
  status,
300
- (_f = statusQuery.data) === null || _f === void 0 ? void 0 : _f.stripeConfirmationRedirectUrl,
301
- (_g = statusQuery.data) === null || _g === void 0 ? void 0 : _g.paypalApprovalUrl,
307
+ (_g = statusQuery.data) === null || _g === void 0 ? void 0 : _g.stripeConfirmationRedirectUrl,
308
+ (_h = statusQuery.data) === null || _h === void 0 ? void 0 : _h.paypalApprovalUrl,
302
309
  redirectToUrl,
303
310
  returnedCartBiId,
304
311
  ]);
@@ -339,7 +346,7 @@ export default function useCheckout(options = {}) {
339
346
  clearError: () => setError(''),
340
347
  cardError,
341
348
  lastComment,
342
- subscriptionDetails: (_h = subscriptionIdQuery.data) !== null && _h !== void 0 ? _h : null,
349
+ subscriptionDetails: (_j = subscriptionIdQuery.data) !== null && _j !== void 0 ? _j : null,
343
350
  billing,
344
351
  setField,
345
352
  numberMountRef,
@@ -349,13 +356,13 @@ export default function useCheckout(options = {}) {
349
356
  processingSubscription,
350
357
  returningFromStripe: returnedCartBiId !== null,
351
358
  placeOrder,
352
- billingPlans: (_k = (_j = billingPlansQuery.data) === null || _j === void 0 ? void 0 : _j.plans) !== null && _k !== void 0 ? _k : [],
353
- activeSubscriberCount: (_m = (_l = activeSubscriptionsQuery.data) === null || _l === void 0 ? void 0 : _l.active) !== null && _m !== void 0 ? _m : null,
359
+ billingPlans: (_l = (_k = billingPlansQuery.data) === null || _k === void 0 ? void 0 : _k.plans) !== null && _l !== void 0 ? _l : [],
360
+ activeSubscriberCount: (_o = (_m = activeSubscriptionsQuery.data) === null || _m === void 0 ? void 0 : _m.active) !== null && _o !== void 0 ? _o : null,
354
361
  selectedCycle,
355
362
  setCycle,
356
363
  sessionReady,
357
364
  userCountry,
358
- course: (_o = courseDetailQuery.data) !== null && _o !== void 0 ? _o : null,
365
+ course: (_p = courseDetailQuery.data) !== null && _p !== void 0 ? _p : null,
359
366
  };
360
367
  }
361
368
  //# 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,\n STATUS_LABELS,\n STRIPE_ELEMENT_OPTS,\n phaseFor,\n DEFAULTS,\n BILLING_COUNTRIES,\n} from '../../constants';\nimport type {\n Status, Phase, Cart, BillingInfo, CheckoutClient, StripeLike, StripeElementLike,\n StripeElementsLike, BillingPlan, CheckoutConsent, SupportedPaymentMethod,\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\nconst getInitialBillingCountry = (userCountry: string | null): string => {\n const normalized = userCountry?.trim().toUpperCase();\n if (normalized && BILLING_COUNTRIES.some(([countryCode]) => countryCode === normalized)) {\n return normalized;\n }\n return 'US';\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?: SupportedPaymentMethod) => 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: getInitialBillingCountry(userCountry), 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, returnedCartBiId ? 3 : false);\n const status: Status | null = statusQuery.data?.status ?? null;\n const lastComment = statusQuery.data?.lastComment ?? '';\n const { isSuccess: isStatusSuccess, refetch: refetchStatus } = statusQuery;\n const postReturnRefetchBiIdRef = useRef<string | null>(null);\n\n // When returning from Stripe, immediately refetch status instead of waiting 1 second\n // This ensures spinner hides and error message shows quickly\n useEffect(() => {\n if (!returnedCartBiId) {\n postReturnRefetchBiIdRef.current = null;\n return;\n }\n\n if (\n isStatusSuccess\n && postReturnRefetchBiIdRef.current !== returnedCartBiId\n ) {\n postReturnRefetchBiIdRef.current = returnedCartBiId;\n refetchStatus();\n }\n }, [isStatusSuccess, refetchStatus, returnedCartBiId]);\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 stripeRedirectUrl = statusQuery.data?.stripeConfirmationRedirectUrl;\n const paypalRedirectUrl = statusQuery.data?.paypalApprovalUrl;\n const redirectUrl = stripeRedirectUrl || paypalRedirectUrl;\n if (\n status !== STATUS.STRIPE_CONFIRMATION_REQUIRED\n || !redirectUrl\n || redirectedUrlRef.current === redirectUrl\n || (returnedCartBiId !== null && redirectUrl === stripeRedirectUrl)\n ) return;\n\n redirectedUrlRef.current = redirectUrl;\n redirectToUrl(redirectUrl);\n }, [\n status,\n statusQuery.data?.stripeConfirmationRedirectUrl,\n statusQuery.data?.paypalApprovalUrl,\n redirectToUrl,\n returnedCartBiId,\n ]);\n\n // --- actions ---\n const placeOrder = useCallback((consent: CheckoutConsent, paymentMethod: SupportedPaymentMethod = '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
+ {"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, writeCourseRunKeyQueryParam, writePlanQueryParam } from '../../utils/planQuery';\nimport { getReturnedCartBiIdFromUrl, loadStripe } from '../../utils/stripe';\nimport {\n STATUS,\n STATUS_LABELS,\n STRIPE_ELEMENT_OPTS,\n phaseFor,\n DEFAULTS,\n BILLING_COUNTRIES,\n} from '../../constants';\nimport type {\n Status, Phase, Cart, BillingInfo, CheckoutClient, StripeLike, StripeElementLike,\n StripeElementsLike, BillingPlan, CheckoutConsent, SupportedPaymentMethod,\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\nconst getInitialBillingCountry = (userCountry: string | null): string => {\n const normalized = userCountry?.trim().toUpperCase();\n if (normalized && BILLING_COUNTRIES.some(([countryCode]) => countryCode === normalized)) {\n return normalized;\n }\n return 'US';\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?: SupportedPaymentMethod) => 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 useEffect(() => {\n const resolvedCourseKey = courseDetailQuery.data?.resolvedCourseKey;\n if (!resolvedCourseKey || !courseRunKey || resolvedCourseKey === courseRunKey) return;\n writeCourseRunKeyQueryParam(resolvedCourseKey);\n }, [courseDetailQuery.data?.resolvedCourseKey, 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: getInitialBillingCountry(userCountry), 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, returnedCartBiId ? 3 : false);\n const status: Status | null = statusQuery.data?.status ?? null;\n const lastComment = statusQuery.data?.lastComment ?? '';\n const { isSuccess: isStatusSuccess, refetch: refetchStatus } = statusQuery;\n const postReturnRefetchBiIdRef = useRef<string | null>(null);\n\n // When returning from Stripe, immediately refetch status instead of waiting 1 second\n // This ensures spinner hides and error message shows quickly\n useEffect(() => {\n if (!returnedCartBiId) {\n postReturnRefetchBiIdRef.current = null;\n return;\n }\n\n if (\n isStatusSuccess\n && postReturnRefetchBiIdRef.current !== returnedCartBiId\n ) {\n postReturnRefetchBiIdRef.current = returnedCartBiId;\n refetchStatus();\n }\n }, [isStatusSuccess, refetchStatus, returnedCartBiId]);\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 stripeRedirectUrl = statusQuery.data?.stripeConfirmationRedirectUrl;\n const paypalRedirectUrl = statusQuery.data?.paypalApprovalUrl;\n const redirectUrl = stripeRedirectUrl || paypalRedirectUrl;\n if (\n status !== STATUS.STRIPE_CONFIRMATION_REQUIRED\n || !redirectUrl\n || redirectedUrlRef.current === redirectUrl\n || (returnedCartBiId !== null && redirectUrl === stripeRedirectUrl)\n ) return;\n\n redirectedUrlRef.current = redirectUrl;\n redirectToUrl(redirectUrl);\n }, [\n status,\n statusQuery.data?.stripeConfirmationRedirectUrl,\n statusQuery.data?.paypalApprovalUrl,\n redirectToUrl,\n returnedCartBiId,\n ]);\n\n // --- actions ---\n const placeOrder = useCallback((consent: CheckoutConsent, paymentMethod: SupportedPaymentMethod = '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"]}
package/dist/types.d.ts CHANGED
@@ -70,6 +70,7 @@ export interface CourseDetail {
70
70
  maDescription: string | null;
71
71
  tShortDescription: string | null;
72
72
  imageUrl: string | null;
73
+ resolvedCourseKey?: string | null;
73
74
  tCourseKey: string | null;
74
75
  tBrandText: string | null;
75
76
  tPrimarySubjectArea: string | null;
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 PaymentMethod = 'card' | 'googlepay' | 'paypal';\n\nexport type SupportedPaymentMethod = Exclude<PaymentMethod, 'googlepay'>;\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 -------------------------------------------------\n/** Result of tCartCancel (SUBS-1127). */\nexport interface CartCancelResult {\n ok: boolean,\n /** NOT_AUTHENTICATED | BI_ID_REQUIRED | CART_NOT_FOUND | NOT_YOUR_CART | CART_NOT_OPEN | CANCEL_FAILED */\n error?: string,\n}\n\nexport interface CheckoutClient {\n gql: GqlRunner,\n setToken: (token: string) => void,\n createCart: (vars: CartIds) => Promise<Cart>,\n /** Abandon the learner's own open cart, cancelling any pending Stripe / PayPal subscription on it. */\n cancelCart: (biId: string) => Promise<CartCancelResult>,\n getCartItem: (biId: string) => Promise<{ cartUid: string, cartItemUid: string }>,\n updateCartItem: (args: UpdateCartItemArgs) => Promise<void>,\n updateCartConsent: (args: UpdateCartConsentArgs) => Promise<void>,\n waitForCartBusinessProcess: (biId: string) => 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 SupportedPaymentMethod = Exclude<PaymentMethod, 'googlepay'>;\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 resolvedCourseKey?: 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 -------------------------------------------------\n/** Result of tCartCancel (SUBS-1127). */\nexport interface CartCancelResult {\n ok: boolean,\n /** NOT_AUTHENTICATED | BI_ID_REQUIRED | CART_NOT_FOUND | NOT_YOUR_CART | CART_NOT_OPEN | CANCEL_FAILED */\n error?: string,\n}\n\nexport interface CheckoutClient {\n gql: GqlRunner,\n setToken: (token: string) => void,\n createCart: (vars: CartIds) => Promise<Cart>,\n /** Abandon the learner's own open cart, cancelling any pending Stripe / PayPal subscription on it. */\n cancelCart: (biId: string) => Promise<CartCancelResult>,\n getCartItem: (biId: string) => Promise<{ cartUid: string, cartItemUid: string }>,\n updateCartItem: (args: UpdateCartItemArgs) => Promise<void>,\n updateCartConsent: (args: UpdateCartConsentArgs) => Promise<void>,\n waitForCartBusinessProcess: (biId: string) => 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"]}
@@ -16,6 +16,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
16
16
  */
17
17
  import { GET_CUSTOMER, GET_BILLING_PLANS, GET_PRODUCT, GET_ACTIVE_SUBSCRIPTIONS_STATE, GET_IS_CURRENT_USER_SUBSCRIBER, GET_COURSE_DETAIL } from './queries';
18
18
  import { getSiteConfig } from '@openedx/frontend-base';
19
+ function encodeCourseRunKey(key) {
20
+ return encodeURIComponent(key.trim());
21
+ }
19
22
  /** Resolve the viax customer uid for the authenticated user by email. */
20
23
  export function getCustomerUid(gql, userEmail) {
21
24
  return __awaiter(this, void 0, void 0, function* () {
@@ -109,13 +112,14 @@ export function getCourseDetail(gql, courseKey) {
109
112
  const key = courseKey.trim();
110
113
  if (!key)
111
114
  return null;
115
+ const encodedKey = encodeCourseRunKey(key);
112
116
  const siteConfig = getSiteConfig();
113
117
  const LMS_BASE_URL = siteConfig.lmsBaseUrl;
114
118
  // 1. Fetch resolved key from Partner REST API
115
119
  let resolvedCourseKey = key;
116
120
  try {
117
- const url = new URL(`${LMS_BASE_URL.replace(/\/$/, '')}/api/subscription/course-runs`);
118
- url.searchParams.set('key', key);
121
+ const url = new URL(`${LMS_BASE_URL.replace(/\/$/, '')}/api/subscription/course_runs/`);
122
+ url.search = `key=${encodedKey}`;
119
123
  const res = yield fetch(url.toString(), {
120
124
  method: 'GET',
121
125
  headers: { Accept: 'application/json' },
@@ -123,9 +127,9 @@ export function getCourseDetail(gql, courseKey) {
123
127
  if (res.ok) {
124
128
  const payload = yield res.json();
125
129
  // Extract key from elements array as per Partner API response format
126
- const partnerApiKey = (_b = (_a = payload === null || payload === void 0 ? void 0 : payload.elements) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.key;
127
- if (partnerApiKey) {
128
- resolvedCourseKey = partnerApiKey;
130
+ const partnerCourseKey = (_b = (_a = payload === null || payload === void 0 ? void 0 : payload.elements) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.course;
131
+ if (partnerCourseKey) {
132
+ resolvedCourseKey = partnerCourseKey;
129
133
  }
130
134
  }
131
135
  }
@@ -145,6 +149,7 @@ export function getCourseDetail(gql, courseKey) {
145
149
  maDescription: (_g = node.maDescription) !== null && _g !== void 0 ? _g : null,
146
150
  tShortDescription: (_h = node.tShortDescription) !== null && _h !== void 0 ? _h : null,
147
151
  imageUrl: (_j = node.imageUrl) !== null && _j !== void 0 ? _j : null,
152
+ resolvedCourseKey,
148
153
  tCourseKey: (_k = node.tCourseKey) !== null && _k !== void 0 ? _k : null,
149
154
  tBrandText: (_l = node.tBrandText) !== null && _l !== void 0 ? _l : null,
150
155
  tPrimarySubjectArea: (_m = node.tPrimarySubjectArea) !== null && _m !== void 0 ? _m : null,
@@ -1 +1 @@
1
- {"version":3,"file":"bootstrapApi.js","sourceRoot":"","sources":["../../src/utils/bootstrapApi.ts"],"names":[],"mappings":";;;;;;;;;AAAA;;;;;;GAMG;AACH,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,8BAA8B,EAAE,8BAA8B,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAE5J,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,yEAAyE;AACzE,MAAM,UAAgB,cAAc,CAAC,GAAc,EAAE,SAAiB;;;QACpE,MAAM,MAAM,GAAG,gCAAgC,SAAS,GAAG,CAAC;QAC5D,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QACjD,MAAM,IAAI,GAA6B,MAAA,MAAA,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,kBAAkB,0CAAE,KAAK,0CAAG,CAAC,CAAC,0CAAE,IAAI,CAAC;QAClF,IAAI,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,GAAG,CAAA;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,SAAS,kCAAkC,CAAC,CAAC;QAC3G,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB,CAAC;CAAA;AAED;;;;GAIG;AACH,MAAM,UAAgB,aAAa,CAAC,GAAc,EAAE,IAAY;;;QAC9D,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,UAAU,CAAC;QACjC,IAAI,CAAC,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,GAAG,CAAA;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,cAAc,CAAC,CAAC;QACnE,OAAO;YACL,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,MAAM,EAAE,MAAA,OAAO,CAAC,MAAM,mCAAI,EAAE;YAC5B,eAAe,EAAE,CAAC,MAAA,OAAO,CAAC,iBAAiB,mCAAI,EAAE,CAAC,CAAC,GAAG,CACpD,CAAC,CAAkC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAC3E;SACF,CAAC;IACJ,CAAC;CAAA;AAED,kDAAkD;AAClD,MAAM,UAAgB,eAAe,CAAC,GAAc;;;QAClD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,iBAAiB,CAAC,CAAC;QAC1C,MAAM,KAAK,GAQL,MAAA,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,yBAAyB,0CAAE,KAAK,mCAAI,EAAE,CAAC;QAEnD,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAE9D,MAAM,KAAK,GAAkB,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;;YAAC,OAAA,CAAC;gBACpD,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,aAAa,EAAE,IAAI,CAAC,cAAc;gBAClC,MAAM,EAAE,MAAA,MAAA,IAAI,CAAC,aAAa,0CAAE,MAAM,mCAAI,GAAG;gBACzC,QAAQ,EAAE,MAAA,MAAA,MAAA,IAAI,CAAC,aAAa,0CAAE,KAAK,0CAAE,IAAI,mCAAI,KAAK;gBAClD,UAAU,EAAE,IAAI,CAAC,aAAa;gBAC9B,cAAc,EAAE;oBACd,GAAG,EAAE,MAAA,MAAA,IAAI,CAAC,iBAAiB,0CAAE,GAAG,mCAAI,EAAE;oBACtC,MAAM,EAAE,MAAA,MAAA,IAAI,CAAC,iBAAiB,0CAAE,MAAM,mCAAI,EAAE;iBAC7C;aACF,CAAC,CAAA;SAAA,CAAC,CAAC;QAEJ,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;CAAA;AAED,yFAAyF;AACzF,MAAM,UAAgB,uBAAuB,CAAC,GAAc;;;QAC1D,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,8BAA8B,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,wBAAwB,CAAC;QAC9C,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACzB,OAAO;YACL,YAAY,EAAE,MAAM,CAAC,YAAY,KAAK,IAAI;YAC1C,gBAAgB,EAAE,MAAA,MAAM,CAAC,gBAAgB,mCAAI,IAAI;YACjD,MAAM,EAAE,MAAA,MAAM,CAAC,MAAM,mCAAI,IAAI;YAC7B,qBAAqB,EAAE,MAAA,MAAM,CAAC,qBAAqB,mCAAI,IAAI;YAC3D,gBAAgB,EAAE,MAAA,MAAM,CAAC,gBAAgB,mCAAI,IAAI;SAClD,CAAC;IACJ,CAAC;CAAA;AAED,gDAAgD;AAChD,MAAM,UAAgB,sBAAsB,CAAC,GAAc;;;QACzD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,8BAA8B,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,yBAAyB,0CAAE,MAAM,CAAC;QACvD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC7E,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC;CAAA;AAED;+EAC+E;AAC/E,MAAM,UAAgB,eAAe,CAAC,GAAc,EAAE,SAAiB;;;QACrE,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QAEtB,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;QACnC,MAAM,YAAY,GAAG,UAAU,CAAC,UAAU,CAAC;QAE3C,8CAA8C;QAC9C,IAAI,iBAAiB,GAAG,GAAG,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,+BAA+B,CAAC,CAAC;YACvF,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;YAEjC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE;gBACtC,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;aACxC,CAAC,CAAC;YAEH,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;gBACX,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;gBACjC,qEAAqE;gBACrE,MAAM,aAAa,GAAG,MAAA,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,0CAAG,CAAC,CAAC,0CAAE,GAAG,CAAC;gBAClD,IAAI,aAAa,EAAE,CAAC;oBAClB,iBAAiB,GAAG,aAAa,CAAC;gBACpC,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,gEAAgE;YAChE,OAAO,CAAC,KAAK,CAAC,kDAAkD,EAAE,KAAK,CAAC,CAAC;QAC3E,CAAC;QAED,qFAAqF;QACrF,MAAM,MAAM,GAAG,iBAAiB,iBAAiB,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC;QACzE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,iBAAiB,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QACtD,MAAM,IAAI,GAAG,MAAA,MAAA,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,gBAAgB,0CAAE,KAAK,0CAAG,CAAC,CAAC,0CAAE,IAAI,CAAC;QACtD,IAAI,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,IAAI,CAAA;YAAE,OAAO,IAAI,CAAC;QAE7B,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,MAAA,IAAI,CAAC,MAAM,mCAAI,EAAE;YACzB,aAAa,EAAE,MAAA,IAAI,CAAC,aAAa,mCAAI,IAAI;YACzC,iBAAiB,EAAE,MAAA,IAAI,CAAC,iBAAiB,mCAAI,IAAI;YACjD,QAAQ,EAAE,MAAA,IAAI,CAAC,QAAQ,mCAAI,IAAI;YAC/B,UAAU,EAAE,MAAA,IAAI,CAAC,UAAU,mCAAI,IAAI;YACnC,UAAU,EAAE,MAAA,IAAI,CAAC,UAAU,mCAAI,IAAI;YACnC,mBAAmB,EAAE,MAAA,IAAI,CAAC,mBAAmB,mCAAI,IAAI;YACrD,UAAU,EAAE,MAAA,IAAI,CAAC,UAAU,mCAAI,IAAI;YACnC,SAAS,EAAE,MAAA,IAAI,CAAC,SAAS,mCAAI,IAAI;YACjC,WAAW,EAAE,MAAA,MAAA,IAAI,CAAC,WAAW,0CAAE,IAAI,mCAAI,IAAI;YAC3C,YAAY,EAAE,MAAA,IAAI,CAAC,YAAY,mCAAI,IAAI;YACvC,aAAa,EAAE,MAAA,IAAI,CAAC,aAAa,mCAAI,IAAI;YACzC,aAAa,EAAE,MAAA,MAAA,IAAI,CAAC,aAAa,0CAAE,IAAI,mCAAI,IAAI;YAC/C,UAAU,EAAE,MAAA,IAAI,CAAC,UAAU,mCAAI,IAAI;YACnC,WAAW,EAAE,MAAA,IAAI,CAAC,WAAW,mCAAI,IAAI;YACrC,WAAW,EAAE,MAAA,MAAA,IAAI,CAAC,WAAW,0CAAE,IAAI,mCAAI,IAAI;YAC3C,UAAU,EAAE,MAAA,IAAI,CAAC,UAAU,mCAAI,IAAI;YACnC,yBAAyB,EAAE,MAAA,IAAI,CAAC,yBAAyB,mCAAI,IAAI;YACjE,oBAAoB,EAAE,MAAA,IAAI,CAAC,oBAAoB,mCAAI,IAAI;YACvD,cAAc,EAAE,MAAA,IAAI,CAAC,cAAc,mCAAI,IAAI;SAC5C,CAAC;IACJ,CAAC;CAAA","sourcesContent":["/**\n * api/bootstrapApi.ts — fetch the runtime ids needed to create a cart.\n *\n * customerUid — looked up by the user's email from their viax party record.\n * salableUid — the product uid from getProduct(maId), used as biiSalable.\n * billingPlanUid — selected from filterCustomerBillingPlan by the caller.\n */\nimport { GET_CUSTOMER, GET_BILLING_PLANS, GET_PRODUCT, GET_ACTIVE_SUBSCRIPTIONS_STATE, GET_IS_CURRENT_USER_SUBSCRIBER, GET_COURSE_DETAIL } from './queries';\nimport type { GqlRunner, BillingPlansResult, BillingPlan, TwouCustomer, ProductInfo, CurrentUserSubscriberResult, CourseDetail } from '../types';\nimport { getSiteConfig } from '@openedx/frontend-base';\n\n/** Resolve the viax customer uid for the authenticated user by email. */\nexport async function getCustomerUid(gql: GqlRunner, userEmail: string): Promise<string> {\n const filter = `parParty.paContacts.email = '${userEmail}'`;\n const data = await gql(GET_CUSTOMER, { filter });\n const node: TwouCustomer | undefined = data?.filterTwouCustomer?.edges?.[0]?.node;\n if (!node?.uid) throw new Error(`No viax customer found for ${userEmail}. Ensure your account is linked.`);\n return node.uid;\n}\n\n/**\n * Fetch the TwouSubscriptionPlan uid to use as biiSalable in createTwouCart.\n * The maId is the product market-area identifier, e.g. \"TWOU-SUB-PLAN-001\".\n * The returned `uid` is passed as biiSalable when creating the cart.\n */\nexport async function getSalableUid(gql: GqlRunner, maId: string): Promise<ProductInfo> {\n const data = await gql(GET_PRODUCT, { maId });\n const product = data?.getProduct;\n if (!product?.uid) throw new Error(`Product \"${maId}\" not found.`);\n return {\n uid: product.uid,\n maName: product.maName ?? '',\n ownershipModels: (product.saOwnershipModels ?? []).map(\n (m: { uid: string, omName: string }) => ({ uid: m.uid, omName: m.omName }),\n ),\n };\n}\n\n/** Fetch all available customer billing plans. */\nexport async function getBillingPlans(gql: GqlRunner): Promise<BillingPlansResult> {\n const data = await gql(GET_BILLING_PLANS);\n const edges: {\n node: {\n uid: string,\n tStripePriceId: string,\n tStripeAmount?: { amount: string, units?: { code: string } },\n bipIsTemplate: boolean,\n bipOwnershipModel?: { uid: string, omName: string },\n },\n }[] = data?.filterCustomerBillingPlan?.edges ?? [];\n\n if (!edges.length) throw new Error('No billing plans found.');\n\n const plans: BillingPlan[] = edges.map(({ node }) => ({\n uid: node.uid,\n stripePriceId: node.tStripePriceId,\n amount: node.tStripeAmount?.amount ?? '0',\n currency: node.tStripeAmount?.units?.code ?? 'USD',\n isTemplate: node.bipIsTemplate,\n ownershipModel: {\n uid: node.bipOwnershipModel?.uid ?? '',\n omName: node.bipOwnershipModel?.omName ?? '',\n },\n }));\n\n return { plans };\n}\n\n/** Check whether the currently authenticated user already has an active subscription. */\nexport async function isCurrentUserSubscriber(gql: GqlRunner): Promise<CurrentUserSubscriberResult | null> {\n const data = await gql(GET_IS_CURRENT_USER_SUBSCRIBER);\n const result = data?.tIsCurrentUserSubscriber;\n if (!result) return null;\n return {\n isSubscriber: result.isSubscriber === true,\n subscriptionBiId: result.subscriptionBiId ?? null,\n status: result.status ?? null,\n subscriptionStartDate: result.subscriptionStartDate ?? null,\n currentPeriodEnd: result.currentPeriodEnd ?? null,\n };\n}\n\n/** Fetch the number of active subscriptions. */\nexport async function getActiveSubscriptions(gql: GqlRunner): Promise<{ active: number }> {\n const data = await gql(GET_ACTIVE_SUBSCRIPTIONS_STATE);\n const active = data?.tActiveSubscriptionsState?.active;\n if (typeof active !== 'number') {\n throw new Error('Unexpected response shape for tActiveSubscriptionsState');\n }\n return { active };\n}\n\n/** Fetch course details for the enrollment card by tCourseKey.\n * Sends the course key to Viax exactly as provided (e.g. HarvardX+CS50+X). */\nexport async function getCourseDetail(gql: GqlRunner, courseKey: string): Promise<CourseDetail | null> {\n const key = courseKey.trim();\n if (!key) return null;\n\n const siteConfig = getSiteConfig();\n const LMS_BASE_URL = siteConfig.lmsBaseUrl;\n\n // 1. Fetch resolved key from Partner REST API\n let resolvedCourseKey = key;\n try {\n const url = new URL(`${LMS_BASE_URL.replace(/\\/$/, '')}/api/subscription/course-runs`);\n url.searchParams.set('key', key);\n\n const res = await fetch(url.toString(), {\n method: 'GET',\n headers: { Accept: 'application/json' },\n });\n\n if (res.ok) {\n const payload = await res.json();\n // Extract key from elements array as per Partner API response format\n const partnerApiKey = payload?.elements?.[0]?.key;\n if (partnerApiKey) {\n resolvedCourseKey = partnerApiKey;\n }\n }\n } catch (error) {\n // If Partner API fails, fallback gracefully to the original key\n console.error('Failed to fetch course run key from Partner API:', error);\n }\n\n // Escape single quotes for the filter string only — do not alter `+` or other chars.\n const filter = `tCourseKey = '${resolvedCourseKey.replace(/'/g, \"''\")}'`;\n const data = await gql(GET_COURSE_DETAIL, { filter });\n const node = data?.filterTwouCourse?.edges?.[0]?.node;\n if (!node?.maId) return null;\n\n return {\n maId: node.maId,\n maName: node.maName ?? '',\n maDescription: node.maDescription ?? null,\n tShortDescription: node.tShortDescription ?? null,\n imageUrl: node.imageUrl ?? null,\n tCourseKey: node.tCourseKey ?? null,\n tBrandText: node.tBrandText ?? null,\n tPrimarySubjectArea: node.tPrimarySubjectArea ?? null,\n tLevelType: node.tLevelType ?? null,\n tLanguage: node.tLanguage ?? null,\n tPacingType: node.tPacingType?.code ?? null,\n tDurationLow: node.tDurationLow ?? null,\n tDurationHigh: node.tDurationHigh ?? null,\n tDurationUnit: node.tDurationUnit?.code ?? null,\n tEffortLow: node.tEffortLow ?? null,\n tEffortHigh: node.tEffortHigh ?? null,\n tEffortUnit: node.tEffortUnit?.code ?? null,\n tUrlCourse: node.tUrlCourse ?? null,\n tB2cSubscriptionInclusion: node.tB2cSubscriptionInclusion ?? null,\n tHasOfacRestrictions: node.tHasOfacRestrictions ?? null,\n tCatalogActive: node.tCatalogActive ?? null,\n };\n}\n"]}
1
+ {"version":3,"file":"bootstrapApi.js","sourceRoot":"","sources":["../../src/utils/bootstrapApi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/bootstrapApi.ts — fetch the runtime ids needed to create a cart.\n *\n * customerUid — looked up by the user's email from their viax party record.\n * salableUid — the product uid from getProduct(maId), used as biiSalable.\n * billingPlanUid — selected from filterCustomerBillingPlan by the caller.\n */\nimport { GET_CUSTOMER, GET_BILLING_PLANS, GET_PRODUCT, GET_ACTIVE_SUBSCRIPTIONS_STATE, GET_IS_CURRENT_USER_SUBSCRIBER, GET_COURSE_DETAIL } from './queries';\nimport type { GqlRunner, BillingPlansResult, BillingPlan, TwouCustomer, ProductInfo, CurrentUserSubscriberResult, CourseDetail } from '../types';\nimport { getSiteConfig } from '@openedx/frontend-base';\n\nfunction encodeCourseRunKey(key: string): string {\n return encodeURIComponent(key.trim());\n}\n\n/** Resolve the viax customer uid for the authenticated user by email. */\nexport async function getCustomerUid(gql: GqlRunner, userEmail: string): Promise<string> {\n const filter = `parParty.paContacts.email = '${userEmail}'`;\n const data = await gql(GET_CUSTOMER, { filter });\n const node: TwouCustomer | undefined = data?.filterTwouCustomer?.edges?.[0]?.node;\n if (!node?.uid) throw new Error(`No viax customer found for ${userEmail}. Ensure your account is linked.`);\n return node.uid;\n}\n\n/**\n * Fetch the TwouSubscriptionPlan uid to use as biiSalable in createTwouCart.\n * The maId is the product market-area identifier, e.g. \"TWOU-SUB-PLAN-001\".\n * The returned `uid` is passed as biiSalable when creating the cart.\n */\nexport async function getSalableUid(gql: GqlRunner, maId: string): Promise<ProductInfo> {\n const data = await gql(GET_PRODUCT, { maId });\n const product = data?.getProduct;\n if (!product?.uid) throw new Error(`Product \"${maId}\" not found.`);\n return {\n uid: product.uid,\n maName: product.maName ?? '',\n ownershipModels: (product.saOwnershipModels ?? []).map(\n (m: { uid: string, omName: string }) => ({ uid: m.uid, omName: m.omName }),\n ),\n };\n}\n\n/** Fetch all available customer billing plans. */\nexport async function getBillingPlans(gql: GqlRunner): Promise<BillingPlansResult> {\n const data = await gql(GET_BILLING_PLANS);\n const edges: {\n node: {\n uid: string,\n tStripePriceId: string,\n tStripeAmount?: { amount: string, units?: { code: string } },\n bipIsTemplate: boolean,\n bipOwnershipModel?: { uid: string, omName: string },\n },\n }[] = data?.filterCustomerBillingPlan?.edges ?? [];\n\n if (!edges.length) throw new Error('No billing plans found.');\n\n const plans: BillingPlan[] = edges.map(({ node }) => ({\n uid: node.uid,\n stripePriceId: node.tStripePriceId,\n amount: node.tStripeAmount?.amount ?? '0',\n currency: node.tStripeAmount?.units?.code ?? 'USD',\n isTemplate: node.bipIsTemplate,\n ownershipModel: {\n uid: node.bipOwnershipModel?.uid ?? '',\n omName: node.bipOwnershipModel?.omName ?? '',\n },\n }));\n\n return { plans };\n}\n\n/** Check whether the currently authenticated user already has an active subscription. */\nexport async function isCurrentUserSubscriber(gql: GqlRunner): Promise<CurrentUserSubscriberResult | null> {\n const data = await gql(GET_IS_CURRENT_USER_SUBSCRIBER);\n const result = data?.tIsCurrentUserSubscriber;\n if (!result) return null;\n return {\n isSubscriber: result.isSubscriber === true,\n subscriptionBiId: result.subscriptionBiId ?? null,\n status: result.status ?? null,\n subscriptionStartDate: result.subscriptionStartDate ?? null,\n currentPeriodEnd: result.currentPeriodEnd ?? null,\n };\n}\n\n/** Fetch the number of active subscriptions. */\nexport async function getActiveSubscriptions(gql: GqlRunner): Promise<{ active: number }> {\n const data = await gql(GET_ACTIVE_SUBSCRIPTIONS_STATE);\n const active = data?.tActiveSubscriptionsState?.active;\n if (typeof active !== 'number') {\n throw new Error('Unexpected response shape for tActiveSubscriptionsState');\n }\n return { active };\n}\n\n/** Fetch course details for the enrollment card by tCourseKey.\n * Sends the course key to Viax exactly as provided (e.g. HarvardX+CS50+X). */\nexport async function getCourseDetail(gql: GqlRunner, courseKey: string): Promise<CourseDetail | null> {\n const key = courseKey.trim();\n if (!key) return null;\n\n const encodedKey = encodeCourseRunKey(key);\n const siteConfig = getSiteConfig();\n const LMS_BASE_URL = siteConfig.lmsBaseUrl;\n\n // 1. Fetch resolved key from Partner REST API\n let resolvedCourseKey = key;\n try {\n const url = new URL(`${LMS_BASE_URL.replace(/\\/$/, '')}/api/subscription/course_runs/`);\n url.search = `key=${encodedKey}`;\n\n const res = await fetch(url.toString(), {\n method: 'GET',\n headers: { Accept: 'application/json' },\n });\n\n if (res.ok) {\n const payload = await res.json();\n // Extract key from elements array as per Partner API response format\n const partnerCourseKey = payload?.elements?.[0]?.course;\n if (partnerCourseKey) {\n resolvedCourseKey = partnerCourseKey;\n }\n }\n } catch (error) {\n // If Partner API fails, fallback gracefully to the original key\n console.error('Failed to fetch course run key from Partner API:', error);\n }\n\n // Escape single quotes for the filter string only — do not alter `+` or other chars.\n const filter = `tCourseKey = '${resolvedCourseKey.replace(/'/g, \"''\")}'`;\n const data = await gql(GET_COURSE_DETAIL, { filter });\n const node = data?.filterTwouCourse?.edges?.[0]?.node;\n if (!node?.maId) return null;\n\n return {\n maId: node.maId,\n maName: node.maName ?? '',\n maDescription: node.maDescription ?? null,\n tShortDescription: node.tShortDescription ?? null,\n imageUrl: node.imageUrl ?? null,\n resolvedCourseKey,\n tCourseKey: node.tCourseKey ?? null,\n tBrandText: node.tBrandText ?? null,\n tPrimarySubjectArea: node.tPrimarySubjectArea ?? null,\n tLevelType: node.tLevelType ?? null,\n tLanguage: node.tLanguage ?? null,\n tPacingType: node.tPacingType?.code ?? null,\n tDurationLow: node.tDurationLow ?? null,\n tDurationHigh: node.tDurationHigh ?? null,\n tDurationUnit: node.tDurationUnit?.code ?? null,\n tEffortLow: node.tEffortLow ?? null,\n tEffortHigh: node.tEffortHigh ?? null,\n tEffortUnit: node.tEffortUnit?.code ?? null,\n tUrlCourse: node.tUrlCourse ?? null,\n tB2cSubscriptionInclusion: node.tB2cSubscriptionInclusion ?? null,\n tHasOfacRestrictions: node.tHasOfacRestrictions ?? null,\n tCatalogActive: node.tCatalogActive ?? null,\n };\n}\n"]}
@@ -14,3 +14,9 @@ export declare function planToQueryValue(cycle: BillingCycle): 'monthly' | 'annu
14
14
  */
15
15
  export declare function withPlanQuery(search: string, cycle: BillingCycle): string;
16
16
  export declare function writePlanQueryParam(cycle: BillingCycle): void;
17
+ /**
18
+ * Set ?course_run_key= while preserving other params.
19
+ * Avoid URLSearchParams so literal `+` stays as `+` in the browser URL.
20
+ */
21
+ export declare function withCourseRunKeyQuery(search: string, courseRunKey: string): string;
22
+ export declare function writeCourseRunKeyQueryParam(courseRunKey: string): void;
@@ -1,4 +1,5 @@
1
1
  const PLAN_QUERY_KEY = 'plan';
2
+ const COURSE_RUN_KEY_QUERY_KEY = 'course_run_key';
2
3
  /**
3
4
  * Map ?plan= values to the internal billing cycle.
4
5
  * Accepts monthly, annual, and annually (the public query value).
@@ -54,4 +55,23 @@ export function writePlanQueryParam(cycle) {
54
55
  const next = `${window.location.pathname}${withPlanQuery(window.location.search, cycle)}${window.location.hash}`;
55
56
  window.history.replaceState(window.history.state, '', next);
56
57
  }
58
+ /**
59
+ * Set ?course_run_key= while preserving other params.
60
+ * Avoid URLSearchParams so literal `+` stays as `+` in the browser URL.
61
+ */
62
+ export function withCourseRunKeyQuery(search, courseRunKey) {
63
+ const raw = search.startsWith('?') ? search.slice(1) : search;
64
+ const parts = raw
65
+ .split('&')
66
+ .filter((part) => (part.length > 0
67
+ && !part.toLowerCase().startsWith(`${COURSE_RUN_KEY_QUERY_KEY}=`)));
68
+ parts.push(`${COURSE_RUN_KEY_QUERY_KEY}=${courseRunKey}`);
69
+ return `?${parts.join('&')}`;
70
+ }
71
+ export function writeCourseRunKeyQueryParam(courseRunKey) {
72
+ if (typeof window === 'undefined')
73
+ return;
74
+ const next = `${window.location.pathname}${withCourseRunKeyQuery(window.location.search, courseRunKey)}${window.location.hash}`;
75
+ window.history.replaceState(window.history.state, '', next);
76
+ }
57
77
  //# sourceMappingURL=planQuery.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"planQuery.js","sourceRoot":"","sources":["../../src/utils/planQuery.ts"],"names":[],"mappings":"AAEA,MAAM,cAAc,GAAG,MAAM,CAAC;AAE9B;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAA8B;IAC3D,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACvC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,UAAU;QAAE,OAAO,QAAQ,CAAC;IAChE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAc;;IAC9C,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,IAAI,CAAC;QACH,OAAO,MAAA,cAAc,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,mCAAI,SAAS,CAAC;IACnE,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,MAAA,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,mCAAI,SAAS,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,SAAS,CAAC;IACpD,OAAO,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAmB;IAClD,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;AACrD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,KAAmB;IAC/D,MAAM,SAAS,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9D,MAAM,KAAK,GAAG,GAAG;SACd,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAChB,IAAI,CAAC,MAAM,GAAG,CAAC;WACZ,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,cAAc,GAAG,CAAC,CACxD,CAAC,CAAC;IACL,KAAK,CAAC,IAAI,CAAC,GAAG,cAAc,IAAI,SAAS,EAAE,CAAC,CAAC;IAC7C,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAmB;IACrD,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO;IAC1C,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACjH,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;AAC9D,CAAC","sourcesContent":["import type { BillingCycle } from '../types';\n\nconst PLAN_QUERY_KEY = 'plan';\n\n/**\n * Map ?plan= values to the internal billing cycle.\n * Accepts monthly, annual, and annually (the public query value).\n */\nexport function parsePlanParam(raw: string | null | undefined): BillingCycle | null {\n if (!raw) return null;\n const value = raw.trim().toLowerCase();\n if (value === 'monthly') return 'monthly';\n if (value === 'annual' || value === 'annually') return 'annual';\n return null;\n}\n\nexport function getPlanFromSearch(search: string): BillingCycle {\n const match = /[?&]plan=([^&]*)/i.exec(search);\n if (!match) return 'monthly';\n try {\n return parsePlanParam(decodeURIComponent(match[1])) ?? 'monthly';\n } catch {\n return parsePlanParam(match[1]) ?? 'monthly';\n }\n}\n\nexport function getPlanFromUrl(): BillingCycle {\n if (typeof window === 'undefined') return 'monthly';\n return getPlanFromSearch(window.location.search);\n}\n\nexport function planToQueryValue(cycle: BillingCycle): 'monthly' | 'annually' {\n return cycle === 'annual' ? 'annually' : 'monthly';\n}\n\n/**\n * Set ?plan= while preserving other params.\n * Avoid URLSearchParams — it treats bare `+` as a space and would break\n * course_run_key values like HarvardX+CS50+X.\n */\nexport function withPlanQuery(search: string, cycle: BillingCycle): string {\n const planValue = planToQueryValue(cycle);\n const raw = search.startsWith('?') ? search.slice(1) : search;\n const parts = raw\n .split('&')\n .filter((part) => (\n part.length > 0\n && !part.toLowerCase().startsWith(`${PLAN_QUERY_KEY}=`)\n ));\n parts.push(`${PLAN_QUERY_KEY}=${planValue}`);\n return `?${parts.join('&')}`;\n}\n\nexport function writePlanQueryParam(cycle: BillingCycle): void {\n if (typeof window === 'undefined') return;\n const next = `${window.location.pathname}${withPlanQuery(window.location.search, cycle)}${window.location.hash}`;\n window.history.replaceState(window.history.state, '', next);\n}\n"]}
1
+ {"version":3,"file":"planQuery.js","sourceRoot":"","sources":["../../src/utils/planQuery.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BillingCycle } from '../types';\n\nconst PLAN_QUERY_KEY = 'plan';\nconst COURSE_RUN_KEY_QUERY_KEY = 'course_run_key';\n\n/**\n * Map ?plan= values to the internal billing cycle.\n * Accepts monthly, annual, and annually (the public query value).\n */\nexport function parsePlanParam(raw: string | null | undefined): BillingCycle | null {\n if (!raw) return null;\n const value = raw.trim().toLowerCase();\n if (value === 'monthly') return 'monthly';\n if (value === 'annual' || value === 'annually') return 'annual';\n return null;\n}\n\nexport function getPlanFromSearch(search: string): BillingCycle {\n const match = /[?&]plan=([^&]*)/i.exec(search);\n if (!match) return 'monthly';\n try {\n return parsePlanParam(decodeURIComponent(match[1])) ?? 'monthly';\n } catch {\n return parsePlanParam(match[1]) ?? 'monthly';\n }\n}\n\nexport function getPlanFromUrl(): BillingCycle {\n if (typeof window === 'undefined') return 'monthly';\n return getPlanFromSearch(window.location.search);\n}\n\nexport function planToQueryValue(cycle: BillingCycle): 'monthly' | 'annually' {\n return cycle === 'annual' ? 'annually' : 'monthly';\n}\n\n/**\n * Set ?plan= while preserving other params.\n * Avoid URLSearchParams — it treats bare `+` as a space and would break\n * course_run_key values like HarvardX+CS50+X.\n */\nexport function withPlanQuery(search: string, cycle: BillingCycle): string {\n const planValue = planToQueryValue(cycle);\n const raw = search.startsWith('?') ? search.slice(1) : search;\n const parts = raw\n .split('&')\n .filter((part) => (\n part.length > 0\n && !part.toLowerCase().startsWith(`${PLAN_QUERY_KEY}=`)\n ));\n parts.push(`${PLAN_QUERY_KEY}=${planValue}`);\n return `?${parts.join('&')}`;\n}\n\nexport function writePlanQueryParam(cycle: BillingCycle): void {\n if (typeof window === 'undefined') return;\n const next = `${window.location.pathname}${withPlanQuery(window.location.search, cycle)}${window.location.hash}`;\n window.history.replaceState(window.history.state, '', next);\n}\n\n/**\n * Set ?course_run_key= while preserving other params.\n * Avoid URLSearchParams so literal `+` stays as `+` in the browser URL.\n */\nexport function withCourseRunKeyQuery(search: string, courseRunKey: string): string {\n const raw = search.startsWith('?') ? search.slice(1) : search;\n const parts = raw\n .split('&')\n .filter((part) => (\n part.length > 0\n && !part.toLowerCase().startsWith(`${COURSE_RUN_KEY_QUERY_KEY}=`)\n ));\n parts.push(`${COURSE_RUN_KEY_QUERY_KEY}=${courseRunKey}`);\n return `?${parts.join('&')}`;\n}\n\nexport function writeCourseRunKeyQueryParam(courseRunKey: string): void {\n if (typeof window === 'undefined') return;\n const next = `${window.location.pathname}${withCourseRunKeyQuery(window.location.search, courseRunKey)}${window.location.hash}`;\n window.history.replaceState(window.history.state, '', next);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edx/frontend-app-subscription-checkout",
3
- "version": "1.24.0",
3
+ "version": "1.24.2",
4
4
  "description": "Subscription Checkout Frontend application",
5
5
  "repository": {
6
6
  "type": "git",