@edx/frontend-app-alacarte-checkout 1.12.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/data/hooks/useAlacarteCheckout.js +19 -1
- package/dist/data/hooks/useAlacarteCheckout.js.map +1 -1
- package/dist/utils/alacartePayment.d.ts +7 -0
- package/dist/utils/alacartePayment.js +3 -1
- package/dist/utils/alacartePayment.js.map +1 -1
- package/dist/utils/provision.js +1 -1
- package/dist/utils/provision.js.map +1 -1
- package/package.json +1 -1
|
@@ -28,6 +28,7 @@ import { createAlacarteCheckoutClient } from '../../utils/alacarteCheckout';
|
|
|
28
28
|
import { getCouponErrorMessage } from '../../utils/couponErrors';
|
|
29
29
|
import { placeAlacarteOrder, toAlacarteBillingAddress, } from '../../utils/alacartePayment';
|
|
30
30
|
import { AlacartePlaceOrderError } from '../../utils/placeOrderErrors';
|
|
31
|
+
import { CardValidationError } from '../../utils/stripeCard';
|
|
31
32
|
import { useViaxSessionQuery } from './useCheckoutQueries';
|
|
32
33
|
import { useOwnedProductQuery, useAlacarteCheckoutQuery, useSelectProductMutation, useProductDetailQuery, useUpdateCheckoutMutation, usePlacedOrderQuery, useOrderDatesQuery, } from './useAlacarteQueries';
|
|
33
34
|
/**
|
|
@@ -279,6 +280,11 @@ export function useAlacarteCheckout(options) {
|
|
|
279
280
|
const [couponError, setCouponError] = useState(null);
|
|
280
281
|
const [placeOrderError, setPlaceOrderError] = useState(null);
|
|
281
282
|
const [placingOrder, setPlacingOrder] = useState(false);
|
|
283
|
+
// biId of a checkout that's locked (CART_PAYMENT_PENDING) from a prior
|
|
284
|
+
// tCheckoutPlaceOrder call whose Stripe confirmation was rejected
|
|
285
|
+
// client-side. The next attempt must skip tCheckoutUpdate (it would fail
|
|
286
|
+
// with CHECKOUT_NOT_OPEN) and go straight to tCheckoutPlaceOrder again.
|
|
287
|
+
const [cardRetryBiId, setCardRetryBiId] = useState(null);
|
|
282
288
|
// Reset local overrides when the product selector changes.
|
|
283
289
|
useEffect(() => {
|
|
284
290
|
setCheckoutOverride(null);
|
|
@@ -397,11 +403,23 @@ export function useAlacarteCheckout(options) {
|
|
|
397
403
|
checkout,
|
|
398
404
|
billing: toAlacarteBillingAddress(opts.billing),
|
|
399
405
|
confirmCardPayment: opts.confirmCardPayment,
|
|
406
|
+
skipPrepare: cardRetryBiId === checkout.biId,
|
|
400
407
|
});
|
|
408
|
+
setCardRetryBiId(null);
|
|
401
409
|
setCheckoutOverride(updated);
|
|
402
410
|
yield setOrderBiId(updated.biId);
|
|
403
411
|
}
|
|
404
412
|
catch (err) {
|
|
413
|
+
if (err instanceof CardValidationError) {
|
|
414
|
+
// tCheckoutPlaceOrder already succeeded and locked the checkout
|
|
415
|
+
// (CART_PAYMENT_PENDING); Stripe rejected the card before it ever
|
|
416
|
+
// reached the backend. Next attempt must retry tCheckoutPlaceOrder
|
|
417
|
+
// directly, not tCheckoutUpdate.
|
|
418
|
+
setCardRetryBiId(checkout.biId);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
setCardRetryBiId(null);
|
|
422
|
+
}
|
|
405
423
|
if (err instanceof AlacartePlaceOrderError && err.checkout) {
|
|
406
424
|
setCheckoutOverride(err.checkout);
|
|
407
425
|
}
|
|
@@ -411,7 +429,7 @@ export function useAlacarteCheckout(options) {
|
|
|
411
429
|
finally {
|
|
412
430
|
setPlacingOrder(false);
|
|
413
431
|
}
|
|
414
|
-
}), [derived.checkout, client, setOrderBiId]);
|
|
432
|
+
}), [derived.checkout, client, setOrderBiId, cardRetryBiId]);
|
|
415
433
|
/** Fire-and-forget wrapper for card payments. */
|
|
416
434
|
const placeOrder = useCallback((opts) => {
|
|
417
435
|
if (placingOrder)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAlacarteCheckout.js","sourceRoot":"","sources":["../../../src/data/hooks/useAlacarteCheckout.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * data/hooks/useAlacarteCheckout.ts — page-level orchestration for the à la\n * carte checkout flow:\n *\n * 1. Read a product selector (course or program) from the URL via nuqs.\n * 2. If a product is selected: check ownership (tOwnedProducts). Already\n * owned -> 'alreadyOwned'. Otherwise select it into the checkout\n * (tCheckoutSelect) and fetch its display details in parallel.\n * 3. If no product is selected: look for an existing open checkout\n * (tCheckout). Found -> 'ready'. Not found -> 'noActiveCheckout'.\n * 4. Coupon apply/remove and payment prep via tCheckoutUpdate.\n *\n * See /alacarte-checkout-api.md and /alacarte-owned-products-api.md at the\n * repo root for the underlying API contract.\n */\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { useQueryStates, useQueryState, parseAsString } from 'nuqs';\nimport { createAlacarteCheckoutClient } from '../../utils/alacarteCheckout';\nimport { getCouponErrorMessage } from '../../utils/couponErrors';\nimport {\n placeAlacarteOrder,\n toAlacarteBillingAddress,\n} from '../../utils/alacartePayment';\nimport { AlacartePlaceOrderError } from '../../utils/placeOrderErrors';\nimport { useViaxSessionQuery } from './useCheckoutQueries';\nimport {\n useOwnedProductQuery, useAlacarteCheckoutQuery, useSelectProductMutation, useProductDetailQuery,\n useUpdateCheckoutMutation, usePlacedOrderQuery, useOrderDatesQuery,\n} from './useAlacarteQueries';\nimport type {\n AlacarteCheckout, AlacarteCheckoutResult, AlacarteLine, AlacarteMoney, AlacartePriceChanged,\n OwnedProduct, OwnedProductResult, PlacedOrder, ProductDetail, ProductSelector,\n} from '../../types/alacarte';\nimport type { BillingInfo } from '../../types';\n\nexport type AlacartePhase = 'loading' | 'alreadyOwned' | 'noActiveCheckout' | 'notPurchasable' | 'ready' | 'error';\n\nexport type AlacarteNoticeMessageId = 'alreadyOwnedCourse' | 'alreadyOwnedProgram' | 'noActiveCheckout';\n\n/** Pure phase decision output — no action callbacks. */\nexport interface AlacartePhaseState {\n phase: AlacartePhase,\n noticeMessageId?: AlacarteNoticeMessageId,\n selector: ProductSelector,\n checkout: AlacarteCheckout | null,\n line: AlacarteLine | null,\n productDetail: ProductDetail | null,\n error: string | null,\n priceChanged: AlacartePriceChanged | null,\n priceAccepted: boolean,\n /** True when the learner already owns this product. The checkout data may\n * still be populated so the UI can render the full page under an overlay. */\n isAlreadyOwned: boolean,\n /** Product name from the owned-products query — used as a fallback when the\n * checkout line (which normally carries the name) is not available. */\n ownedProductName: string | null,\n}\n\nexport interface UseAlacarteCheckoutResult extends AlacartePhaseState {\n couponError: string | null,\n couponApplying: boolean,\n applyCoupon: (code: string) => void,\n removeCoupon: () => void,\n clearCouponError: () => void,\n placingOrder: boolean,\n placeOrderError: string | null,\n /**\n * Accepts price (if needed) + terms, calls tCheckoutPlaceOrder with billing,\n * confirms the card via Stripe.js, then sets ?order_bi_id= to poll tPlacedOrder.\n * Fire-and-forget — errors go to placeOrderError state.\n */\n placeOrder: (opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => void,\n /**\n * Same as placeOrder but returns a Promise — used by ExpressCheckout\n * so the wallet sheet can await the result and call ev.complete().\n */\n placeOrderAsync: (opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => Promise<void>,\n clearPlaceOrderError: () => void,\n /** When ?order_bi_id= is present, the placed order (or null while polling). */\n placedOrder: PlacedOrder | null,\n awaitingPlacedOrder: boolean,\n placedOrderError: string | null,\n /** ISO timestamp the order was placed (tPlacedAt, falling back to\n * biCreatedAt); null while the order-dates query hasn't resolved yet. */\n placedOrderPlacedAt: string | null,\n}\n\n/**\n * Read the product selector from the URL: course_uid+course_run_key, or\n * program_uid, or neither. Both course params must be present together; if a\n * course param and a program param are both present, that's\n * CONFLICTING_SELECTION.\n *\n * NOTE: course_run_key values look like \"course-v1:HarvardX+CS50+X\" — nuqs\n * decodes query values the standards-compliant way (literal `+` -> space),\n * same as native URLSearchParams. This assumes whoever generates links into\n * this page percent-encodes `+` as %2B. Verify this in practice; see the plan\n * doc for the fallback if it's ever violated (drop nuqs for course_run_key\n * specifically).\n */\nexport function useProductSelector(): ProductSelector {\n const [{ courseUid, runKey, programUid }] = useQueryStates({\n courseUid: parseAsString,\n runKey: parseAsString,\n programUid: parseAsString,\n }, {\n urlKeys: {\n courseUid: 'course_uid',\n runKey: 'course_run_key',\n programUid: 'program_uid',\n },\n });\n\n return useMemo<ProductSelector>(() => {\n const hasCourse = !!courseUid || !!runKey;\n const hasProgram = !!programUid;\n if (hasCourse && hasProgram) return { kind: 'invalid' };\n if (courseUid && runKey) return { kind: 'course', courseUid, runKey };\n if (programUid) return { kind: 'program', programUid };\n return { kind: 'none' };\n }, [courseUid, runKey, programUid]);\n}\n\n/** Reads ?discount_code= — applied to the checkout right after it's created. */\nexport function useDiscountCode(): string | null {\n const [discountCode] = useQueryState('discount_code', parseAsString);\n return discountCode;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : 'Unknown error';\n}\n\n/**\n * Build a synthetic AlacarteLine from the owned-product pricing data so the\n * checkout overlay can show subtotal / deduction / total even when the backend\n * blocks tCheckoutSelect and returns no checkout object.\n */\nfunction buildLineFromOwnedProduct(product: OwnedProduct): AlacarteLine | null {\n if (!product.price) return null;\n const { full, deduction, amountDue, currency } = product.price;\n const toMoney = (amount: number): AlacarteMoney => ({ amount, currency });\n\n if (product.kind === 'program') {\n return {\n biiId: '',\n kind: 'program',\n programId: product.productId,\n programName: product.name,\n price: toMoney(full),\n deduction: deduction ? toMoney(deduction) : null,\n ownedCourses: product.ownedCourses.map((c) => ({\n courseId: c.courseId, name: c.name, via: c.via,\n })),\n discount: null,\n amountDue: toMoney(amountDue),\n billingAddressSet: false,\n };\n }\n\n return {\n biiId: '',\n kind: 'course',\n courseName: product.name,\n courseUid: product.productId,\n courseRunKey: '',\n seatType: 'verified',\n price: toMoney(full),\n discount: null,\n amountDue: toMoney(amountDue),\n billingAddressSet: false,\n };\n}\n\ninterface QueryLikeState<T> {\n isLoading: boolean,\n isError: boolean,\n error?: unknown,\n data?: T,\n}\n\ninterface MutationLikeState<T> {\n isIdle: boolean,\n isPending: boolean,\n isError: boolean,\n error?: unknown,\n data?: T,\n}\n\nexport interface DeriveAlacartePhaseInput {\n selector: ProductSelector,\n sessionReady: boolean,\n sessionIsError: boolean,\n sessionErrorMessage?: string,\n ownedQuery: QueryLikeState<OwnedProductResult>,\n selectMutation: MutationLikeState<AlacarteCheckoutResult>,\n checkoutQuery: QueryLikeState<AlacarteCheckoutResult>,\n productDetail: ProductDetail | null,\n /** Coupon (?discount_code=) applied right after the checkout is created —\n * when it succeeds, its checkout (with the coupon's discount reflected)\n * is shown in place of the pre-coupon one from selectMutation. */\n couponMutation?: MutationLikeState<AlacarteCheckoutResult>,\n /** True when a URL discount code still needs to be applied — the phase\n * stays 'loading' until the coupon mutation resolves so the user never sees\n * the pre-discount price flash. */\n urlCouponPending?: boolean,\n /** Latest successful manual update (coupon / acceptPrice / terms). Wins over\n * select + URL coupon when present. */\n checkoutOverride?: AlacarteCheckout | null,\n}\n\n/**\n * Pure decision function — no hooks, no React Query/nuqs types beyond plain\n * data — so every branch combination can be unit-tested directly.\n */\nexport function deriveAlacartePhase(input: DeriveAlacartePhaseInput): AlacartePhaseState {\n const {\n selector, sessionReady, sessionIsError, sessionErrorMessage,\n ownedQuery, selectMutation, checkoutQuery, productDetail, couponMutation,\n checkoutOverride = null, urlCouponPending: urlCouponPendingProp = false,\n } = input;\n\n const ownedProductName = ownedQuery.data?.product?.name ?? null;\n\n const empty: Omit<AlacartePhaseState, 'phase'> = {\n selector,\n checkout: null,\n line: null,\n productDetail: null,\n error: null,\n priceChanged: null,\n priceAccepted: false,\n isAlreadyOwned: false,\n ownedProductName: null,\n };\n\n const withCheckout = (\n checkout: AlacarteCheckout | null,\n priceChanged: AlacartePriceChanged | null = null,\n isAlreadyOwned = false,\n ): AlacartePhaseState => ({\n phase: isAlreadyOwned ? 'alreadyOwned' : 'ready',\n selector,\n checkout,\n line: checkout?.line ?? null,\n productDetail,\n error: null,\n priceChanged: priceChanged ?? null,\n priceAccepted: checkout?.priceAccepted === true,\n isAlreadyOwned,\n ownedProductName: isAlreadyOwned ? ownedProductName : null,\n });\n\n if (selector.kind === 'invalid') {\n return { phase: 'error', ...empty, error: 'CONFLICTING_SELECTION' };\n }\n if (sessionIsError) {\n return { phase: 'error', ...empty, error: sessionErrorMessage ?? 'Session error' };\n }\n\n const hasSelector = selector.kind === 'course' || selector.kind === 'program';\n\n if (hasSelector) {\n if (!sessionReady || ownedQuery.isLoading) {\n return { phase: 'loading', ...empty };\n }\n if (ownedQuery.isError) {\n return { phase: 'error', ...empty, error: errorMessage(ownedQuery.error) };\n }\n\n const isOwned = ownedQuery.data?.ok === true && ownedQuery.data.product?.status === 'full';\n\n if (selectMutation.isIdle || selectMutation.isPending) {\n return { phase: 'loading', ...empty };\n }\n if (selectMutation.isError) {\n return { phase: 'error', ...empty, error: errorMessage(selectMutation.error) };\n }\n if (selectMutation.data && !selectMutation.data.ok) {\n // The backend may block ALREADY_OWNED at select time. When the\n // blocked response still includes checkout data (line, pricing), use\n // it so the overlay page shows the real totals. Fall back to the\n // standalone tCheckout query, then to an empty state.\n if (selectMutation.data.blocked?.reason === 'ALREADY_OWNED') {\n const blockedCheckout = selectMutation.data.checkout\n ?? (checkoutQuery.data?.ok ? checkoutQuery.data.checkout : null);\n if (blockedCheckout) {\n return withCheckout(blockedCheckout, selectMutation.data.priceChanged ?? null, true);\n }\n // Build a synthetic line from the owned-product pricing so the\n // overlay can show subtotal / deduction / total.\n const ownedProduct = ownedQuery.data?.product;\n const syntheticLine = ownedProduct ? buildLineFromOwnedProduct(ownedProduct) : null;\n return {\n phase: 'alreadyOwned',\n noticeMessageId: selectMutation.data.blocked.kind === 'course' ? 'alreadyOwnedCourse' : 'alreadyOwnedProgram',\n ...empty,\n line: syntheticLine,\n productDetail,\n isAlreadyOwned: true,\n ownedProductName,\n };\n }\n if (selectMutation.data.blocked?.reason === 'NOT_PURCHASABLE') {\n return {\n phase: 'notPurchasable',\n ...empty,\n productDetail,\n error: selectMutation.data.blocked.kind === 'program'\n ? 'This program is not eligible for checkout.'\n : 'This course is not eligible for checkout.',\n };\n }\n const blockedDetail = selectMutation.data.blocked?.detail;\n return {\n phase: 'error',\n ...empty,\n error: blockedDetail ?? selectMutation.data.error ?? 'unknown',\n };\n }\n\n // When a URL discount code still needs to be applied, stay in 'loading'\n // so the user never sees the pre-discount price flash.\n if (urlCouponPendingProp && !checkoutOverride) {\n return { phase: 'loading', ...empty };\n }\n\n // Once the coupon-apply (?discount_code=) mutation succeeds, its checkout\n // (with the discount reflected) supersedes the pre-coupon one. Manual\n // updates (override) win over both.\n const finalCheckout = checkoutOverride\n ?? (couponMutation?.data?.ok\n ? couponMutation.data.checkout\n : selectMutation.data?.checkout ?? null);\n\n // When select succeeds but the owned-products query says the learner\n // already has this product, show the full checkout under an overlay.\n return withCheckout(\n finalCheckout,\n selectMutation.data?.priceChanged\n ?? (couponMutation?.data?.ok ? couponMutation.data.priceChanged : undefined)\n ?? null,\n isOwned,\n );\n }\n\n // No selector: fall back to the learner's existing open checkout, if any.\n if (!sessionReady || checkoutQuery.isLoading) {\n return { phase: 'loading', ...empty };\n }\n if (checkoutQuery.isError) {\n return { phase: 'error', ...empty, error: errorMessage(checkoutQuery.error) };\n }\n if (checkoutOverride || checkoutQuery.data?.checkout) {\n const checkout = checkoutOverride ?? checkoutQuery.data!.checkout;\n return withCheckout(checkout, checkoutQuery.data?.priceChanged ?? null);\n }\n\n return { phase: 'noActiveCheckout', noticeMessageId: 'noActiveCheckout', ...empty, isAlreadyOwned: false, ownedProductName: null };\n}\n\nexport function useAlacarteCheckout(\n options: { apiUrl?: string, edxSessionUrl?: string },\n): UseAlacarteCheckoutResult {\n const { apiUrl, edxSessionUrl } = options;\n const selector = useProductSelector();\n const discountCode = useDiscountCode();\n const [orderBiId, setOrderBiId] = useQueryState('order_bi_id', parseAsString);\n\n const client = useMemo(\n () => (apiUrl ? createAlacarteCheckoutClient({ apiUrl }) : null),\n [apiUrl],\n );\n\n // Reuses the same query key as useCheckout's session exchange — React\n // Query dedupes the network call regardless of how many hooks read it.\n const sessionQuery = useViaxSessionQuery(edxSessionUrl ?? null);\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 const hasSelector = selector.kind === 'course' || selector.kind === 'program';\n const productId = selector.kind === 'course'\n ? selector.courseUid\n : selector.kind === 'program' ? selector.programUid : null;\n\n const ownedQuery = useOwnedProductQuery(client, sessionReady && hasSelector, productId);\n const isOwned = ownedQuery.data?.ok === true && ownedQuery.data.product?.status === 'full';\n\n const selectMutation = useSelectProductMutation(client);\n const { isIdle: selectIsIdle, mutate: selectMutate } = selectMutation;\n\n // Mutations aren't called during render — fire once the owned-products\n // query resolves (regardless of result) so we always get checkout/line data.\n useEffect(() => {\n if (!hasSelector || ownedQuery.isLoading || ownedQuery.isError) return;\n if (selectIsIdle) selectMutate(selector);\n }, [hasSelector, ownedQuery.isLoading, ownedQuery.isError, selector, selectIsIdle, selectMutate]);\n\n const productDetailEnabled = hasSelector && sessionReady && !ownedQuery.isLoading;\n const productDetailQuery = useProductDetailQuery(client, productDetailEnabled, selector);\n\n // Run the checkout query when there's no selector (standard path) OR when\n // the product is already owned — after the select mutation is blocked with\n // ALREADY_OWNED the server may still have an open checkout with pricing data.\n const selectBlocked = !selectMutation.isIdle && !selectMutation.isPending\n && selectMutation.data && !selectMutation.data.ok;\n const checkoutQuery = useAlacarteCheckoutQuery(\n client,\n sessionReady && (!hasSelector || (isOwned && !!selectBlocked)),\n );\n\n // Apply ?discount_code= to the freshly-created checkout — fired once the\n // select mutation succeeds with a checkout that doesn't already carry it.\n const urlCouponMutation = useUpdateCheckoutMutation(client);\n const { isIdle: couponIsIdle, mutate: couponMutate } = urlCouponMutation;\n const createdCheckout = selectMutation.data?.ok ? selectMutation.data.checkout : null;\n\n useEffect(() => {\n if (!createdCheckout || !discountCode) return;\n if (createdCheckout.couponCode === discountCode) return;\n if (couponIsIdle) {\n couponMutate({ biId: createdCheckout.biId, couponCode: discountCode });\n }\n }, [createdCheckout, discountCode, couponIsIdle, couponMutate]);\n\n const [checkoutOverride, setCheckoutOverride] = useState<AlacarteCheckout | null>(null);\n const [couponError, setCouponError] = useState<string | null>(null);\n const [placeOrderError, setPlaceOrderError] = useState<string | null>(null);\n const [placingOrder, setPlacingOrder] = useState(false);\n\n // Reset local overrides when the product selector changes.\n useEffect(() => {\n setCheckoutOverride(null);\n setCouponError(null);\n setPlaceOrderError(null);\n }, [selector]);\n\n const manualUpdateMutation = useUpdateCheckoutMutation(client);\n\n const placedOrderQuery = usePlacedOrderQuery(\n client,\n orderBiId,\n sessionReady && !!orderBiId,\n );\n\n const orderDatesQuery = useOrderDatesQuery(\n client,\n orderBiId,\n sessionReady && !!orderBiId,\n );\n\n const derived = deriveAlacartePhase({\n selector,\n sessionReady,\n sessionIsError: sessionQuery.isError,\n sessionErrorMessage: sessionQuery.error?.message,\n ownedQuery: {\n isLoading: ownedQuery.isLoading,\n isError: ownedQuery.isError,\n error: ownedQuery.error,\n data: ownedQuery.data,\n },\n selectMutation: {\n isIdle: selectMutation.isIdle,\n isPending: selectMutation.isPending,\n isError: selectMutation.isError,\n error: selectMutation.error,\n data: selectMutation.data,\n },\n checkoutQuery: {\n isLoading: checkoutQuery.isLoading,\n isError: checkoutQuery.isError,\n error: checkoutQuery.error,\n data: checkoutQuery.data,\n },\n productDetail: productDetailQuery.data ?? null,\n couponMutation: {\n isIdle: urlCouponMutation.isIdle,\n isPending: urlCouponMutation.isPending,\n isError: urlCouponMutation.isError,\n error: urlCouponMutation.error,\n data: urlCouponMutation.data,\n },\n checkoutOverride,\n // The URL coupon is pending only when the checkout exists, doesn't already\n // carry the code, and the mutation hasn't completed yet. When the backend\n // returns the coupon pre-applied (couponCode === discountCode) or when\n // there's no discount_code param at all, this is false from the start.\n urlCouponPending: !!discountCode\n && !!createdCheckout\n && createdCheckout.couponCode !== discountCode\n && (urlCouponMutation.isIdle || urlCouponMutation.isPending),\n });\n\n const applyCoupon = useCallback((code: string) => {\n const biId = derived.checkout?.biId;\n if (!biId || !client) return;\n const trimmed = code.trim();\n if (!trimmed) return;\n\n setCouponError(null);\n manualUpdateMutation.mutate(\n { biId, couponCode: trimmed },\n {\n onSuccess: (result) => {\n if (result.ok && result.checkout) {\n setCheckoutOverride(result.checkout);\n setCouponError(null);\n } else {\n setCouponError(getCouponErrorMessage(result.error));\n }\n },\n onError: (err) => {\n setCouponError(errorMessage(err));\n },\n },\n );\n }, [derived.checkout?.biId, client, manualUpdateMutation]);\n\n const removeCoupon = useCallback(() => {\n const currentCheckout = derived.checkout;\n if (!currentCheckout?.biId || !client) return;\n\n setCouponError(null);\n manualUpdateMutation.mutate(\n { biId: currentCheckout.biId, couponCode: '' },\n {\n onSuccess: (result) => {\n if (!result.ok) {\n setCouponError(getCouponErrorMessage(result.error));\n return;\n }\n // The backend may return a checkout whose line still carries the\n // old discount/amountDue. Always patch the line to clear the\n // coupon so the cart summary reflects the removal immediately.\n const base = result.checkout ?? currentCheckout;\n const line = base.line;\n const patchedLine = line\n ? {\n ...line,\n discount: null,\n amountDue: line.price && line.kind === 'program' && 'deduction' in line && line.deduction\n ? { amount: line.price.amount - line.deduction.amount, currency: line.price.currency }\n : line.price,\n } as typeof line\n : null;\n setCheckoutOverride({\n ...base,\n couponCode: null,\n line: patchedLine,\n });\n setCouponError(null);\n },\n onError: (err) => {\n setCouponError(errorMessage(err));\n },\n },\n );\n }, [derived.checkout, client, manualUpdateMutation]);\n\n const placeOrderCore = useCallback(async (opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => {\n const checkout = derived.checkout;\n if (!checkout || !client) throw new Error('Checkout not ready');\n\n setPlacingOrder(true);\n setPlaceOrderError(null);\n\n try {\n const { checkout: updated } = await placeAlacarteOrder({\n updateCheckout: (args) => client.updateCheckout(args),\n placeCheckoutOrder: (biId, billing) => client.placeCheckoutOrder(biId, billing),\n checkout,\n billing: toAlacarteBillingAddress(opts.billing),\n confirmCardPayment: opts.confirmCardPayment,\n });\n\n setCheckoutOverride(updated);\n await setOrderBiId(updated.biId);\n } catch (err: unknown) {\n if (err instanceof AlacartePlaceOrderError && err.checkout) {\n setCheckoutOverride(err.checkout);\n }\n setPlaceOrderError(errorMessage(err));\n throw err;\n } finally {\n setPlacingOrder(false);\n }\n }, [derived.checkout, client, setOrderBiId]);\n\n /** Fire-and-forget wrapper for card payments. */\n const placeOrder = useCallback((opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => {\n if (placingOrder) return;\n placeOrderCore(opts).catch(() => { /* error already in state */ });\n }, [placeOrderCore, placingOrder]);\n\n const placedOrder = placedOrderQuery.data?.ok ? placedOrderQuery.data.order : null;\n\n const dates = orderDatesQuery.data?.ok ? orderDatesQuery.data.dates : null;\n const placedOrderPlacedAt = dates?.tPlacedAt ?? dates?.biCreatedAt ?? null;\n\n const placedOrderError = (() => {\n if (!orderBiId) return null;\n if (\n placedOrderQuery.data\n && !placedOrderQuery.data.ok\n && placedOrderQuery.data.error !== 'NOT_PLACED'\n && placedOrderQuery.data.error !== 'ORDER_READ_FAILED'\n ) {\n return placedOrderQuery.data.error ?? 'ORDER_READ_FAILED';\n }\n return null;\n })();\n\n return {\n ...derived,\n couponError,\n couponApplying: manualUpdateMutation.isPending,\n applyCoupon,\n removeCoupon,\n clearCouponError: () => setCouponError(null),\n placingOrder,\n placeOrderError,\n placeOrder,\n placeOrderAsync: placeOrderCore,\n clearPlaceOrderError: () => setPlaceOrderError(null),\n placedOrder,\n awaitingPlacedOrder: !!orderBiId && !placedOrder && !placedOrderError && (\n placedOrderQuery.isLoading\n || placedOrderQuery.isFetching\n || placedOrderQuery.data?.error === 'NOT_PLACED'\n || placedOrderQuery.data?.error === 'ORDER_READ_FAILED'\n || (placedOrderQuery.data?.ok === true && !placedOrderQuery.data.order)\n || !placedOrderQuery.data\n ),\n placedOrderError,\n placedOrderPlacedAt,\n };\n}\n\nexport default useAlacarteCheckout;\n"]}
|
|
1
|
+
{"version":3,"file":"useAlacarteCheckout.js","sourceRoot":"","sources":["../../../src/data/hooks/useAlacarteCheckout.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * data/hooks/useAlacarteCheckout.ts — page-level orchestration for the à la\n * carte checkout flow:\n *\n * 1. Read a product selector (course or program) from the URL via nuqs.\n * 2. If a product is selected: check ownership (tOwnedProducts). Already\n * owned -> 'alreadyOwned'. Otherwise select it into the checkout\n * (tCheckoutSelect) and fetch its display details in parallel.\n * 3. If no product is selected: look for an existing open checkout\n * (tCheckout). Found -> 'ready'. Not found -> 'noActiveCheckout'.\n * 4. Coupon apply/remove and payment prep via tCheckoutUpdate.\n *\n * See /alacarte-checkout-api.md and /alacarte-owned-products-api.md at the\n * repo root for the underlying API contract.\n */\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { useQueryStates, useQueryState, parseAsString } from 'nuqs';\nimport { createAlacarteCheckoutClient } from '../../utils/alacarteCheckout';\nimport { getCouponErrorMessage } from '../../utils/couponErrors';\nimport {\n placeAlacarteOrder,\n toAlacarteBillingAddress,\n} from '../../utils/alacartePayment';\nimport { AlacartePlaceOrderError } from '../../utils/placeOrderErrors';\nimport { CardValidationError } from '../../utils/stripeCard';\nimport { useViaxSessionQuery } from './useCheckoutQueries';\nimport {\n useOwnedProductQuery, useAlacarteCheckoutQuery, useSelectProductMutation, useProductDetailQuery,\n useUpdateCheckoutMutation, usePlacedOrderQuery, useOrderDatesQuery,\n} from './useAlacarteQueries';\nimport type {\n AlacarteCheckout, AlacarteCheckoutResult, AlacarteLine, AlacarteMoney, AlacartePriceChanged,\n OwnedProduct, OwnedProductResult, PlacedOrder, ProductDetail, ProductSelector,\n} from '../../types/alacarte';\nimport type { BillingInfo } from '../../types';\n\nexport type AlacartePhase = 'loading' | 'alreadyOwned' | 'noActiveCheckout' | 'notPurchasable' | 'ready' | 'error';\n\nexport type AlacarteNoticeMessageId = 'alreadyOwnedCourse' | 'alreadyOwnedProgram' | 'noActiveCheckout';\n\n/** Pure phase decision output — no action callbacks. */\nexport interface AlacartePhaseState {\n phase: AlacartePhase,\n noticeMessageId?: AlacarteNoticeMessageId,\n selector: ProductSelector,\n checkout: AlacarteCheckout | null,\n line: AlacarteLine | null,\n productDetail: ProductDetail | null,\n error: string | null,\n priceChanged: AlacartePriceChanged | null,\n priceAccepted: boolean,\n /** True when the learner already owns this product. The checkout data may\n * still be populated so the UI can render the full page under an overlay. */\n isAlreadyOwned: boolean,\n /** Product name from the owned-products query — used as a fallback when the\n * checkout line (which normally carries the name) is not available. */\n ownedProductName: string | null,\n}\n\nexport interface UseAlacarteCheckoutResult extends AlacartePhaseState {\n couponError: string | null,\n couponApplying: boolean,\n applyCoupon: (code: string) => void,\n removeCoupon: () => void,\n clearCouponError: () => void,\n placingOrder: boolean,\n placeOrderError: string | null,\n /**\n * Accepts price (if needed) + terms, calls tCheckoutPlaceOrder with billing,\n * confirms the card via Stripe.js, then sets ?order_bi_id= to poll tPlacedOrder.\n * Fire-and-forget — errors go to placeOrderError state.\n */\n placeOrder: (opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => void,\n /**\n * Same as placeOrder but returns a Promise — used by ExpressCheckout\n * so the wallet sheet can await the result and call ev.complete().\n */\n placeOrderAsync: (opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => Promise<void>,\n clearPlaceOrderError: () => void,\n /** When ?order_bi_id= is present, the placed order (or null while polling). */\n placedOrder: PlacedOrder | null,\n awaitingPlacedOrder: boolean,\n placedOrderError: string | null,\n /** ISO timestamp the order was placed (tPlacedAt, falling back to\n * biCreatedAt); null while the order-dates query hasn't resolved yet. */\n placedOrderPlacedAt: string | null,\n}\n\n/**\n * Read the product selector from the URL: course_uid+course_run_key, or\n * program_uid, or neither. Both course params must be present together; if a\n * course param and a program param are both present, that's\n * CONFLICTING_SELECTION.\n *\n * NOTE: course_run_key values look like \"course-v1:HarvardX+CS50+X\" — nuqs\n * decodes query values the standards-compliant way (literal `+` -> space),\n * same as native URLSearchParams. This assumes whoever generates links into\n * this page percent-encodes `+` as %2B. Verify this in practice; see the plan\n * doc for the fallback if it's ever violated (drop nuqs for course_run_key\n * specifically).\n */\nexport function useProductSelector(): ProductSelector {\n const [{ courseUid, runKey, programUid }] = useQueryStates({\n courseUid: parseAsString,\n runKey: parseAsString,\n programUid: parseAsString,\n }, {\n urlKeys: {\n courseUid: 'course_uid',\n runKey: 'course_run_key',\n programUid: 'program_uid',\n },\n });\n\n return useMemo<ProductSelector>(() => {\n const hasCourse = !!courseUid || !!runKey;\n const hasProgram = !!programUid;\n if (hasCourse && hasProgram) return { kind: 'invalid' };\n if (courseUid && runKey) return { kind: 'course', courseUid, runKey };\n if (programUid) return { kind: 'program', programUid };\n return { kind: 'none' };\n }, [courseUid, runKey, programUid]);\n}\n\n/** Reads ?discount_code= — applied to the checkout right after it's created. */\nexport function useDiscountCode(): string | null {\n const [discountCode] = useQueryState('discount_code', parseAsString);\n return discountCode;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : 'Unknown error';\n}\n\n/**\n * Build a synthetic AlacarteLine from the owned-product pricing data so the\n * checkout overlay can show subtotal / deduction / total even when the backend\n * blocks tCheckoutSelect and returns no checkout object.\n */\nfunction buildLineFromOwnedProduct(product: OwnedProduct): AlacarteLine | null {\n if (!product.price) return null;\n const { full, deduction, amountDue, currency } = product.price;\n const toMoney = (amount: number): AlacarteMoney => ({ amount, currency });\n\n if (product.kind === 'program') {\n return {\n biiId: '',\n kind: 'program',\n programId: product.productId,\n programName: product.name,\n price: toMoney(full),\n deduction: deduction ? toMoney(deduction) : null,\n ownedCourses: product.ownedCourses.map((c) => ({\n courseId: c.courseId, name: c.name, via: c.via,\n })),\n discount: null,\n amountDue: toMoney(amountDue),\n billingAddressSet: false,\n };\n }\n\n return {\n biiId: '',\n kind: 'course',\n courseName: product.name,\n courseUid: product.productId,\n courseRunKey: '',\n seatType: 'verified',\n price: toMoney(full),\n discount: null,\n amountDue: toMoney(amountDue),\n billingAddressSet: false,\n };\n}\n\ninterface QueryLikeState<T> {\n isLoading: boolean,\n isError: boolean,\n error?: unknown,\n data?: T,\n}\n\ninterface MutationLikeState<T> {\n isIdle: boolean,\n isPending: boolean,\n isError: boolean,\n error?: unknown,\n data?: T,\n}\n\nexport interface DeriveAlacartePhaseInput {\n selector: ProductSelector,\n sessionReady: boolean,\n sessionIsError: boolean,\n sessionErrorMessage?: string,\n ownedQuery: QueryLikeState<OwnedProductResult>,\n selectMutation: MutationLikeState<AlacarteCheckoutResult>,\n checkoutQuery: QueryLikeState<AlacarteCheckoutResult>,\n productDetail: ProductDetail | null,\n /** Coupon (?discount_code=) applied right after the checkout is created —\n * when it succeeds, its checkout (with the coupon's discount reflected)\n * is shown in place of the pre-coupon one from selectMutation. */\n couponMutation?: MutationLikeState<AlacarteCheckoutResult>,\n /** True when a URL discount code still needs to be applied — the phase\n * stays 'loading' until the coupon mutation resolves so the user never sees\n * the pre-discount price flash. */\n urlCouponPending?: boolean,\n /** Latest successful manual update (coupon / acceptPrice / terms). Wins over\n * select + URL coupon when present. */\n checkoutOverride?: AlacarteCheckout | null,\n}\n\n/**\n * Pure decision function — no hooks, no React Query/nuqs types beyond plain\n * data — so every branch combination can be unit-tested directly.\n */\nexport function deriveAlacartePhase(input: DeriveAlacartePhaseInput): AlacartePhaseState {\n const {\n selector, sessionReady, sessionIsError, sessionErrorMessage,\n ownedQuery, selectMutation, checkoutQuery, productDetail, couponMutation,\n checkoutOverride = null, urlCouponPending: urlCouponPendingProp = false,\n } = input;\n\n const ownedProductName = ownedQuery.data?.product?.name ?? null;\n\n const empty: Omit<AlacartePhaseState, 'phase'> = {\n selector,\n checkout: null,\n line: null,\n productDetail: null,\n error: null,\n priceChanged: null,\n priceAccepted: false,\n isAlreadyOwned: false,\n ownedProductName: null,\n };\n\n const withCheckout = (\n checkout: AlacarteCheckout | null,\n priceChanged: AlacartePriceChanged | null = null,\n isAlreadyOwned = false,\n ): AlacartePhaseState => ({\n phase: isAlreadyOwned ? 'alreadyOwned' : 'ready',\n selector,\n checkout,\n line: checkout?.line ?? null,\n productDetail,\n error: null,\n priceChanged: priceChanged ?? null,\n priceAccepted: checkout?.priceAccepted === true,\n isAlreadyOwned,\n ownedProductName: isAlreadyOwned ? ownedProductName : null,\n });\n\n if (selector.kind === 'invalid') {\n return { phase: 'error', ...empty, error: 'CONFLICTING_SELECTION' };\n }\n if (sessionIsError) {\n return { phase: 'error', ...empty, error: sessionErrorMessage ?? 'Session error' };\n }\n\n const hasSelector = selector.kind === 'course' || selector.kind === 'program';\n\n if (hasSelector) {\n if (!sessionReady || ownedQuery.isLoading) {\n return { phase: 'loading', ...empty };\n }\n if (ownedQuery.isError) {\n return { phase: 'error', ...empty, error: errorMessage(ownedQuery.error) };\n }\n\n const isOwned = ownedQuery.data?.ok === true && ownedQuery.data.product?.status === 'full';\n\n if (selectMutation.isIdle || selectMutation.isPending) {\n return { phase: 'loading', ...empty };\n }\n if (selectMutation.isError) {\n return { phase: 'error', ...empty, error: errorMessage(selectMutation.error) };\n }\n if (selectMutation.data && !selectMutation.data.ok) {\n // The backend may block ALREADY_OWNED at select time. When the\n // blocked response still includes checkout data (line, pricing), use\n // it so the overlay page shows the real totals. Fall back to the\n // standalone tCheckout query, then to an empty state.\n if (selectMutation.data.blocked?.reason === 'ALREADY_OWNED') {\n const blockedCheckout = selectMutation.data.checkout\n ?? (checkoutQuery.data?.ok ? checkoutQuery.data.checkout : null);\n if (blockedCheckout) {\n return withCheckout(blockedCheckout, selectMutation.data.priceChanged ?? null, true);\n }\n // Build a synthetic line from the owned-product pricing so the\n // overlay can show subtotal / deduction / total.\n const ownedProduct = ownedQuery.data?.product;\n const syntheticLine = ownedProduct ? buildLineFromOwnedProduct(ownedProduct) : null;\n return {\n phase: 'alreadyOwned',\n noticeMessageId: selectMutation.data.blocked.kind === 'course' ? 'alreadyOwnedCourse' : 'alreadyOwnedProgram',\n ...empty,\n line: syntheticLine,\n productDetail,\n isAlreadyOwned: true,\n ownedProductName,\n };\n }\n if (selectMutation.data.blocked?.reason === 'NOT_PURCHASABLE') {\n return {\n phase: 'notPurchasable',\n ...empty,\n productDetail,\n error: selectMutation.data.blocked.kind === 'program'\n ? 'This program is not eligible for checkout.'\n : 'This course is not eligible for checkout.',\n };\n }\n const blockedDetail = selectMutation.data.blocked?.detail;\n return {\n phase: 'error',\n ...empty,\n error: blockedDetail ?? selectMutation.data.error ?? 'unknown',\n };\n }\n\n // When a URL discount code still needs to be applied, stay in 'loading'\n // so the user never sees the pre-discount price flash.\n if (urlCouponPendingProp && !checkoutOverride) {\n return { phase: 'loading', ...empty };\n }\n\n // Once the coupon-apply (?discount_code=) mutation succeeds, its checkout\n // (with the discount reflected) supersedes the pre-coupon one. Manual\n // updates (override) win over both.\n const finalCheckout = checkoutOverride\n ?? (couponMutation?.data?.ok\n ? couponMutation.data.checkout\n : selectMutation.data?.checkout ?? null);\n\n // When select succeeds but the owned-products query says the learner\n // already has this product, show the full checkout under an overlay.\n return withCheckout(\n finalCheckout,\n selectMutation.data?.priceChanged\n ?? (couponMutation?.data?.ok ? couponMutation.data.priceChanged : undefined)\n ?? null,\n isOwned,\n );\n }\n\n // No selector: fall back to the learner's existing open checkout, if any.\n if (!sessionReady || checkoutQuery.isLoading) {\n return { phase: 'loading', ...empty };\n }\n if (checkoutQuery.isError) {\n return { phase: 'error', ...empty, error: errorMessage(checkoutQuery.error) };\n }\n if (checkoutOverride || checkoutQuery.data?.checkout) {\n const checkout = checkoutOverride ?? checkoutQuery.data!.checkout;\n return withCheckout(checkout, checkoutQuery.data?.priceChanged ?? null);\n }\n\n return { phase: 'noActiveCheckout', noticeMessageId: 'noActiveCheckout', ...empty, isAlreadyOwned: false, ownedProductName: null };\n}\n\nexport function useAlacarteCheckout(\n options: { apiUrl?: string, edxSessionUrl?: string },\n): UseAlacarteCheckoutResult {\n const { apiUrl, edxSessionUrl } = options;\n const selector = useProductSelector();\n const discountCode = useDiscountCode();\n const [orderBiId, setOrderBiId] = useQueryState('order_bi_id', parseAsString);\n\n const client = useMemo(\n () => (apiUrl ? createAlacarteCheckoutClient({ apiUrl }) : null),\n [apiUrl],\n );\n\n // Reuses the same query key as useCheckout's session exchange — React\n // Query dedupes the network call regardless of how many hooks read it.\n const sessionQuery = useViaxSessionQuery(edxSessionUrl ?? null);\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 const hasSelector = selector.kind === 'course' || selector.kind === 'program';\n const productId = selector.kind === 'course'\n ? selector.courseUid\n : selector.kind === 'program' ? selector.programUid : null;\n\n const ownedQuery = useOwnedProductQuery(client, sessionReady && hasSelector, productId);\n const isOwned = ownedQuery.data?.ok === true && ownedQuery.data.product?.status === 'full';\n\n const selectMutation = useSelectProductMutation(client);\n const { isIdle: selectIsIdle, mutate: selectMutate } = selectMutation;\n\n // Mutations aren't called during render — fire once the owned-products\n // query resolves (regardless of result) so we always get checkout/line data.\n useEffect(() => {\n if (!hasSelector || ownedQuery.isLoading || ownedQuery.isError) return;\n if (selectIsIdle) selectMutate(selector);\n }, [hasSelector, ownedQuery.isLoading, ownedQuery.isError, selector, selectIsIdle, selectMutate]);\n\n const productDetailEnabled = hasSelector && sessionReady && !ownedQuery.isLoading;\n const productDetailQuery = useProductDetailQuery(client, productDetailEnabled, selector);\n\n // Run the checkout query when there's no selector (standard path) OR when\n // the product is already owned — after the select mutation is blocked with\n // ALREADY_OWNED the server may still have an open checkout with pricing data.\n const selectBlocked = !selectMutation.isIdle && !selectMutation.isPending\n && selectMutation.data && !selectMutation.data.ok;\n const checkoutQuery = useAlacarteCheckoutQuery(\n client,\n sessionReady && (!hasSelector || (isOwned && !!selectBlocked)),\n );\n\n // Apply ?discount_code= to the freshly-created checkout — fired once the\n // select mutation succeeds with a checkout that doesn't already carry it.\n const urlCouponMutation = useUpdateCheckoutMutation(client);\n const { isIdle: couponIsIdle, mutate: couponMutate } = urlCouponMutation;\n const createdCheckout = selectMutation.data?.ok ? selectMutation.data.checkout : null;\n\n useEffect(() => {\n if (!createdCheckout || !discountCode) return;\n if (createdCheckout.couponCode === discountCode) return;\n if (couponIsIdle) {\n couponMutate({ biId: createdCheckout.biId, couponCode: discountCode });\n }\n }, [createdCheckout, discountCode, couponIsIdle, couponMutate]);\n\n const [checkoutOverride, setCheckoutOverride] = useState<AlacarteCheckout | null>(null);\n const [couponError, setCouponError] = useState<string | null>(null);\n const [placeOrderError, setPlaceOrderError] = useState<string | null>(null);\n const [placingOrder, setPlacingOrder] = useState(false);\n // biId of a checkout that's locked (CART_PAYMENT_PENDING) from a prior\n // tCheckoutPlaceOrder call whose Stripe confirmation was rejected\n // client-side. The next attempt must skip tCheckoutUpdate (it would fail\n // with CHECKOUT_NOT_OPEN) and go straight to tCheckoutPlaceOrder again.\n const [cardRetryBiId, setCardRetryBiId] = useState<string | null>(null);\n\n // Reset local overrides when the product selector changes.\n useEffect(() => {\n setCheckoutOverride(null);\n setCouponError(null);\n setPlaceOrderError(null);\n }, [selector]);\n\n const manualUpdateMutation = useUpdateCheckoutMutation(client);\n\n const placedOrderQuery = usePlacedOrderQuery(\n client,\n orderBiId,\n sessionReady && !!orderBiId,\n );\n\n const orderDatesQuery = useOrderDatesQuery(\n client,\n orderBiId,\n sessionReady && !!orderBiId,\n );\n\n const derived = deriveAlacartePhase({\n selector,\n sessionReady,\n sessionIsError: sessionQuery.isError,\n sessionErrorMessage: sessionQuery.error?.message,\n ownedQuery: {\n isLoading: ownedQuery.isLoading,\n isError: ownedQuery.isError,\n error: ownedQuery.error,\n data: ownedQuery.data,\n },\n selectMutation: {\n isIdle: selectMutation.isIdle,\n isPending: selectMutation.isPending,\n isError: selectMutation.isError,\n error: selectMutation.error,\n data: selectMutation.data,\n },\n checkoutQuery: {\n isLoading: checkoutQuery.isLoading,\n isError: checkoutQuery.isError,\n error: checkoutQuery.error,\n data: checkoutQuery.data,\n },\n productDetail: productDetailQuery.data ?? null,\n couponMutation: {\n isIdle: urlCouponMutation.isIdle,\n isPending: urlCouponMutation.isPending,\n isError: urlCouponMutation.isError,\n error: urlCouponMutation.error,\n data: urlCouponMutation.data,\n },\n checkoutOverride,\n // The URL coupon is pending only when the checkout exists, doesn't already\n // carry the code, and the mutation hasn't completed yet. When the backend\n // returns the coupon pre-applied (couponCode === discountCode) or when\n // there's no discount_code param at all, this is false from the start.\n urlCouponPending: !!discountCode\n && !!createdCheckout\n && createdCheckout.couponCode !== discountCode\n && (urlCouponMutation.isIdle || urlCouponMutation.isPending),\n });\n\n const applyCoupon = useCallback((code: string) => {\n const biId = derived.checkout?.biId;\n if (!biId || !client) return;\n const trimmed = code.trim();\n if (!trimmed) return;\n\n setCouponError(null);\n manualUpdateMutation.mutate(\n { biId, couponCode: trimmed },\n {\n onSuccess: (result) => {\n if (result.ok && result.checkout) {\n setCheckoutOverride(result.checkout);\n setCouponError(null);\n } else {\n setCouponError(getCouponErrorMessage(result.error));\n }\n },\n onError: (err) => {\n setCouponError(errorMessage(err));\n },\n },\n );\n }, [derived.checkout?.biId, client, manualUpdateMutation]);\n\n const removeCoupon = useCallback(() => {\n const currentCheckout = derived.checkout;\n if (!currentCheckout?.biId || !client) return;\n\n setCouponError(null);\n manualUpdateMutation.mutate(\n { biId: currentCheckout.biId, couponCode: '' },\n {\n onSuccess: (result) => {\n if (!result.ok) {\n setCouponError(getCouponErrorMessage(result.error));\n return;\n }\n // The backend may return a checkout whose line still carries the\n // old discount/amountDue. Always patch the line to clear the\n // coupon so the cart summary reflects the removal immediately.\n const base = result.checkout ?? currentCheckout;\n const line = base.line;\n const patchedLine = line\n ? {\n ...line,\n discount: null,\n amountDue: line.price && line.kind === 'program' && 'deduction' in line && line.deduction\n ? { amount: line.price.amount - line.deduction.amount, currency: line.price.currency }\n : line.price,\n } as typeof line\n : null;\n setCheckoutOverride({\n ...base,\n couponCode: null,\n line: patchedLine,\n });\n setCouponError(null);\n },\n onError: (err) => {\n setCouponError(errorMessage(err));\n },\n },\n );\n }, [derived.checkout, client, manualUpdateMutation]);\n\n const placeOrderCore = useCallback(async (opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => {\n const checkout = derived.checkout;\n if (!checkout || !client) throw new Error('Checkout not ready');\n\n setPlacingOrder(true);\n setPlaceOrderError(null);\n\n try {\n const { checkout: updated } = await placeAlacarteOrder({\n updateCheckout: (args) => client.updateCheckout(args),\n placeCheckoutOrder: (biId, billing) => client.placeCheckoutOrder(biId, billing),\n checkout,\n billing: toAlacarteBillingAddress(opts.billing),\n confirmCardPayment: opts.confirmCardPayment,\n skipPrepare: cardRetryBiId === checkout.biId,\n });\n\n setCardRetryBiId(null);\n setCheckoutOverride(updated);\n await setOrderBiId(updated.biId);\n } catch (err: unknown) {\n if (err instanceof CardValidationError) {\n // tCheckoutPlaceOrder already succeeded and locked the checkout\n // (CART_PAYMENT_PENDING); Stripe rejected the card before it ever\n // reached the backend. Next attempt must retry tCheckoutPlaceOrder\n // directly, not tCheckoutUpdate.\n setCardRetryBiId(checkout.biId);\n } else {\n setCardRetryBiId(null);\n }\n if (err instanceof AlacartePlaceOrderError && err.checkout) {\n setCheckoutOverride(err.checkout);\n }\n setPlaceOrderError(errorMessage(err));\n throw err;\n } finally {\n setPlacingOrder(false);\n }\n }, [derived.checkout, client, setOrderBiId, cardRetryBiId]);\n\n /** Fire-and-forget wrapper for card payments. */\n const placeOrder = useCallback((opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => {\n if (placingOrder) return;\n placeOrderCore(opts).catch(() => { /* error already in state */ });\n }, [placeOrderCore, placingOrder]);\n\n const placedOrder = placedOrderQuery.data?.ok ? placedOrderQuery.data.order : null;\n\n const dates = orderDatesQuery.data?.ok ? orderDatesQuery.data.dates : null;\n const placedOrderPlacedAt = dates?.tPlacedAt ?? dates?.biCreatedAt ?? null;\n\n const placedOrderError = (() => {\n if (!orderBiId) return null;\n if (\n placedOrderQuery.data\n && !placedOrderQuery.data.ok\n && placedOrderQuery.data.error !== 'NOT_PLACED'\n && placedOrderQuery.data.error !== 'ORDER_READ_FAILED'\n ) {\n return placedOrderQuery.data.error ?? 'ORDER_READ_FAILED';\n }\n return null;\n })();\n\n return {\n ...derived,\n couponError,\n couponApplying: manualUpdateMutation.isPending,\n applyCoupon,\n removeCoupon,\n clearCouponError: () => setCouponError(null),\n placingOrder,\n placeOrderError,\n placeOrder,\n placeOrderAsync: placeOrderCore,\n clearPlaceOrderError: () => setPlaceOrderError(null),\n placedOrder,\n awaitingPlacedOrder: !!orderBiId && !placedOrder && !placedOrderError && (\n placedOrderQuery.isLoading\n || placedOrderQuery.isFetching\n || placedOrderQuery.data?.error === 'NOT_PLACED'\n || placedOrderQuery.data?.error === 'ORDER_READ_FAILED'\n || (placedOrderQuery.data?.ok === true && !placedOrderQuery.data.order)\n || !placedOrderQuery.data\n ),\n placedOrderError,\n placedOrderPlacedAt,\n };\n}\n\nexport default useAlacarteCheckout;\n"]}
|
|
@@ -51,6 +51,13 @@ export declare function placeAlacarteOrder(args: {
|
|
|
51
51
|
checkout: AlacarteCheckout;
|
|
52
52
|
billing: AlacarteBillingAddress;
|
|
53
53
|
confirmCardPayment: (clientSecret: string) => Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Set when retrying after Stripe.js rejected the card client-side
|
|
56
|
+
* (CardValidationError). tCheckoutPlaceOrder already ran and locked the
|
|
57
|
+
* checkout into CART_PAYMENT_PENDING, so re-running tCheckoutUpdate here
|
|
58
|
+
* would fail with CHECKOUT_NOT_OPEN. Skip straight to tCheckoutPlaceOrder.
|
|
59
|
+
*/
|
|
60
|
+
skipPrepare?: boolean;
|
|
54
61
|
}): Promise<AlacartePlaceOrderResult>;
|
|
55
62
|
/** Prep only — used by tests that assert the update step. */
|
|
56
63
|
export declare function prepareAlacartePlaceOrder(args: {
|
|
@@ -49,7 +49,9 @@ export function toAlacarteBillingAddress(billing) {
|
|
|
49
49
|
export function placeAlacarteOrder(args) {
|
|
50
50
|
return __awaiter(this, void 0, void 0, function* () {
|
|
51
51
|
var _a, _b, _c, _d;
|
|
52
|
-
const checkout =
|
|
52
|
+
const checkout = args.skipPrepare
|
|
53
|
+
? args.checkout
|
|
54
|
+
: yield prepareAlacartePayment(args.updateCheckout, args.checkout);
|
|
53
55
|
if (!checkout.priceAccepted) {
|
|
54
56
|
throw new Error('Price must be accepted before payment.');
|
|
55
57
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alacartePayment.js","sourceRoot":"","sources":["../../src/utils/alacartePayment.ts"],"names":[],"mappings":";;;;;;;;;AAgBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AASvD;;;GAGG;AACH,MAAM,UAAgB,sBAAsB,CAC1C,cAAgC,EAChC,QAA0B;;;QAE1B,MAAM,MAAM,GAAG,MAAM,cAAc,iBACjC,IAAI,EAAE,QAAQ,CAAC,IAAI,EACnB,aAAa,EAAE,IAAI,IAChB,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EACxD,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,MAAA,MAAM,CAAC,KAAK,mCAAI,yCAAyC,CAAC,CAAC;QAC7E,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QAED,OAAO,MAAM,CAAC,QAAQ,CAAC;IACzB,CAAC;CAAA;AAOD;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAQxC;IACC,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;QAC7C,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,QAAQ,EAAE,OAAO,CAAC,GAAG;KACtB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAgB,kBAAkB,CAAC,
|
|
1
|
+
{"version":3,"file":"alacartePayment.js","sourceRoot":"","sources":["../../src/utils/alacartePayment.ts"],"names":[],"mappings":";;;;;;;;;AAgBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AASvD;;;GAGG;AACH,MAAM,UAAgB,sBAAsB,CAC1C,cAAgC,EAChC,QAA0B;;;QAE1B,MAAM,MAAM,GAAG,MAAM,cAAc,iBACjC,IAAI,EAAE,QAAQ,CAAC,IAAI,EACnB,aAAa,EAAE,IAAI,IAChB,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EACxD,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,MAAA,MAAM,CAAC,KAAK,mCAAI,yCAAyC,CAAC,CAAC;QAC7E,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QAED,OAAO,MAAM,CAAC,QAAQ,CAAC;IACzB,CAAC;CAAA;AAOD;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAQxC;IACC,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;QAC7C,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,QAAQ,EAAE,OAAO,CAAC,GAAG;KACtB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAgB,kBAAkB,CAAC,IAgBxC;;;QACC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW;YAC/B,CAAC,CAAC,IAAI,CAAC,QAAQ;YACf,CAAC,CAAC,MAAM,sBAAsB,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAErE,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC9C,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAE1E,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAA,MAAA,MAAM,CAAC,OAAO,0CAAE,YAAY,CAAA,EAAE,CAAC;YAChD,MAAM,iBAAiB,CACrB,MAAA,MAAM,CAAC,KAAK,mCAAI,qBAAqB,EACrC,MAAA,MAAM,CAAC,QAAQ,mCAAI,IAAI,CACxB,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAE3D,OAAO;YACL,QAAQ,EAAE,MAAA,MAAM,CAAC,QAAQ,mCAAI,QAAQ;YACrC,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC;IACJ,CAAC;CAAA;AAED,6DAA6D;AAC7D,MAAM,UAAgB,yBAAyB,CAAC,IAG/C;;QACC,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAClF,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC;CAAA","sourcesContent":["/**\n * Prepares an à la carte checkout and places the order via tCheckoutPlaceOrder.\n *\n * Sequence:\n * 1. tCheckoutUpdate(termsAccepted, acceptPrice?)\n * 2. tCheckoutPlaceOrder(biId, billing address) → Stripe client secret\n * 3. stripe.confirmCardPayment(clientSecret, { payment_method: { card, billing_details } })\n * 4. caller polls tPlacedOrder(biId) until the order is placed\n *\n * Do not call platform BI transitions (Sanction_Check / CART_PAYMENT_PENDING)\n * directly — tCheckoutPlaceOrder handles sanctions + payment start.\n */\nimport type {\n AlacarteBillingAddress, AlacarteCheckout, AlacarteCheckoutResult,\n AlacartePlaceOrderApiResult, AlacartePlaceOrderPayment,\n} from '../types/alacarte';\nimport { toPlaceOrderError } from './placeOrderErrors';\n\ntype UpdateCheckoutFn = (args: {\n biId?: string,\n couponCode?: string,\n termsAccepted?: boolean,\n acceptPrice?: boolean,\n}) => Promise<AlacarteCheckoutResult>;\n\n/**\n * Calls tCheckoutUpdate with termsAccepted (and acceptPrice when the cart\n * still needs price acceptance). Returns the updated checkout.\n */\nexport async function prepareAlacartePayment(\n updateCheckout: UpdateCheckoutFn,\n checkout: AlacarteCheckout,\n): Promise<AlacarteCheckout> {\n const result = await updateCheckout({\n biId: checkout.biId,\n termsAccepted: true,\n ...(checkout.priceAccepted ? {} : { acceptPrice: true }),\n });\n\n if (!result.ok || !result.checkout) {\n throw new Error(result.error ?? 'Could not prepare checkout for payment.');\n }\n\n if (!result.checkout.priceAccepted) {\n throw new Error('Price must be accepted before payment.');\n }\n\n return result.checkout;\n}\n\nexport interface AlacartePlaceOrderResult {\n checkout: AlacarteCheckout,\n payment: AlacartePlaceOrderPayment,\n}\n\n/**\n * Maps form BillingInfo onto tCheckoutPlaceOrder billing.\n * Only country is required by the API; other fields are included when present.\n */\nexport function toAlacarteBillingAddress(billing: {\n country: string,\n firstName?: string,\n lastName?: string,\n line1?: string,\n city?: string,\n state?: string,\n zip?: string,\n}): AlacarteBillingAddress {\n return {\n country: billing.country.trim().toUpperCase(),\n firstName: billing.firstName,\n lastName: billing.lastName,\n line1: billing.line1,\n city: billing.city,\n state: billing.state,\n postcode: billing.zip,\n };\n}\n\n/**\n * Full place-order sequence:\n * prep → tCheckoutPlaceOrder → Stripe confirmCardPayment\n *\n * Caller then sets ?order_bi_id= and polls tPlacedOrder.\n */\nexport async function placeAlacarteOrder(args: {\n updateCheckout: UpdateCheckoutFn,\n placeCheckoutOrder: (\n biId: string,\n billing?: AlacarteBillingAddress,\n ) => Promise<AlacartePlaceOrderApiResult>,\n checkout: AlacarteCheckout,\n billing: AlacarteBillingAddress,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n /**\n * Set when retrying after Stripe.js rejected the card client-side\n * (CardValidationError). tCheckoutPlaceOrder already ran and locked the\n * checkout into CART_PAYMENT_PENDING, so re-running tCheckoutUpdate here\n * would fail with CHECKOUT_NOT_OPEN. Skip straight to tCheckoutPlaceOrder.\n */\n skipPrepare?: boolean,\n}): Promise<AlacartePlaceOrderResult> {\n const checkout = args.skipPrepare\n ? args.checkout\n : await prepareAlacartePayment(args.updateCheckout, args.checkout);\n\n if (!checkout.priceAccepted) {\n throw new Error('Price must be accepted before payment.');\n }\n\n if (!args.billing.country) {\n throw new Error('Please select a country.');\n }\n\n const placed = await args.placeCheckoutOrder(checkout.biId, args.billing);\n\n if (!placed.ok || !placed.payment?.clientSecret) {\n throw toPlaceOrderError(\n placed.error ?? 'PAYMENT_NOT_STARTED',\n placed.checkout ?? null,\n );\n }\n\n await args.confirmCardPayment(placed.payment.clientSecret);\n\n return {\n checkout: placed.checkout ?? checkout,\n payment: placed.payment,\n };\n}\n\n/** Prep only — used by tests that assert the update step. */\nexport async function prepareAlacartePlaceOrder(args: {\n updateCheckout: UpdateCheckoutFn,\n checkout: AlacarteCheckout,\n}): Promise<{ checkout: AlacarteCheckout }> {\n const checkout = await prepareAlacartePayment(args.updateCheckout, args.checkout);\n return { checkout };\n}\n"]}
|
package/dist/utils/provision.js
CHANGED
|
@@ -21,7 +21,7 @@ import { EnvironmentTypes, getAppConfig, getSiteConfig } from '@openedx/frontend
|
|
|
21
21
|
import { appId } from '../constants';
|
|
22
22
|
/** Hardcoded edX JWT used only for local/dev checkout against stage Viax. */
|
|
23
23
|
const DEV_SESSION_BODY = {
|
|
24
|
-
payload: '
|
|
24
|
+
payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIdwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MDc5OTUzOCwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkwNzk1OTM4LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJBRF8yNjA5X0dQQ0EiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiIxZDMyZDQwN2YxMTJkZjNiMmRhNWRjNjkxNjM1NWIzYiIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1ODg5LCJlbWFpbCI6ImdpZ2FsYTgzODVAYnVsbGJhYnkuY29tIiwibmFtZSI6ImFrYXNoIGRldmdhbiIsImZhbWlseV9uYW1lIjoiIiwiZ2l2ZW5fbmFtZSI6IiIsImFkbWluaXN0cmF0b3IiOmZhbHNlLCJzdXBlcnVzZXIiOmZhbHNlfQ',
|
|
25
25
|
signature: 'QhmQkgQ6m-96Do32Qsol7wXq8uzvX5Qi43c98oS2BzQdMROZdY2ES98_l_NZ0UVzuYZzmcnerEi6HiWXxE1V6RS05aW0RjA4dLcO_MRU8v2TiARIo7Pl4s0q0p5rbO8vQ3lTUeSoCKfVdNNNPc1ODxYEKE0jLKUAE1Z9ZM1D7BceJHv1CGMMwnZpr4-EWJ2APsHkZ_q-8MNLIy61BI9yiOZDOtU4nn4Y0l7X6cFLcu2bzOMEKkzEk5ru0FtW-VZuHz84L5Ta6xEymAJqQeOh3f50GZqV5P4yV11s1GFdn6a1viBiYSToF0qVLfcLlDJtZM5XI8ae3BPwJII_6KNSlg'
|
|
26
26
|
};
|
|
27
27
|
const EDX_JWT_PAYLOAD_COOKIE_NAMES = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provision.js","sourceRoot":"","sources":["../../src/utils/provision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;;;;;;;;;;AAEH,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AA+BrC,6EAA6E;AAC7E,MAAM,gBAAgB,GAAG;IACvB,OAAO,EAAE,4uBAA4uB;IACrvB,SAAS,EAAE,wVAAwV;CACpW,CAAC;AAEF,MAAM,4BAA4B,GAAG;IACnC,qCAAqC;IACrC,+BAA+B;CAChC,CAAC;AAEF,MAAM,8BAA8B,GAAG;IACrC,gCAAgC;IAChC,0BAA0B;CAC3B,CAAC;AAEF,SAAS,cAAc,CAAC,IAAY;IAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM;SAC3B,KAAK,CAAC,IAAI,CAAC;SACX,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;IAE3D,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,OAAO,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAe;IAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,mBAAmB;IAC1B,MAAM,OAAO,GAAG,mBAAmB,CAAC,4BAA4B,CAAC,CAAC;IAClE,MAAM,SAAS,GAAG,mBAAmB,CAAC,8BAA8B,CAAC,CAAC;IAEtE,OAAO,OAAO,IAAI,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9D,CAAC;AAED,SAAS,oBAAoB,CAAC,IAA6B;;IACzD,MAAM,SAAS,GAAG,CAChB,MAAA,MAAA,MAAA,IAAI,CAAC,SAAS,mCAAI,IAAI,CAAC,KAAK,mCAAI,IAAI,CAAC,YAAY,mCAAI,IAAI,CAAC,WAAW,CAChD,CAAC;IACxB,MAAM,WAAW,GAAG,CAAC,MAAA,IAAI,CAAC,WAAW,mCAAI,IAAI,CAAC,YAAY,CAAuB,CAAC;IAElF,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACxE,IAAI,CAAC,WAAW;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAE3E,OAAO;QACL,SAAS;QACT,WAAW;QACX,QAAQ,EAAE,IAAI,CAAC,QAA8B;QAC7C,SAAS,EAAE,IAAI,CAAC,SAA+B;QAC/C,QAAQ,EAAE,IAAI,CAAC,QAA8B;QAC7C,KAAK,EAAE,IAAI,CAAC,KAA2B;KACxC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAgB,gBAAgB,CAAC,UAAkB,EAAE,QAAgB;;QACzE,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,UAAU,yBAAyB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,EAAE;gBAC5F,WAAW,EAAE,SAAS;aACvB,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YACzB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA6B,CAAC;YACzD,OAAO,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QAChF,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CAAA;AAED,oEAAoE;AACpE,MAAM,UAAgB,aAAa;;QACjC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,mCAAmC,EAAE;YAC3D,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;QAC9D,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAsB,CAAC;QAClD,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QAED,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;CAAA;AAED,2DAA2D;AAC3D,MAAM,UAAgB,kBAAkB,CACtC,cAAsB,EACtB,SAAiB;;QAEjB,MAAM,UAAU,GAAG,mBAAmB,EAAE,CAAC;QACzC,MAAM,WAAW,GAAG,UAAU;YAC5B,CAAC,iBAAG,UAAU,EAAE,SAAS,IAAK,UAAU,EACxC,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;QAE9B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,cAAc,EAAE;YACtC,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,SAAS;YACtB,OAAO,EAAE;gBACP,MAAM,EAAE,kBAAkB;gBAC1B,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;QAChE,CAAC;QAED,OAAO,GAAG,CAAC,IAAI,EAAgC,CAAC;IAClD,CAAC;CAAA;AAED,SAAS,aAAa;IACpB,OAAO,aAAa,EAAE,CAAC,WAAW,KAAK,gBAAgB,CAAC,WAAW,CAAC;AACtE,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAgB,eAAe,CAAC,UAAkB;;QACtD,2EAA2E;QAC3E,wEAAwE;QACxE,gEAAgE;QAChE,IAAI,aAAa,EAAE,EAAE,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;gBACtC,IAAI,SAAS,CAAC,gBAAgB,EAAE,CAAC;oBAC/B,MAAM,IAAI,GAAG,SAAS,CAAC,yBAAoD,CAAC;oBAC5E,IAAI,IAAI,EAAE,CAAC;wBACT,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC;oBACpC,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,WAAM,CAAC;gBACP,iFAAiF;YACnF,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAA2B,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;QAEvE,IAAI,GAAa,CAAC;QAClB,IAAI,aAAa,EAAE,EAAE,CAAC;YACpB,GAAG,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE;gBAC5B,MAAM,EAAE,MAAM;gBACd,WAAW,EAAE,SAAS;gBACtB,OAAO,kCACF,OAAO,KACV,cAAc,EAAE,kBAAkB,GACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC;aACvC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,GAAG,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE;gBAC5B,MAAM,EAAE,MAAM;gBACd,WAAW,EAAE,SAAS;gBACtB,OAAO;aACR,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,yCAAyC,GAAG,CAAC,MAAM,uCAAuC,CAC3F,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAkD,CAAC;QAC9E,IAAI,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAEpD,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;CAAA","sourcesContent":["/**\n * api/provision.ts — exchange the edX session for a viax token.\n *\n * {LMS_BASE_URL}/api/edx-session\n * The LMS reads the edX JWT from browser cookies, exchanges it with viax,\n * and returns a per-user token for authenticated API calls.\n *\n * Dev uses POST with a hardcoded JWT payload (local LMS cookie exchange is\n * unavailable). Stage and prod use POST and rely on the browser session cookies.\n */\n\nimport { EnvironmentTypes, getAppConfig, getSiteConfig } from '@openedx/frontend-base';\nimport { appId } from '../constants';\n\nexport interface EdxSessionResult {\n viaxToken: string, // raw JWT — add 'Bearer ' prefix before use\n customerUid: string,\n partyUid?: string,\n edxUserId?: string,\n username?: string,\n email?: string,\n}\n\nexport interface GeolocationResult {\n status: 'resolved' | 'not_found' | 'error',\n ip_address: string,\n ip_address_type: string | null,\n country_code: string | null,\n country_name: string | null,\n region_code: string | null,\n region_name: string | null,\n city: string | null,\n postal_code: string | null,\n latitude: number | null,\n longitude: number | null,\n accuracy_radius_km: number | null,\n confidence: string | null,\n source: string | null,\n cache_status: string | null,\n looked_up_at: string | null,\n fallback_reason: string | null,\n}\n\n/** Hardcoded edX JWT used only for local/dev checkout against stage Viax. */\nconst DEV_SESSION_BODY = {\n payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIiwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MDc5OTUzOCwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkwNzk1OTM4LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJBRF8yNjA5X0dQQ0EiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiIxZDMyZDQwN2YxMTJkZjNiMmRhNWRjNjkxNjM1NWIzYiIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1ODg5LCJlbWFpbCI6ImdpZ2FsYTgzODVAYnVsbGJhYnkuY29tIiwibmFtZSI6ImFrYXNoIGRldmdhbiIsImZhbWlseV9uYW1lIjoiIiwiZ2l2ZW5fbmFtZSI6IiIsImFkbWluaXN0cmF0b3IiOmZhbHNlLCJzdXBlcnVzZXIiOmZhbHNlfQ',\n signature: 'QhmQkgQ6m-96Do32Qsol7wXq8uzvX5Qi43c98oS2BzQdMROZdY2ES98_l_NZ0UVzuYZzmcnerEi6HiWXxE1V6RS05aW0RjA4dLcO_MRU8v2TiARIo7Pl4s0q0p5rbO8vQ3lTUeSoCKfVdNNNPc1ODxYEKE0jLKUAE1Z9ZM1D7BceJHv1CGMMwnZpr4-EWJ2APsHkZ_q-8MNLIy61BI9yiOZDOtU4nn4Y0l7X6cFLcu2bzOMEKkzEk5ru0FtW-VZuHz84L5Ta6xEymAJqQeOh3f50GZqV5P4yV11s1GFdn6a1viBiYSToF0qVLfcLlDJtZM5XI8ae3BPwJII_6KNSlg'\n};\n\nconst EDX_JWT_PAYLOAD_COOKIE_NAMES = [\n 'stage-edx-jwt-cookie-header-payload',\n 'edx-jwt-cookie-header-payload',\n];\n\nconst EDX_JWT_SIGNATURE_COOKIE_NAMES = [\n 'stage-edx-jwt-cookie-signature',\n 'edx-jwt-cookie-signature',\n];\n\nfunction getCookieValue(name: string): string | null {\n const cookie = document.cookie\n .split('; ')\n .find((cookiePart) => cookiePart.startsWith(`${name}=`));\n\n if (!cookie) return null;\n\n return decodeURIComponent(cookie.substring(name.length + 1));\n}\n\nfunction getFirstCookieValue(names: string[]): string | null {\n for (const name of names) {\n const value = getCookieValue(name);\n if (value) return value;\n }\n\n return null;\n}\n\nfunction getEdxJwtCookieBody(): { payload: string, signature: string } | null {\n const payload = getFirstCookieValue(EDX_JWT_PAYLOAD_COOKIE_NAMES);\n const signature = getFirstCookieValue(EDX_JWT_SIGNATURE_COOKIE_NAMES);\n\n return payload && signature ? { payload, signature } : null;\n}\n\nfunction parseSessionResponse(data: Record<string, unknown>): EdxSessionResult {\n const viaxToken = (\n data.viaxToken ?? data.token ?? data.access_token ?? data.accessToken\n ) as string | undefined;\n const customerUid = (data.customerUid ?? data.customer_uid) as string | undefined;\n\n if (!viaxToken) throw new Error('Session response missing viax token.');\n if (!customerUid) throw new Error('Session response missing customerUid.');\n\n return {\n viaxToken,\n customerUid,\n partyUid: data.partyUid as string | undefined,\n edxUserId: data.edxUserId as string | undefined,\n username: data.username as string | undefined,\n email: data.email as string | undefined,\n };\n}\n\n/**\n * Fetch the ISO 3166-1 alpha-2 country code from the user's edX account profile.\n * Returns null if the request fails or the field is absent.\n */\nexport async function fetchUserCountry(lmsBaseUrl: string, username: string): Promise<string | null> {\n try {\n const res = await fetch(`${lmsBaseUrl}/api/user/v1/accounts/${encodeURIComponent(username)}`, {\n credentials: 'include',\n });\n if (!res.ok) return null;\n const data = await res.json() as Record<string, unknown>;\n return typeof data.country === 'string' && data.country ? data.country : null;\n } catch {\n return null;\n }\n}\n\n/** Fetch the learner's current public IP for geolocation lookup. */\nexport async function fetchPublicIp(): Promise<string> {\n const res = await fetch('https://api.ipify.org?format=json', {\n headers: { Accept: 'application/json' },\n });\n\n if (!res.ok) {\n throw new Error(`Public IP lookup failed (${res.status}).`);\n }\n\n const data = await res.json() as { ip?: unknown };\n if (typeof data.ip !== 'string' || !data.ip) {\n throw new Error('Public IP lookup returned an invalid address.');\n }\n\n return data.ip;\n}\n\n/** Look up geolocation data for an explicit IP address. */\nexport async function fetchIpGeolocation(\n geolocationUrl: string,\n ipAddress: string,\n): Promise<GeolocationResult> {\n const edxJwtBody = getEdxJwtCookieBody();\n const requestBody = edxJwtBody\n ? { ip_address: ipAddress, ...edxJwtBody }\n : { ip_address: ipAddress };\n\n const res = await fetch(geolocationUrl, {\n method: 'POST',\n credentials: 'include',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!res.ok) {\n throw new Error(`Geolocation lookup failed (${res.status}).`);\n }\n\n return res.json() as Promise<GeolocationResult>;\n}\n\nfunction isDevelopment(): boolean {\n return getSiteConfig().environment === EnvironmentTypes.DEVELOPMENT;\n}\n\n/** Exchange the learner's edX session (cookies) for a viax token. */\nexport async function fetchEdxSession(sessionUrl: string): Promise<EdxSessionResult> {\n // When DEV_MOCK_SESSION is true, skip the network call entirely and return\n // the hardcoded response from site config. This avoids CORS errors when\n // testing over HTTPS locally (e.g. for Apple Pay / Google Pay).\n if (isDevelopment()) {\n try {\n const appConfig = getAppConfig(appId);\n if (appConfig.DEV_MOCK_SESSION) {\n const mock = appConfig.DEV_MOCK_SESSION_RESPONSE as Record<string, unknown>;\n if (mock) {\n return parseSessionResponse(mock);\n }\n }\n } catch {\n // getAppConfig may throw if called before init — fall through to the real fetch.\n }\n }\n\n const headers: Record<string, string> = { Accept: 'application/json' };\n\n let res: Response;\n if (isDevelopment()) {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers: {\n ...headers,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(DEV_SESSION_BODY),\n });\n } else {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers,\n });\n }\n\n if (!res.ok) {\n throw new Error(\n `Failed to establish checkout session (${res.status}). Please log into edX and try again.`,\n );\n }\n\n const data = await res.json() as Record<string, unknown> & { error?: string };\n if (data.error) throw new Error(String(data.error));\n\n return parseSessionResponse(data);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"provision.js","sourceRoot":"","sources":["../../src/utils/provision.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/provision.ts — exchange the edX session for a viax token.\n *\n * {LMS_BASE_URL}/api/edx-session\n * The LMS reads the edX JWT from browser cookies, exchanges it with viax,\n * and returns a per-user token for authenticated API calls.\n *\n * Dev uses POST with a hardcoded JWT payload (local LMS cookie exchange is\n * unavailable). Stage and prod use POST and rely on the browser session cookies.\n */\n\nimport { EnvironmentTypes, getAppConfig, getSiteConfig } from '@openedx/frontend-base';\nimport { appId } from '../constants';\n\nexport interface EdxSessionResult {\n viaxToken: string, // raw JWT — add 'Bearer ' prefix before use\n customerUid: string,\n partyUid?: string,\n edxUserId?: string,\n username?: string,\n email?: string,\n}\n\nexport interface GeolocationResult {\n status: 'resolved' | 'not_found' | 'error',\n ip_address: string,\n ip_address_type: string | null,\n country_code: string | null,\n country_name: string | null,\n region_code: string | null,\n region_name: string | null,\n city: string | null,\n postal_code: string | null,\n latitude: number | null,\n longitude: number | null,\n accuracy_radius_km: number | null,\n confidence: string | null,\n source: string | null,\n cache_status: string | null,\n looked_up_at: string | null,\n fallback_reason: string | null,\n}\n\n/** Hardcoded edX JWT used only for local/dev checkout against stage Viax. */\nconst DEV_SESSION_BODY = {\n payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIdwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MDc5OTUzOCwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkwNzk1OTM4LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJBRF8yNjA5X0dQQ0EiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiIxZDMyZDQwN2YxMTJkZjNiMmRhNWRjNjkxNjM1NWIzYiIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1ODg5LCJlbWFpbCI6ImdpZ2FsYTgzODVAYnVsbGJhYnkuY29tIiwibmFtZSI6ImFrYXNoIGRldmdhbiIsImZhbWlseV9uYW1lIjoiIiwiZ2l2ZW5fbmFtZSI6IiIsImFkbWluaXN0cmF0b3IiOmZhbHNlLCJzdXBlcnVzZXIiOmZhbHNlfQ',\n signature: 'QhmQkgQ6m-96Do32Qsol7wXq8uzvX5Qi43c98oS2BzQdMROZdY2ES98_l_NZ0UVzuYZzmcnerEi6HiWXxE1V6RS05aW0RjA4dLcO_MRU8v2TiARIo7Pl4s0q0p5rbO8vQ3lTUeSoCKfVdNNNPc1ODxYEKE0jLKUAE1Z9ZM1D7BceJHv1CGMMwnZpr4-EWJ2APsHkZ_q-8MNLIy61BI9yiOZDOtU4nn4Y0l7X6cFLcu2bzOMEKkzEk5ru0FtW-VZuHz84L5Ta6xEymAJqQeOh3f50GZqV5P4yV11s1GFdn6a1viBiYSToF0qVLfcLlDJtZM5XI8ae3BPwJII_6KNSlg'\n};\n\nconst EDX_JWT_PAYLOAD_COOKIE_NAMES = [\n 'stage-edx-jwt-cookie-header-payload',\n 'edx-jwt-cookie-header-payload',\n];\n\nconst EDX_JWT_SIGNATURE_COOKIE_NAMES = [\n 'stage-edx-jwt-cookie-signature',\n 'edx-jwt-cookie-signature',\n];\n\nfunction getCookieValue(name: string): string | null {\n const cookie = document.cookie\n .split('; ')\n .find((cookiePart) => cookiePart.startsWith(`${name}=`));\n\n if (!cookie) return null;\n\n return decodeURIComponent(cookie.substring(name.length + 1));\n}\n\nfunction getFirstCookieValue(names: string[]): string | null {\n for (const name of names) {\n const value = getCookieValue(name);\n if (value) return value;\n }\n\n return null;\n}\n\nfunction getEdxJwtCookieBody(): { payload: string, signature: string } | null {\n const payload = getFirstCookieValue(EDX_JWT_PAYLOAD_COOKIE_NAMES);\n const signature = getFirstCookieValue(EDX_JWT_SIGNATURE_COOKIE_NAMES);\n\n return payload && signature ? { payload, signature } : null;\n}\n\nfunction parseSessionResponse(data: Record<string, unknown>): EdxSessionResult {\n const viaxToken = (\n data.viaxToken ?? data.token ?? data.access_token ?? data.accessToken\n ) as string | undefined;\n const customerUid = (data.customerUid ?? data.customer_uid) as string | undefined;\n\n if (!viaxToken) throw new Error('Session response missing viax token.');\n if (!customerUid) throw new Error('Session response missing customerUid.');\n\n return {\n viaxToken,\n customerUid,\n partyUid: data.partyUid as string | undefined,\n edxUserId: data.edxUserId as string | undefined,\n username: data.username as string | undefined,\n email: data.email as string | undefined,\n };\n}\n\n/**\n * Fetch the ISO 3166-1 alpha-2 country code from the user's edX account profile.\n * Returns null if the request fails or the field is absent.\n */\nexport async function fetchUserCountry(lmsBaseUrl: string, username: string): Promise<string | null> {\n try {\n const res = await fetch(`${lmsBaseUrl}/api/user/v1/accounts/${encodeURIComponent(username)}`, {\n credentials: 'include',\n });\n if (!res.ok) return null;\n const data = await res.json() as Record<string, unknown>;\n return typeof data.country === 'string' && data.country ? data.country : null;\n } catch {\n return null;\n }\n}\n\n/** Fetch the learner's current public IP for geolocation lookup. */\nexport async function fetchPublicIp(): Promise<string> {\n const res = await fetch('https://api.ipify.org?format=json', {\n headers: { Accept: 'application/json' },\n });\n\n if (!res.ok) {\n throw new Error(`Public IP lookup failed (${res.status}).`);\n }\n\n const data = await res.json() as { ip?: unknown };\n if (typeof data.ip !== 'string' || !data.ip) {\n throw new Error('Public IP lookup returned an invalid address.');\n }\n\n return data.ip;\n}\n\n/** Look up geolocation data for an explicit IP address. */\nexport async function fetchIpGeolocation(\n geolocationUrl: string,\n ipAddress: string,\n): Promise<GeolocationResult> {\n const edxJwtBody = getEdxJwtCookieBody();\n const requestBody = edxJwtBody\n ? { ip_address: ipAddress, ...edxJwtBody }\n : { ip_address: ipAddress };\n\n const res = await fetch(geolocationUrl, {\n method: 'POST',\n credentials: 'include',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!res.ok) {\n throw new Error(`Geolocation lookup failed (${res.status}).`);\n }\n\n return res.json() as Promise<GeolocationResult>;\n}\n\nfunction isDevelopment(): boolean {\n return getSiteConfig().environment === EnvironmentTypes.DEVELOPMENT;\n}\n\n/** Exchange the learner's edX session (cookies) for a viax token. */\nexport async function fetchEdxSession(sessionUrl: string): Promise<EdxSessionResult> {\n // When DEV_MOCK_SESSION is true, skip the network call entirely and return\n // the hardcoded response from site config. This avoids CORS errors when\n // testing over HTTPS locally (e.g. for Apple Pay / Google Pay).\n if (isDevelopment()) {\n try {\n const appConfig = getAppConfig(appId);\n if (appConfig.DEV_MOCK_SESSION) {\n const mock = appConfig.DEV_MOCK_SESSION_RESPONSE as Record<string, unknown>;\n if (mock) {\n return parseSessionResponse(mock);\n }\n }\n } catch {\n // getAppConfig may throw if called before init — fall through to the real fetch.\n }\n }\n\n const headers: Record<string, string> = { Accept: 'application/json' };\n\n let res: Response;\n if (isDevelopment()) {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers: {\n ...headers,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(DEV_SESSION_BODY),\n });\n } else {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers,\n });\n }\n\n if (!res.ok) {\n throw new Error(\n `Failed to establish checkout session (${res.status}). Please log into edX and try again.`,\n );\n }\n\n const data = await res.json() as Record<string, unknown> & { error?: string };\n if (data.error) throw new Error(String(data.error));\n\n return parseSessionResponse(data);\n}\n"]}
|