@edx/frontend-app-subscription-checkout 1.15.0 → 1.16.1
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/checkoutPage/CheckoutPage.js +2 -6
- package/dist/checkoutPage/CheckoutPage.js.map +1 -1
- package/dist/components/CartSummary/CartSummary.js +2 -2
- package/dist/components/CartSummary/CartSummary.js.map +1 -1
- package/dist/components/Checkout/Checkout.d.ts +2 -0
- package/dist/components/Checkout/Checkout.js +7 -3
- package/dist/components/Checkout/Checkout.js.map +1 -1
- package/dist/components/Checkout/Messages.d.ts +8 -0
- package/dist/components/Checkout/Messages.js +10 -0
- package/dist/components/Checkout/Messages.js.map +1 -0
- package/dist/components/CheckoutGate/CheckoutGate.js +7 -3
- package/dist/components/CheckoutGate/CheckoutGate.js.map +1 -1
- package/dist/components/CheckoutGate/Messages.d.ts +8 -0
- package/dist/components/CheckoutGate/Messages.js +10 -0
- package/dist/components/CheckoutGate/Messages.js.map +1 -0
- package/dist/components/PaymentForm/Messages.d.ts +6 -1
- package/dist/components/PaymentForm/Messages.js +9 -4
- package/dist/components/PaymentForm/Messages.js.map +1 -1
- package/dist/components/PaymentForm/PaymentForm.d.ts +4 -3
- package/dist/components/PaymentForm/PaymentForm.js +40 -12
- package/dist/components/PaymentForm/PaymentForm.js.map +1 -1
- package/dist/constants.d.ts +2 -3
- package/dist/constants.js +6 -5
- package/dist/constants.js.map +1 -1
- package/dist/data/hooks/useCheckout.d.ts +2 -2
- package/dist/data/hooks/useCheckout.js +9 -2
- package/dist/data/hooks/useCheckout.js.map +1 -1
- package/dist/data/hooks/useCheckoutGate.d.ts +4 -3
- package/dist/data/hooks/useCheckoutGate.js +28 -6
- package/dist/data/hooks/useCheckoutGate.js.map +1 -1
- package/dist/data/hooks/useCheckoutMutations.d.ts +2 -2
- package/dist/data/hooks/useCheckoutMutations.js +4 -3
- package/dist/data/hooks/useCheckoutMutations.js.map +1 -1
- package/dist/data/hooks/useCheckoutQueries.d.ts +2 -0
- package/dist/data/hooks/useCheckoutQueries.js +10 -0
- package/dist/data/hooks/useCheckoutQueries.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/checkoutErrors.d.ts +1 -0
- package/dist/utils/checkoutErrors.js +11 -0
- package/dist/utils/checkoutErrors.js.map +1 -0
- package/dist/utils/provision.d.ts +24 -1
- package/dist/utils/provision.js +69 -3
- package/dist/utils/provision.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,16 +1,28 @@
|
|
|
1
|
+
import { useEffect, useMemo } from 'react';
|
|
1
2
|
import { ALLOWED_CHECKOUT_COUNTRIES, GATE_CHECK_COUNTRY, } from '../../constants';
|
|
2
|
-
import {
|
|
3
|
+
import { createCheckoutClient } from '../../utils/checkout';
|
|
4
|
+
import { getLearnerFacingCheckoutMessage } from '../../utils/checkoutErrors';
|
|
5
|
+
import { useCurrentUserSubscriberQuery, useViaxSessionQuery, useUserCountryQuery, } from './useCheckoutQueries';
|
|
3
6
|
export function useCheckoutGate(options = {}) {
|
|
4
|
-
var _a, _b, _c;
|
|
5
|
-
const { edxSessionUrl, learnerDashboardUrl, } = options;
|
|
7
|
+
var _a, _b, _c, _d;
|
|
8
|
+
const { apiUrl, edxSessionUrl, learnerDashboardUrl, } = options;
|
|
9
|
+
const client = useMemo(() => (apiUrl ? createCheckoutClient({ apiUrl }) : null), [apiUrl]);
|
|
6
10
|
const sessionQuery = useViaxSessionQuery(edxSessionUrl !== null && edxSessionUrl !== void 0 ? edxSessionUrl : null);
|
|
7
11
|
const sessionReady = !!sessionQuery.data;
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (sessionQuery.data && client) {
|
|
14
|
+
client.setToken(sessionQuery.data.viaxToken);
|
|
15
|
+
}
|
|
16
|
+
}, [sessionQuery.data, client]);
|
|
17
|
+
const currentUserSubscriberQuery = useCurrentUserSubscriberQuery(client, sessionReady);
|
|
8
18
|
const userCountryQuery = useUserCountryQuery(sessionReady && GATE_CHECK_COUNTRY ? (edxSessionUrl !== null && edxSessionUrl !== void 0 ? edxSessionUrl : null) : null, (_b = (_a = sessionQuery.data) === null || _a === void 0 ? void 0 : _a.username) !== null && _b !== void 0 ? _b : null);
|
|
9
19
|
const userCountry = (_c = userCountryQuery.data) !== null && _c !== void 0 ? _c : null;
|
|
20
|
+
const isExistingSubscriber = ((_d = currentUserSubscriberQuery.data) === null || _d === void 0 ? void 0 : _d.isSubscriber) === true;
|
|
10
21
|
// Use isLoading (isPending && isFetching), not isPending alone.
|
|
11
22
|
// In TanStack Query v5, disabled queries with no data stay isPending forever —
|
|
12
23
|
// e.g. country check when username is missing from the session response.
|
|
13
24
|
const loading = (!sessionReady && !sessionQuery.isError)
|
|
25
|
+
|| currentUserSubscriberQuery.isLoading
|
|
14
26
|
|| (GATE_CHECK_COUNTRY && userCountryQuery.isLoading);
|
|
15
27
|
// GATE_CHECK_COUNTRY=false → treat all countries as allowed.
|
|
16
28
|
const isCountryAllowed = !GATE_CHECK_COUNTRY
|
|
@@ -23,17 +35,27 @@ export function useCheckoutGate(options = {}) {
|
|
|
23
35
|
else if (loading) {
|
|
24
36
|
gateStatus = 'loading';
|
|
25
37
|
}
|
|
38
|
+
else if (isExistingSubscriber) {
|
|
39
|
+
gateStatus = 'existingSubscriber';
|
|
40
|
+
}
|
|
26
41
|
else if (!isCountryAllowed) {
|
|
27
42
|
gateStatus = 'blocked';
|
|
28
43
|
}
|
|
29
44
|
else {
|
|
30
45
|
gateStatus = 'allowed';
|
|
31
46
|
}
|
|
32
|
-
const
|
|
47
|
+
const gateMessage = gateStatus === 'error'
|
|
33
48
|
? (sessionQuery.error instanceof Error
|
|
34
49
|
? sessionQuery.error.message
|
|
35
50
|
: 'Failed to establish checkout session.')
|
|
36
|
-
:
|
|
37
|
-
|
|
51
|
+
: gateStatus === 'existingSubscriber'
|
|
52
|
+
? getLearnerFacingCheckoutMessage('duplicate_active_subscription')
|
|
53
|
+
: null;
|
|
54
|
+
return {
|
|
55
|
+
gateStatus,
|
|
56
|
+
userCountry,
|
|
57
|
+
learnerDashboardUrl,
|
|
58
|
+
gateMessage,
|
|
59
|
+
};
|
|
38
60
|
}
|
|
39
61
|
//# sourceMappingURL=useCheckoutGate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCheckoutGate.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutGate.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"useCheckoutGate.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutGate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAC;AAC7E,OAAO,EACL,6BAA6B,EAC7B,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAoB9B,MAAM,UAAU,eAAe,CAAC,UAAkC,EAAE;;IAClE,MAAM,EACJ,MAAM,EACN,aAAa,EACb,mBAAmB,GACpB,GAAG,OAAO,CAAC;IAEZ,MAAM,MAAM,GAAG,OAAO,CACpB,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EACxD,CAAC,MAAM,CAAC,CACT,CAAC;IACF,MAAM,YAAY,GAAG,mBAAmB,CAAC,aAAa,aAAb,aAAa,cAAb,aAAa,GAAI,IAAI,CAAC,CAAC;IAChE,MAAM,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC;IAEzC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,YAAY,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC;YAChC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAEhC,MAAM,0BAA0B,GAAG,6BAA6B,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACvF,MAAM,gBAAgB,GAAG,mBAAmB,CAC1C,YAAY,IAAI,kBAAkB,CAAC,CAAC,CAAC,CAAC,aAAa,aAAb,aAAa,cAAb,aAAa,GAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EACnE,MAAA,MAAA,YAAY,CAAC,IAAI,0CAAE,QAAQ,mCAAI,IAAI,CACpC,CAAC;IAEF,MAAM,WAAW,GAAG,MAAA,gBAAgB,CAAC,IAAI,mCAAI,IAAI,CAAC;IAClD,MAAM,oBAAoB,GAAG,CAAA,MAAA,0BAA0B,CAAC,IAAI,0CAAE,YAAY,MAAK,IAAI,CAAC;IAEpF,gEAAgE;IAChE,+EAA+E;IAC/E,yEAAyE;IACzE,MAAM,OAAO,GAAG,CAAC,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC;WACnD,0BAA0B,CAAC,SAAS;WACpC,CAAC,kBAAkB,IAAI,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAExD,6DAA6D;IAC7D,MAAM,gBAAgB,GAAG,CAAC,kBAAkB;WACvC,WAAW,KAAK,IAAI;WACpB,0BAA0B,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAEtD,IAAI,UAAsB,CAAC;IAC3B,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;QACzB,UAAU,GAAG,OAAO,CAAC;IACvB,CAAC;SAAM,IAAI,OAAO,EAAE,CAAC;QACnB,UAAU,GAAG,SAAS,CAAC;IACzB,CAAC;SAAM,IAAI,oBAAoB,EAAE,CAAC;QAChC,UAAU,GAAG,oBAAoB,CAAC;IACpC,CAAC;SAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC7B,UAAU,GAAG,SAAS,CAAC;IACzB,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,SAAS,CAAC;IACzB,CAAC;IAED,MAAM,WAAW,GAAG,UAAU,KAAK,OAAO;QACxC,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,YAAY,KAAK;YAClC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO;YAC5B,CAAC,CAAC,uCAAuC,CAAC;QAC9C,CAAC,CAAC,UAAU,KAAK,oBAAoB;YACnC,CAAC,CAAC,+BAA+B,CAAC,+BAA+B,CAAC;YAClE,CAAC,CAAC,IAAI,CAAC;IAEX,OAAO;QACL,UAAU;QACV,WAAW;QACX,mBAAmB;QACnB,WAAW;KACZ,CAAC;AACJ,CAAC","sourcesContent":["import { useEffect, useMemo } from 'react';\nimport {\n ALLOWED_CHECKOUT_COUNTRIES,\n GATE_CHECK_COUNTRY,\n} from '../../constants';\nimport { createCheckoutClient } from '../../utils/checkout';\nimport { getLearnerFacingCheckoutMessage } from '../../utils/checkoutErrors';\nimport {\n useCurrentUserSubscriberQuery,\n useViaxSessionQuery,\n useUserCountryQuery,\n} from './useCheckoutQueries';\n\nexport interface UseCheckoutGateOptions {\n apiUrl?: string,\n edxSessionUrl?: string,\n learnerDashboardUrl?: string,\n}\n\nexport type GateStatus = 'loading' | 'blocked' | 'allowed' | 'error' | 'existingSubscriber';\n\nexport interface UseCheckoutGateResult {\n /** 'loading' while session/checks are in flight, 'blocked' if country not allowed, 'allowed' if clear, 'error' if session failed. */\n gateStatus: GateStatus,\n /** ISO 3166-1 alpha-2 country code — passed down to Checkout for the UK/CA checkbox. */\n userCountry: string | null,\n learnerDashboardUrl: string | undefined,\n /** Message shown when gateStatus is not 'allowed'. */\n gateMessage: string | null,\n}\n\nexport function useCheckoutGate(options: UseCheckoutGateOptions = {}): UseCheckoutGateResult {\n const {\n apiUrl,\n edxSessionUrl,\n learnerDashboardUrl,\n } = options;\n\n const client = useMemo(\n () => (apiUrl ? createCheckoutClient({ apiUrl }) : null),\n [apiUrl],\n );\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 currentUserSubscriberQuery = useCurrentUserSubscriberQuery(client, sessionReady);\n const userCountryQuery = useUserCountryQuery(\n sessionReady && GATE_CHECK_COUNTRY ? (edxSessionUrl ?? null) : null,\n sessionQuery.data?.username ?? null,\n );\n\n const userCountry = userCountryQuery.data ?? null;\n const isExistingSubscriber = currentUserSubscriberQuery.data?.isSubscriber === true;\n\n // Use isLoading (isPending && isFetching), not isPending alone.\n // In TanStack Query v5, disabled queries with no data stay isPending forever —\n // e.g. country check when username is missing from the session response.\n const loading = (!sessionReady && !sessionQuery.isError)\n || currentUserSubscriberQuery.isLoading\n || (GATE_CHECK_COUNTRY && userCountryQuery.isLoading);\n\n // GATE_CHECK_COUNTRY=false → treat all countries as allowed.\n const isCountryAllowed = !GATE_CHECK_COUNTRY\n || userCountry === null\n || ALLOWED_CHECKOUT_COUNTRIES.includes(userCountry);\n\n let gateStatus: GateStatus;\n if (sessionQuery.isError) {\n gateStatus = 'error';\n } else if (loading) {\n gateStatus = 'loading';\n } else if (isExistingSubscriber) {\n gateStatus = 'existingSubscriber';\n } else if (!isCountryAllowed) {\n gateStatus = 'blocked';\n } else {\n gateStatus = 'allowed';\n }\n\n const gateMessage = gateStatus === 'error'\n ? (sessionQuery.error instanceof Error\n ? sessionQuery.error.message\n : 'Failed to establish checkout session.')\n : gateStatus === 'existingSubscriber'\n ? getLearnerFacingCheckoutMessage('duplicate_active_subscription')\n : null;\n\n return {\n gateStatus,\n userCountry,\n learnerDashboardUrl,\n gateMessage,\n };\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RefObject } from 'react';
|
|
2
2
|
import type { EdxSessionResult } from '../../utils/provision';
|
|
3
|
-
import type { CheckoutClient, StripeLike, StripeElementLike, Cart, BillingInfo, CheckoutConsent, BillingCycle,
|
|
3
|
+
import type { CheckoutClient, StripeLike, StripeElementLike, Cart, BillingInfo, CheckoutConsent, BillingCycle, SupportedPaymentMethod } from '../../types';
|
|
4
4
|
export interface CheckoutMutationsCtx {
|
|
5
5
|
client: CheckoutClient | null;
|
|
6
6
|
sessionRef: RefObject<EdxSessionResult | null>;
|
|
@@ -15,7 +15,7 @@ export interface CheckoutMutationsCtx {
|
|
|
15
15
|
}
|
|
16
16
|
export interface PlaceOrderArgs {
|
|
17
17
|
consent: CheckoutConsent;
|
|
18
|
-
paymentMethod:
|
|
18
|
+
paymentMethod: SupportedPaymentMethod;
|
|
19
19
|
}
|
|
20
20
|
export declare function useCheckoutMutations(ctx: CheckoutMutationsCtx): {
|
|
21
21
|
placeOrder: import("@tanstack/react-query").UseMutationResult<Cart, Error, PlaceOrderArgs, unknown>;
|
|
@@ -81,9 +81,10 @@ export function useCheckoutMutations(ctx) {
|
|
|
81
81
|
const { client, billingRef } = ctx;
|
|
82
82
|
if (!client)
|
|
83
83
|
throw new Error('Checkout is not ready yet.');
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
// Google Pay is not currently supported.
|
|
85
|
+
// if (paymentMethod === 'googlepay') {
|
|
86
|
+
// throw new Error('Google Pay is not supported yet. Please choose Card or PayPal.');
|
|
87
|
+
// }
|
|
87
88
|
const billing = billingRef.current;
|
|
88
89
|
if (!billing)
|
|
89
90
|
throw new Error('Billing information is not available. Please refresh and try again.');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCheckoutMutations.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutMutations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * query/useCheckoutMutations.ts — write-side TanStack mutations.\n *\n * The placeOrder mutation drives the checkout sequence on button click.\n * The viax token and customerUid are established on page load via\n * GET /edx-session and passed in through sessionRef.\n *\n * Card:\n * 1. getSalableUid + getBillingPlans (parallel)\n * 2. createTwouCart\n * 3. stripe.createPaymentMethod → pm_xxx\n * 4. updateTwouCartItem → billing address + pm_xxx\n * 5. updateTwouCart → consent fields (SUBS-228)\n * 6. startBusinessProcessableTransitionV2('Sanction_Check')\n *\n * PayPal (mirrors the card sequence, skipping card tokenization):\n * 1. getSalableUid + getBillingPlans (parallel)\n * 2. createTwouCart\n * 3. updateTwouCartItem → billing address + TwouPaypalPayment\n * 4. updateTwouCart → consent fields + return URL (SUBS-228)\n * 5. startBusinessProcessableTransitionV2('Sanction_Check')\n * The backend then drives bpStatus to STRIPE_CONFIRMATION_REQUIRED and\n * populates tPaypalApprovalUrl, which useCheckout redirects to as the\n * hosted confirmation step for PayPal.\n */\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport type { RefObject } from 'react';\nimport { STATUS } from '../../constants';\nimport { checkoutKeys } from './keys';\nimport type { EdxSessionResult } from '../../utils/provision';\nimport { getPlanPrice } from '../../utils/billingDetails';\nimport { buildStripeReturnUrl } from '../../utils/stripe';\nimport type {\n CheckoutClient, StripeLike, StripeElementLike, Cart, BillingInfo,\n CartBillingAddress, CartStatusResult, CheckoutConsent, BillingCycle, PaymentMethod,\n} from '../../types';\n\nclass CardValidationError extends Error {}\n\nfunction toCartAddress(b: BillingInfo): CartBillingAddress {\n return {\n tFirstName: b.firstName,\n tLastName: b.lastName,\n upaLine1: b.line1,\n upaCity: b.city,\n upaPostcode: b.zip,\n gadStateOrProvince: b.state,\n gadCountry: { iso2Code: b.country },\n };\n}\n\nfunction toStripeAddress(b: BillingInfo): Record<string, string | undefined> {\n return {\n line1: b.line1 || undefined,\n city: b.city || undefined,\n state: b.state || undefined,\n postal_code: b.zip || undefined,\n country: b.country || undefined,\n };\n}\n\nexport interface CheckoutMutationsCtx {\n client: CheckoutClient | null,\n sessionRef: RefObject<EdxSessionResult | null>,\n stripeRef: RefObject<StripeLike | null>,\n cardNumberElRef: RefObject<StripeElementLike | null>,\n billingRef: RefObject<BillingInfo | null>,\n /** Selected monthly/annual cycle at Place order time. */\n selectedCycleRef: RefObject<BillingCycle>,\n setError: (msg: string) => void,\n setCardError: (msg: string) => void,\n onCartCreated: (cart: Cart) => void,\n}\n\nexport interface PlaceOrderArgs {\n consent: CheckoutConsent,\n paymentMethod: PaymentMethod,\n}\n\nexport function useCheckoutMutations(ctx: CheckoutMutationsCtx) {\n const queryClient = useQueryClient();\n\n const optimisticConfirm = (cartBiId: string) => {\n queryClient.setQueryData<CartStatusResult>(checkoutKeys.status(cartBiId), (old) => {\n console.log('old optimisticConfirm', old);\n return {\n status: STATUS.CONFIRM,\n stripeSubscriptionId: old?.stripeSubscriptionId ?? null,\n stripeConfirmationRedirectUrl: old?.stripeConfirmationRedirectUrl ?? null,\n paypalApprovalUrl: old?.paypalApprovalUrl ?? null,\n lastComment: old?.lastComment ?? '',\n };\n });\n queryClient.invalidateQueries({ queryKey: checkoutKeys.status(cartBiId) });\n };\n\n const placeOrder = useMutation<Cart, Error, PlaceOrderArgs>({\n mutationFn: async ({ consent, paymentMethod }) => {\n const { client, billingRef } = ctx;\n if (!client) throw new Error('Checkout is not ready yet.');\n\n if (paymentMethod === 'googlepay') {\n throw new Error('Google Pay is not supported yet. Please choose Card or PayPal.');\n }\n\n const billing = billingRef.current;\n if (!billing) throw new Error('Billing information is not available. Please refresh and try again.');\n\n const session = ctx.sessionRef.current;\n if (!session) {\n throw new Error('Checkout session is not ready yet. Please wait a moment and try again.');\n }\n const { customerUid } = session;\n\n // ── Step 1: Fetch salable(product) uid + billing plans in parallel ─────────────\n const [productInfo, plansResult] = await Promise.all([\n client.getSalableUid('TWOU-SUB-PLAN-001'),\n client.getBillingPlans(),\n ]);\n const salableUid = productInfo.uid;\n const selectedCycle = ctx.selectedCycleRef.current ?? 'monthly';\n const selectedPlan = getPlanPrice(plansResult.plans, selectedCycle);\n if (!selectedPlan) {\n throw new Error(`No billing plan found for ${selectedCycle} billing.`);\n }\n const billingPlanUid = selectedPlan.uid;\n\n // ── Step 2: Create the cart ───────────────────────────────────────────\n const createdCart = await client.createCart({ customerUid, salableUid, billingPlanUid });\n ctx.onCartCreated(createdCart);\n\n // ── Step 3: Attach billing address + payment method to cart item ──────\n const { cartItemUid } = await client.getCartItem(createdCart.biId);\n\n if (paymentMethod === 'paypal') {\n // No card tokenization — PayPal issues a hosted approval redirect instead.\n await client.updateCartItem({\n cartItemUid,\n billingAddress: toCartAddress(billing),\n paypalEmail: session.email ?? null,\n });\n } else {\n const stripe = ctx.stripeRef.current;\n const card = ctx.cardNumberElRef.current;\n if (!stripe || !card) throw new Error('Checkout is not ready yet.');\n\n // Tokenize the card (browser → Stripe).\n const { paymentMethod: stripePaymentMethod, error } = await stripe.createPaymentMethod({\n type: 'card',\n card,\n billing_details: {\n name: `${billing.firstName} ${billing.lastName}`.trim(),\n address: toStripeAddress(billing),\n },\n });\n if (error || !stripePaymentMethod) {\n throw new CardValidationError(error?.message || 'There was a problem with your card.');\n }\n\n await client.updateCartItem({\n cartItemUid,\n billingAddress: toCartAddress(billing),\n paymentMethodId: stripePaymentMethod.id,\n });\n }\n\n // ── Step 4: Persist consent + hosted-payment return URL on the TwouCart BI ────\n await client.updateCartConsent({\n cartUid: createdCart.uid,\n consent,\n returnUrl: buildStripeReturnUrl(createdCart.biId),\n });\n\n // ── Step 5: Fire the backend workflow ─────────────────────────────────\n await client.placeOrder(createdCart.uid);\n\n return createdCart;\n },\n onSuccess: (cart) => {\n optimisticConfirm(cart.biId);\n },\n onError: (err) => {\n if (err instanceof CardValidationError) ctx.setCardError(err.message);\n else ctx.setError(err.message);\n },\n });\n\n return { placeOrder };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useCheckoutMutations.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutMutations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * query/useCheckoutMutations.ts — write-side TanStack mutations.\n *\n * The placeOrder mutation drives the checkout sequence on button click.\n * The viax token and customerUid are established on page load via\n * GET /edx-session and passed in through sessionRef.\n *\n * Card:\n * 1. getSalableUid + getBillingPlans (parallel)\n * 2. createTwouCart\n * 3. stripe.createPaymentMethod → pm_xxx\n * 4. updateTwouCartItem → billing address + pm_xxx\n * 5. updateTwouCart → consent fields (SUBS-228)\n * 6. startBusinessProcessableTransitionV2('Sanction_Check')\n *\n * PayPal (mirrors the card sequence, skipping card tokenization):\n * 1. getSalableUid + getBillingPlans (parallel)\n * 2. createTwouCart\n * 3. updateTwouCartItem → billing address + TwouPaypalPayment\n * 4. updateTwouCart → consent fields + return URL (SUBS-228)\n * 5. startBusinessProcessableTransitionV2('Sanction_Check')\n * The backend then drives bpStatus to STRIPE_CONFIRMATION_REQUIRED and\n * populates tPaypalApprovalUrl, which useCheckout redirects to as the\n * hosted confirmation step for PayPal.\n */\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport type { RefObject } from 'react';\nimport { STATUS } from '../../constants';\nimport { checkoutKeys } from './keys';\nimport type { EdxSessionResult } from '../../utils/provision';\nimport { getPlanPrice } from '../../utils/billingDetails';\nimport { buildStripeReturnUrl } from '../../utils/stripe';\nimport type {\n CheckoutClient, StripeLike, StripeElementLike, Cart, BillingInfo,\n CartBillingAddress, CartStatusResult, CheckoutConsent, BillingCycle, SupportedPaymentMethod,\n} from '../../types';\n\nclass CardValidationError extends Error {}\n\nfunction toCartAddress(b: BillingInfo): CartBillingAddress {\n return {\n tFirstName: b.firstName,\n tLastName: b.lastName,\n upaLine1: b.line1,\n upaCity: b.city,\n upaPostcode: b.zip,\n gadStateOrProvince: b.state,\n gadCountry: { iso2Code: b.country },\n };\n}\n\nfunction toStripeAddress(b: BillingInfo): Record<string, string | undefined> {\n return {\n line1: b.line1 || undefined,\n city: b.city || undefined,\n state: b.state || undefined,\n postal_code: b.zip || undefined,\n country: b.country || undefined,\n };\n}\n\nexport interface CheckoutMutationsCtx {\n client: CheckoutClient | null,\n sessionRef: RefObject<EdxSessionResult | null>,\n stripeRef: RefObject<StripeLike | null>,\n cardNumberElRef: RefObject<StripeElementLike | null>,\n billingRef: RefObject<BillingInfo | null>,\n /** Selected monthly/annual cycle at Place order time. */\n selectedCycleRef: RefObject<BillingCycle>,\n setError: (msg: string) => void,\n setCardError: (msg: string) => void,\n onCartCreated: (cart: Cart) => void,\n}\n\nexport interface PlaceOrderArgs {\n consent: CheckoutConsent,\n paymentMethod: SupportedPaymentMethod,\n}\n\nexport function useCheckoutMutations(ctx: CheckoutMutationsCtx) {\n const queryClient = useQueryClient();\n\n const optimisticConfirm = (cartBiId: string) => {\n queryClient.setQueryData<CartStatusResult>(checkoutKeys.status(cartBiId), (old) => {\n console.log('old optimisticConfirm', old);\n return {\n status: STATUS.CONFIRM,\n stripeSubscriptionId: old?.stripeSubscriptionId ?? null,\n stripeConfirmationRedirectUrl: old?.stripeConfirmationRedirectUrl ?? null,\n paypalApprovalUrl: old?.paypalApprovalUrl ?? null,\n lastComment: old?.lastComment ?? '',\n };\n });\n queryClient.invalidateQueries({ queryKey: checkoutKeys.status(cartBiId) });\n };\n\n const placeOrder = useMutation<Cart, Error, PlaceOrderArgs>({\n mutationFn: async ({ consent, paymentMethod }) => {\n const { client, billingRef } = ctx;\n if (!client) throw new Error('Checkout is not ready yet.');\n\n // Google Pay is not currently supported.\n // if (paymentMethod === 'googlepay') {\n // throw new Error('Google Pay is not supported yet. Please choose Card or PayPal.');\n // }\n\n const billing = billingRef.current;\n if (!billing) throw new Error('Billing information is not available. Please refresh and try again.');\n\n const session = ctx.sessionRef.current;\n if (!session) {\n throw new Error('Checkout session is not ready yet. Please wait a moment and try again.');\n }\n const { customerUid } = session;\n\n // ── Step 1: Fetch salable(product) uid + billing plans in parallel ─────────────\n const [productInfo, plansResult] = await Promise.all([\n client.getSalableUid('TWOU-SUB-PLAN-001'),\n client.getBillingPlans(),\n ]);\n const salableUid = productInfo.uid;\n const selectedCycle = ctx.selectedCycleRef.current ?? 'monthly';\n const selectedPlan = getPlanPrice(plansResult.plans, selectedCycle);\n if (!selectedPlan) {\n throw new Error(`No billing plan found for ${selectedCycle} billing.`);\n }\n const billingPlanUid = selectedPlan.uid;\n\n // ── Step 2: Create the cart ───────────────────────────────────────────\n const createdCart = await client.createCart({ customerUid, salableUid, billingPlanUid });\n ctx.onCartCreated(createdCart);\n\n // ── Step 3: Attach billing address + payment method to cart item ──────\n const { cartItemUid } = await client.getCartItem(createdCart.biId);\n\n if (paymentMethod === 'paypal') {\n // No card tokenization — PayPal issues a hosted approval redirect instead.\n await client.updateCartItem({\n cartItemUid,\n billingAddress: toCartAddress(billing),\n paypalEmail: session.email ?? null,\n });\n } else {\n const stripe = ctx.stripeRef.current;\n const card = ctx.cardNumberElRef.current;\n if (!stripe || !card) throw new Error('Checkout is not ready yet.');\n\n // Tokenize the card (browser → Stripe).\n const { paymentMethod: stripePaymentMethod, error } = await stripe.createPaymentMethod({\n type: 'card',\n card,\n billing_details: {\n name: `${billing.firstName} ${billing.lastName}`.trim(),\n address: toStripeAddress(billing),\n },\n });\n if (error || !stripePaymentMethod) {\n throw new CardValidationError(error?.message || 'There was a problem with your card.');\n }\n\n await client.updateCartItem({\n cartItemUid,\n billingAddress: toCartAddress(billing),\n paymentMethodId: stripePaymentMethod.id,\n });\n }\n\n // ── Step 4: Persist consent + hosted-payment return URL on the TwouCart BI ────\n await client.updateCartConsent({\n cartUid: createdCart.uid,\n consent,\n returnUrl: buildStripeReturnUrl(createdCart.biId),\n });\n\n // ── Step 5: Fire the backend workflow ─────────────────────────────────\n await client.placeOrder(createdCart.uid);\n\n return createdCart;\n },\n onSuccess: (cart) => {\n optimisticConfirm(cart.biId);\n },\n onError: (err) => {\n if (err instanceof CardValidationError) ctx.setCardError(err.message);\n else ctx.setError(err.message);\n },\n });\n\n return { placeOrder };\n}\n"]}
|
|
@@ -13,6 +13,8 @@ export declare function useBillingPlansQuery(client: CheckoutClient | null, sess
|
|
|
13
13
|
export declare function useActiveSubscriptionsQuery(client: CheckoutClient | null, sessionReady: boolean): import("@tanstack/react-query").UseQueryResult<{
|
|
14
14
|
active: number;
|
|
15
15
|
}, Error>;
|
|
16
|
+
/** Checks whether the authenticated learner already has an active subscription. */
|
|
17
|
+
export declare function useCurrentUserSubscriberQuery(client: CheckoutClient | null, sessionReady: boolean): import("@tanstack/react-query").UseQueryResult<import("../../types").CurrentUserSubscriberResult | null, Error>;
|
|
16
18
|
/**
|
|
17
19
|
* Fetches the user's country from their edX account profile.
|
|
18
20
|
* Derives the LMS base URL from the session URL (same origin).
|
|
@@ -44,6 +44,16 @@ export function useActiveSubscriptionsQuery(client, sessionReady) {
|
|
|
44
44
|
retry: 1,
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
+
/** Checks whether the authenticated learner already has an active subscription. */
|
|
48
|
+
export function useCurrentUserSubscriberQuery(client, sessionReady) {
|
|
49
|
+
return useQuery({
|
|
50
|
+
queryKey: ['checkout', 'currentUserSubscriber'],
|
|
51
|
+
enabled: !!client && sessionReady,
|
|
52
|
+
queryFn: () => client.isCurrentUserSubscriber(),
|
|
53
|
+
staleTime: 5 * 60 * 1000,
|
|
54
|
+
retry: 1,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
47
57
|
/**
|
|
48
58
|
* Fetches the user's country from their edX account profile.
|
|
49
59
|
* Derives the LMS base URL from the session URL (same origin).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCheckoutQueries.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutQueries.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"useCheckoutQueries.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutQueries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * query/useCheckoutQueries.ts — read-side TanStack Query hooks.\n *\n * useViaxSessionQuery runs on mount to exchange the edX session for a viax\n * token. Billing plans and subscriber count wait until the session is ready.\n */\nimport { useQuery } from '@tanstack/react-query';\nimport { isTerminalStatus } from '../../constants';\nimport { checkoutKeys } from './keys';\nimport { fetchEdxSession, fetchUserCountry } from '../../utils/provision';\nimport type {\n CheckoutClient, CartStatusResult, BillingPlansResult, CourseDetail,\n} from '../../types';\n\n/** Exchange edX session cookies for a viax token on page load. */\nexport function useViaxSessionQuery(sessionUrl: string | null) {\n return useQuery({\n queryKey: checkoutKeys.viaxSession(),\n enabled: !!sessionUrl,\n queryFn: () => fetchEdxSession(sessionUrl!),\n staleTime: 5 * 60 * 1000,\n retry: 1,\n });\n}\n\n/**\n * Fetches billing plans after the viax session is established.\n * Used to display prices in CartSummary before the user starts checkout.\n */\nexport function useBillingPlansQuery(client: CheckoutClient | null, sessionReady: boolean) {\n return useQuery({\n queryKey: checkoutKeys.billingPlans(),\n enabled: !!client && sessionReady,\n queryFn: (): Promise<BillingPlansResult> => client!.getBillingPlans(),\n staleTime: Infinity,\n retry: 2,\n });\n}\n\n/**\n * Fetches the live active subscriber count after the viax session is ready.\n * Refreshes every 5 minutes so the number stays roughly current.\n */\nexport function useActiveSubscriptionsQuery(client: CheckoutClient | null, sessionReady: boolean) {\n return useQuery({\n queryKey: ['checkout', 'activeSubscriptions'],\n enabled: !!client && sessionReady,\n queryFn: () => client!.getActiveSubscriptions(),\n staleTime: 5 * 60 * 1000, // 5 minutes\n retry: 1,\n });\n}\n\n/** Checks whether the authenticated learner already has an active subscription. */\nexport function useCurrentUserSubscriberQuery(client: CheckoutClient | null, sessionReady: boolean) {\n return useQuery({\n queryKey: ['checkout', 'currentUserSubscriber'],\n enabled: !!client && sessionReady,\n queryFn: () => client!.isCurrentUserSubscriber(),\n staleTime: 5 * 60 * 1000,\n retry: 1,\n });\n}\n\n/**\n * Fetches the user's country from their edX account profile.\n * Derives the LMS base URL from the session URL (same origin).\n * Returns null if the fetch fails or the field is absent — checkout stays open in that case.\n */\nexport function useUserCountryQuery(sessionUrl: string | null, username: string | null) {\n return useQuery({\n queryKey: ['checkout', 'userCountry', username],\n enabled: !!sessionUrl && !!username,\n queryFn: () => {\n const lmsBaseUrl = new URL(sessionUrl!).origin;\n return fetchUserCountry(lmsBaseUrl, username!);\n },\n staleTime: Infinity,\n retry: 1,\n });\n}\n\n/** Polls the cart state machine every second; stops on terminal states. */\nexport function useCartStatusQuery(client: CheckoutClient | null, cartBiId: string | null) {\n return useQuery({\n queryKey: checkoutKeys.status(cartBiId ?? 'none'),\n enabled: !!client && !!cartBiId,\n queryFn: (): Promise<CartStatusResult> => client!.getCartStatus(cartBiId!),\n refetchInterval: (query) =>\n isTerminalStatus(query.state.data?.status ?? null) ? false : 1000,\n refetchOnWindowFocus: false,\n gcTime: 0,\n });\n}\n\n/** Looks up the created subscription biId after success. */\nexport function useSubscriptionIdQuery(\n client: CheckoutClient | null,\n cartBiId: string | null,\n enabled: boolean,\n) {\n return useQuery({\n queryKey: checkoutKeys.subscriptionId(cartBiId ?? 'none'),\n enabled: enabled && !!client && !!cartBiId,\n queryFn: () => client!.findSubscriptionBiId(cartBiId!),\n staleTime: Infinity,\n });\n}\n\n/**\n * Fetches course details when the user arrived via ?course_run_key=.\n * Skipped entirely when no course key is present (standard checkout).\n */\nexport function useCourseDetailQuery(\n client: CheckoutClient | null,\n sessionReady: boolean,\n courseRunKey: string | null,\n) {\n return useQuery({\n queryKey: checkoutKeys.courseDetail(courseRunKey ?? 'none'),\n enabled: !!client && sessionReady && !!courseRunKey,\n queryFn: (): Promise<CourseDetail | null> => client!.getCourseDetail(courseRunKey!),\n staleTime: Infinity,\n retry: 1,\n });\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export interface CartIds {
|
|
|
30
30
|
}
|
|
31
31
|
export type BillingCycle = 'monthly' | 'annual';
|
|
32
32
|
export type PaymentMethod = 'card' | 'googlepay' | 'paypal';
|
|
33
|
+
export type SupportedPaymentMethod = Exclude<PaymentMethod, 'googlepay'>;
|
|
33
34
|
export type BillingPeriod = 'MONTHLY' | 'ANNUAL' | 'QUARTERLY' | string;
|
|
34
35
|
export interface BillingPlanOwnershipModel {
|
|
35
36
|
uid: string;
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Status, Phase } from './constants';\n\nexport type { Status, Phase };\n\n// --- Bootstrap domain --------------------------------------------------------\nexport interface TwouCustomerContact {\n email?: string,\n}\n\nexport interface TwouCustomerParty {\n paName: string,\n indFirstName?: string,\n indLastName?: string,\n paContacts: { edges: { node: TwouCustomerContact }[] },\n}\n\nexport interface TwouCustomer {\n uid: string,\n parId: string,\n parParty: TwouCustomerParty,\n}\n\nexport type GqlRunner = (query: string, variables?: Record<string, unknown>, retry?: boolean) => Promise<any>;\n\n// --- Cart domain -------------------------------------------------------------\nexport interface Cart {\n biId: string,\n uid: string,\n}\n\nexport interface CartIds {\n customerUid: string,\n salableUid: string,\n billingPlanUid: string,\n}\n\nexport type BillingCycle = 'monthly' | 'annual';\n\nexport type PaymentMethod = 'card' | 'googlepay' | 'paypal';\n\nexport type BillingPeriod = 'MONTHLY' | 'ANNUAL' | 'QUARTERLY' | string;\n\nexport interface BillingPlanOwnershipModel {\n uid: string,\n omName: string,\n}\n\nexport interface BillingPlan {\n uid: string,\n stripePriceId: string,\n /** Price amount e.g. \"39.99\" */\n amount: string,\n /** Currency code e.g. \"USD\" */\n currency: string,\n isTemplate: boolean,\n ownershipModel: BillingPlanOwnershipModel,\n}\n\nexport interface SubscriptionDetails {\n biId: string, // \"BI301ZDAD6CMICG2\"\n trialStart: string | null,\n trialEnd: string | null,\n renewalType: string | null, // \"Annual\" | \"Monthly\"\n cardLast4: string | null, // \"4242\"\n cardBrand: string | null, // \"visa\" | \"mastercard\" etc\n stripePaymentMethodId: string | null,\n planName: string | null, // \"Monthly Subscription\"\n}\n\nexport interface CurrentUserSubscriberResult {\n isSubscriber: boolean,\n subscriptionBiId: string | null,\n status: string | null,\n subscriptionStartDate: string | null,\n currentPeriodEnd: string | null,\n}\n\n/** Course shown on checkout / post-checkout when arriving via ?course_run_key=. */\nexport interface CourseDetail {\n maId: string,\n maName: string,\n maDescription: string | null,\n tShortDescription: string | null,\n imageUrl: string | null,\n tCourseKey: string | null,\n tBrandText: string | null,\n tPrimarySubjectArea: string | null,\n tLevelType: string | null,\n tLanguage: string | null,\n tPacingType: string | null,\n tDurationLow: number | null,\n tDurationHigh: number | null,\n tDurationUnit: string | null,\n tEffortLow: number | null,\n tEffortHigh: number | null,\n tEffortUnit: string | null,\n tUrlCourse: string | null,\n tB2cSubscriptionInclusion: boolean | null,\n tHasOfacRestrictions: boolean | null,\n tCatalogActive: boolean | null,\n}\n\n/** Returned by getBillingPlans — all available plans for the product. */\nexport interface BillingPlansResult {\n plans: BillingPlan[],\n}\n\n/** Returned by getSalableUid — the product uid and its ownership models. */\nexport interface ProductInfo {\n uid: string,\n maName: string,\n ownershipModels: { uid: string, omName: string }[],\n}\n\nexport interface CartStatusResult {\n status: Status | null,\n stripeSubscriptionId: string | null,\n /** Stripe-hosted challenge URL for the checkout confirmation step. */\n stripeConfirmationRedirectUrl: string | null,\n /** PayPal-hosted approval URL for the same checkout confirmation step. */\n paypalApprovalUrl: string | null,\n lastComment: string,\n}\n\nexport interface CartBillingAddress {\n tFirstName: string,\n tLastName: string,\n upaLine1: string,\n upaCity: string,\n upaPostcode: string,\n gadStateOrProvince: string,\n gadCountry: { iso2Code: string },\n}\n\nexport interface UpdateCartItemArgs {\n cartItemUid: string,\n billingAddress?: CartBillingAddress,\n paymentMethodId?: string,\n /** Payer email attached when the payment type is TwouPaypalPayment. */\n paypalEmail?: string | null,\n}\n\n/** Auto-renewal consent captured on TwouCart at checkout confirmation (SUBS-228). */\nexport interface CheckoutConsent {\n /** Main auto-renewal checkbox — always required at submit. */\n tConsentRecurring: boolean,\n /** UK/CA resident checkbox; null when that checkbox is not shown. */\n tConsentUkCa: boolean | null,\n /** ISO-8601 timestamp when consent was captured. */\n tConsentAt: string,\n /** Exact disclosure text shown to the learner (audit trail). */\n tConsentText: string,\n}\n\nexport interface UpdateCartConsentArgs {\n cartUid: string,\n consent: CheckoutConsent,\n /** Absolute checkout return URL stored in the backend's tStripeReturnUrl field. */\n returnUrl?: string | null,\n}\n\nexport interface BillingInfo {\n firstName: string,\n lastName: string,\n city: string,\n zip: string,\n country: string,\n line1: string,\n state: string,\n}\n\n// --- Checkout client surface -------------------------------------------------\nexport interface CheckoutClient {\n gql: GqlRunner,\n setToken: (token: string) => void,\n createCart: (vars: CartIds) => Promise<Cart>,\n getCartItem: (biId: string) => Promise<{ cartUid: string, cartItemUid: string }>,\n updateCartItem: (args: UpdateCartItemArgs) => Promise<void>,\n updateCartConsent: (args: UpdateCartConsentArgs) => Promise<void>,\n placeOrder: (cartUid: string) => Promise<unknown>,\n getCartStatus: (biId: string) => Promise<CartStatusResult>,\n findSubscriptionBiId: (biId: string) => Promise<SubscriptionDetails | null>,\n getSalableUid: (maId: string) => Promise<ProductInfo>,\n getBillingPlans: () => Promise<BillingPlansResult>,\n getActiveSubscriptions: () => Promise<{ active: number }>,\n isCurrentUserSubscriber: () => Promise<CurrentUserSubscriberResult | null>,\n getCourseDetail: (courseKey: string) => Promise<CourseDetail | null>,\n}\n\nexport interface DevAuth {\n iamUrl: string,\n clientId: string,\n clientSecret: string,\n}\n\n// --- Minimal Stripe.js types -------------------------------------------------\n// Just the surface this module uses. If you depend on `@stripe/stripe-js`,\n// you can replace these with its richer types.\nexport interface StripeElementChangeEvent {\n error?: { message?: string },\n [key: string]: unknown,\n}\n\nexport interface StripeElementLike {\n mount: (node: HTMLElement) => void,\n on: (event: string, handler: (e: StripeElementChangeEvent) => void) => void,\n unmount?: () => void,\n}\n\nexport interface StripeElementsLike {\n create: (type: string, options?: Record<string, unknown>) => StripeElementLike,\n}\n\nexport interface StripeError {\n message?: string,\n code?: string,\n}\n\nexport interface PaymentMethodResult {\n paymentMethod?: { id: string },\n error?: StripeError,\n}\n\nexport interface ConfirmResult {\n error?: StripeError,\n setupIntent?: unknown,\n paymentIntent?: unknown,\n}\n\nexport interface CreatePaymentMethodParams {\n type: 'card',\n card: StripeElementLike,\n billing_details?: Record<string, unknown>,\n}\n\nexport interface StripeLike {\n elements: () => StripeElementsLike,\n createPaymentMethod: (params: CreatePaymentMethodParams) => Promise<PaymentMethodResult>,\n confirmCardSetup: (clientSecret: string) => Promise<ConfirmResult>,\n confirmCardPayment: (clientSecret: string) => Promise<ConfirmResult>,\n}\n\ndeclare global {\n interface Window {\n Stripe?: (publishableKey: string) => StripeLike,\n }\n}\n\nexport interface TrialDateRange {\n /** e.g. \"June 22, 2026\" */\n displayStart: string,\n /** e.g. \"June 29, 2026\" */\n displayEnd: string,\n}\n"]}
|
|
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 -------------------------------------------------\nexport interface CheckoutClient {\n gql: GqlRunner,\n setToken: (token: string) => void,\n createCart: (vars: CartIds) => Promise<Cart>,\n getCartItem: (biId: string) => Promise<{ cartUid: string, cartItemUid: string }>,\n updateCartItem: (args: UpdateCartItemArgs) => Promise<void>,\n updateCartConsent: (args: UpdateCartConsentArgs) => Promise<void>,\n placeOrder: (cartUid: string) => Promise<unknown>,\n getCartStatus: (biId: string) => Promise<CartStatusResult>,\n findSubscriptionBiId: (biId: string) => Promise<SubscriptionDetails | null>,\n getSalableUid: (maId: string) => Promise<ProductInfo>,\n getBillingPlans: () => Promise<BillingPlansResult>,\n getActiveSubscriptions: () => Promise<{ active: number }>,\n isCurrentUserSubscriber: () => Promise<CurrentUserSubscriberResult | null>,\n getCourseDetail: (courseKey: string) => Promise<CourseDetail | null>,\n}\n\nexport interface DevAuth {\n iamUrl: string,\n clientId: string,\n clientSecret: string,\n}\n\n// --- Minimal Stripe.js types -------------------------------------------------\n// Just the surface this module uses. If you depend on `@stripe/stripe-js`,\n// you can replace these with its richer types.\nexport interface StripeElementChangeEvent {\n error?: { message?: string },\n [key: string]: unknown,\n}\n\nexport interface StripeElementLike {\n mount: (node: HTMLElement) => void,\n on: (event: string, handler: (e: StripeElementChangeEvent) => void) => void,\n unmount?: () => void,\n}\n\nexport interface StripeElementsLike {\n create: (type: string, options?: Record<string, unknown>) => StripeElementLike,\n}\n\nexport interface StripeError {\n message?: string,\n code?: string,\n}\n\nexport interface PaymentMethodResult {\n paymentMethod?: { id: string },\n error?: StripeError,\n}\n\nexport interface ConfirmResult {\n error?: StripeError,\n setupIntent?: unknown,\n paymentIntent?: unknown,\n}\n\nexport interface CreatePaymentMethodParams {\n type: 'card',\n card: StripeElementLike,\n billing_details?: Record<string, unknown>,\n}\n\nexport interface StripeLike {\n elements: () => StripeElementsLike,\n createPaymentMethod: (params: CreatePaymentMethodParams) => Promise<PaymentMethodResult>,\n confirmCardSetup: (clientSecret: string) => Promise<ConfirmResult>,\n confirmCardPayment: (clientSecret: string) => Promise<ConfirmResult>,\n}\n\ndeclare global {\n interface Window {\n Stripe?: (publishableKey: string) => StripeLike,\n }\n}\n\nexport interface TrialDateRange {\n /** e.g. \"June 22, 2026\" */\n displayStart: string,\n /** e.g. \"June 29, 2026\" */\n displayEnd: string,\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getLearnerFacingCheckoutMessage(detail?: string | null): string | null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const DUPLICATE_ACTIVE_SUBSCRIPTION = 'duplicate_active_subscription';
|
|
2
|
+
export function getLearnerFacingCheckoutMessage(detail) {
|
|
3
|
+
const message = detail === null || detail === void 0 ? void 0 : detail.trim();
|
|
4
|
+
if (!message)
|
|
5
|
+
return null;
|
|
6
|
+
if (message.toLowerCase().includes(DUPLICATE_ACTIVE_SUBSCRIPTION)) {
|
|
7
|
+
return 'You already have an active edX Plus subscription. Manage it from your learner dashboard.';
|
|
8
|
+
}
|
|
9
|
+
return message;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=checkoutErrors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkoutErrors.js","sourceRoot":"","sources":["../../src/utils/checkoutErrors.ts"],"names":[],"mappings":"AAAA,MAAM,6BAA6B,GAAG,+BAA+B,CAAC;AAEtE,MAAM,UAAU,+BAA+B,CAAC,MAAsB;IACpE,MAAM,OAAO,GAAG,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,IAAI,EAAE,CAAC;IAC/B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC,EAAE,CAAC;QAClE,OAAO,0FAA0F,CAAC;IACpG,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC","sourcesContent":["const DUPLICATE_ACTIVE_SUBSCRIPTION = 'duplicate_active_subscription';\n\nexport function getLearnerFacingCheckoutMessage(detail?: string | null): string | null {\n const message = detail?.trim();\n if (!message) return null;\n\n if (message.toLowerCase().includes(DUPLICATE_ACTIVE_SUBSCRIPTION)) {\n return 'You already have an active edX Plus subscription. Manage it from your learner dashboard.';\n }\n\n return message;\n}\n"]}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* and returns a per-user token for authenticated API calls.
|
|
7
7
|
*
|
|
8
8
|
* Dev uses POST with a hardcoded JWT payload (local LMS cookie exchange is
|
|
9
|
-
* unavailable). Stage and prod use
|
|
9
|
+
* unavailable). Stage and prod use POST and rely on the browser session cookies.
|
|
10
10
|
*/
|
|
11
11
|
export interface EdxSessionResult {
|
|
12
12
|
viaxToken: string;
|
|
@@ -16,10 +16,33 @@ export interface EdxSessionResult {
|
|
|
16
16
|
username?: string;
|
|
17
17
|
email?: string;
|
|
18
18
|
}
|
|
19
|
+
export interface GeolocationResult {
|
|
20
|
+
status: 'resolved' | 'not_found' | 'error';
|
|
21
|
+
ip_address: string;
|
|
22
|
+
ip_address_type: string | null;
|
|
23
|
+
country_code: string | null;
|
|
24
|
+
country_name: string | null;
|
|
25
|
+
region_code: string | null;
|
|
26
|
+
region_name: string | null;
|
|
27
|
+
city: string | null;
|
|
28
|
+
postal_code: string | null;
|
|
29
|
+
latitude: number | null;
|
|
30
|
+
longitude: number | null;
|
|
31
|
+
accuracy_radius_km: number | null;
|
|
32
|
+
confidence: string | null;
|
|
33
|
+
source: string | null;
|
|
34
|
+
cache_status: string | null;
|
|
35
|
+
looked_up_at: string | null;
|
|
36
|
+
fallback_reason: string | null;
|
|
37
|
+
}
|
|
19
38
|
/**
|
|
20
39
|
* Fetch the ISO 3166-1 alpha-2 country code from the user's edX account profile.
|
|
21
40
|
* Returns null if the request fails or the field is absent.
|
|
22
41
|
*/
|
|
23
42
|
export declare function fetchUserCountry(lmsBaseUrl: string, username: string): Promise<string | null>;
|
|
43
|
+
/** Fetch the learner's current public IP for geolocation lookup. */
|
|
44
|
+
export declare function fetchPublicIp(): Promise<string>;
|
|
45
|
+
/** Look up geolocation data for an explicit IP address. */
|
|
46
|
+
export declare function fetchIpGeolocation(geolocationUrl: string, ipAddress: string): Promise<GeolocationResult>;
|
|
24
47
|
/** Exchange the learner's edX session (cookies) for a viax token. */
|
|
25
48
|
export declare function fetchEdxSession(sessionUrl: string): Promise<EdxSessionResult>;
|
package/dist/utils/provision.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* and returns a per-user token for authenticated API calls.
|
|
7
7
|
*
|
|
8
8
|
* Dev uses POST with a hardcoded JWT payload (local LMS cookie exchange is
|
|
9
|
-
* unavailable). Stage and prod use
|
|
9
|
+
* unavailable). Stage and prod use POST and rely on the browser session cookies.
|
|
10
10
|
*/
|
|
11
11
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
12
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
@@ -20,9 +20,38 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
20
20
|
import { EnvironmentTypes, getSiteConfig } from '@openedx/frontend-base';
|
|
21
21
|
/** Hardcoded edX JWT used only for local/dev checkout against stage Viax. */
|
|
22
22
|
const DEV_SESSION_BODY = {
|
|
23
|
-
payload: '
|
|
24
|
-
signature: '
|
|
23
|
+
payload: '',
|
|
24
|
+
signature: ''
|
|
25
25
|
};
|
|
26
|
+
const EDX_JWT_PAYLOAD_COOKIE_NAMES = [
|
|
27
|
+
'stage-edx-jwt-cookie-header-payload',
|
|
28
|
+
'edx-jwt-cookie-header-payload',
|
|
29
|
+
];
|
|
30
|
+
const EDX_JWT_SIGNATURE_COOKIE_NAMES = [
|
|
31
|
+
'stage-edx-jwt-cookie-signature',
|
|
32
|
+
'edx-jwt-cookie-signature',
|
|
33
|
+
];
|
|
34
|
+
function getCookieValue(name) {
|
|
35
|
+
const cookie = document.cookie
|
|
36
|
+
.split('; ')
|
|
37
|
+
.find((cookiePart) => cookiePart.startsWith(`${name}=`));
|
|
38
|
+
if (!cookie)
|
|
39
|
+
return null;
|
|
40
|
+
return decodeURIComponent(cookie.substring(name.length + 1));
|
|
41
|
+
}
|
|
42
|
+
function getFirstCookieValue(names) {
|
|
43
|
+
for (const name of names) {
|
|
44
|
+
const value = getCookieValue(name);
|
|
45
|
+
if (value)
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
function getEdxJwtCookieBody() {
|
|
51
|
+
const payload = getFirstCookieValue(EDX_JWT_PAYLOAD_COOKIE_NAMES);
|
|
52
|
+
const signature = getFirstCookieValue(EDX_JWT_SIGNATURE_COOKIE_NAMES);
|
|
53
|
+
return payload && signature ? { payload, signature } : null;
|
|
54
|
+
}
|
|
26
55
|
function parseSessionResponse(data) {
|
|
27
56
|
var _a, _b, _c, _d;
|
|
28
57
|
const viaxToken = ((_c = (_b = (_a = data.viaxToken) !== null && _a !== void 0 ? _a : data.token) !== null && _b !== void 0 ? _b : data.access_token) !== null && _c !== void 0 ? _c : data.accessToken);
|
|
@@ -60,6 +89,43 @@ export function fetchUserCountry(lmsBaseUrl, username) {
|
|
|
60
89
|
}
|
|
61
90
|
});
|
|
62
91
|
}
|
|
92
|
+
/** Fetch the learner's current public IP for geolocation lookup. */
|
|
93
|
+
export function fetchPublicIp() {
|
|
94
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
95
|
+
const res = yield fetch('https://api.ipify.org?format=json', {
|
|
96
|
+
headers: { Accept: 'application/json' },
|
|
97
|
+
});
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
throw new Error(`Public IP lookup failed (${res.status}).`);
|
|
100
|
+
}
|
|
101
|
+
const data = yield res.json();
|
|
102
|
+
if (typeof data.ip !== 'string' || !data.ip) {
|
|
103
|
+
throw new Error('Public IP lookup returned an invalid address.');
|
|
104
|
+
}
|
|
105
|
+
return data.ip;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** Look up geolocation data for an explicit IP address. */
|
|
109
|
+
export function fetchIpGeolocation(geolocationUrl, ipAddress) {
|
|
110
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
111
|
+
const edxJwtBody = getEdxJwtCookieBody();
|
|
112
|
+
const requestBody = edxJwtBody
|
|
113
|
+
? Object.assign({ ip_address: ipAddress }, edxJwtBody) : { ip_address: ipAddress };
|
|
114
|
+
const res = yield fetch(geolocationUrl, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
credentials: 'include',
|
|
117
|
+
headers: {
|
|
118
|
+
Accept: 'application/json',
|
|
119
|
+
'Content-Type': 'application/json',
|
|
120
|
+
},
|
|
121
|
+
body: JSON.stringify(requestBody),
|
|
122
|
+
});
|
|
123
|
+
if (!res.ok) {
|
|
124
|
+
throw new Error(`Geolocation lookup failed (${res.status}).`);
|
|
125
|
+
}
|
|
126
|
+
return res.json();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
63
129
|
function isDevelopment() {
|
|
64
130
|
return getSiteConfig().environment === EnvironmentTypes.DEVELOPMENT;
|
|
65
131
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provision.js","sourceRoot":"","sources":["../../src/utils/provision.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/provision.ts — exchange the edX session for a viax token.\n *\n * {LMS_BASE_URL}/api/edx-session\n * The LMS reads the edX JWT from browser cookies, exchanges it with viax,\n * and returns a per-user token for authenticated API calls.\n *\n * Dev uses POST with a hardcoded JWT payload (local LMS cookie exchange is\n * unavailable). Stage and prod use GET and rely on the browser session cookies.\n */\n\nimport { EnvironmentTypes, getSiteConfig } from '@openedx/frontend-base';\n\nexport interface EdxSessionResult {\n viaxToken: string, // raw JWT — add 'Bearer ' prefix before use\n customerUid: string,\n partyUid?: string,\n edxUserId?: string,\n username?: string,\n email?: string,\n}\n\n/** Hardcoded edX JWT used only for local/dev checkout against stage Viax. */\nconst DEV_SESSION_BODY = {\n payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIiwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc4OTc2MjA4MSwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzg5NzU4NDgxLCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJBXzI2MDlfUVhPTSIsInNjb3BlcyI6WyJ1c2VyX2lkIiwiZW1haWwiLCJwcm9maWxlIl0sInZlcnNpb24iOiIxLjIuMCIsInN1YiI6ImI1ODlhMTE3NWE4MjA0OGFlYjBmM2M0MWVjZGEzMGRhIiwiZmlsdGVycyI6WyJ1c2VyOm1lIl0sImlzX3Jlc3RyaWN0ZWQiOmZhbHNlLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInVzZXJfaWQiOjUyODU1MjQsImVtYWlsIjoieWlzZW5hNDA2MEBibG9iYXBwcy5jb20iLCJuYW1lIjoiQWthc2giLCJmYW1pbHlfbmFtZSI6IiIsImdpdmVuX25hbWUiOiIiLCJhZG1pbmlzdHJhdG9yIjpmYWxzZSwic3VwZXJ1c2VyIjpmYWxzZX0',\n signature: 'f3QVBVeUTUdkAP1-Z6OTK6Rh2cnu7H7u_4xTrv27Zo8TVx3C4c72dMqDdm4kRNz6hoPvZxDjldvf7hSCf-L-JZ4S7vSvaHJo94925VuwzGLdW2XdGSvHyBLAejt1VWIAcwHGuCPytx4eUJ3U9pMG-tmZ19uzQicYvGNJ4yQ8UwEaQOM3Y-fHhrXTPwCd-6qRvHJ5jj5kHUzQQXhwIOmBjgdzB3Yw-xFWCX_gNOax2wjWvC9hk417mwRRhDmO5GZ7kzutSp0gAyRM0E7krMJRGwjRe7KoTFDR65nMqvc3gzk9GQ0lO4Ql0Bk1AEe0Vb2RaS1b6BDbF5UVgigk0qYh9Q'\n};\nfunction parseSessionResponse(data: Record<string, unknown>): EdxSessionResult {\n const viaxToken = (\n data.viaxToken ?? data.token ?? data.access_token ?? data.accessToken\n ) as string | undefined;\n const customerUid = (data.customerUid ?? data.customer_uid) as string | undefined;\n\n if (!viaxToken) throw new Error('Session response missing viax token.');\n if (!customerUid) throw new Error('Session response missing customerUid.');\n\n return {\n viaxToken,\n customerUid,\n partyUid: data.partyUid as string | undefined,\n edxUserId: data.edxUserId as string | undefined,\n username: data.username as string | undefined,\n email: data.email as string | undefined,\n };\n}\n\n/**\n * Fetch the ISO 3166-1 alpha-2 country code from the user's edX account profile.\n * Returns null if the request fails or the field is absent.\n */\nexport async function fetchUserCountry(lmsBaseUrl: string, username: string): Promise<string | null> {\n try {\n const res = await fetch(`${lmsBaseUrl}/api/user/v1/accounts/${encodeURIComponent(username)}`, {\n credentials: 'include',\n });\n if (!res.ok) return null;\n const data = await res.json() as Record<string, unknown>;\n return typeof data.country === 'string' && data.country ? data.country : null;\n } catch {\n return null;\n }\n}\n\nfunction isDevelopment(): boolean {\n return getSiteConfig().environment === EnvironmentTypes.DEVELOPMENT;\n}\n\n/** Exchange the learner's edX session (cookies) for a viax token. */\nexport async function fetchEdxSession(sessionUrl: string): Promise<EdxSessionResult> {\n const headers: Record<string, string> = { Accept: 'application/json' };\n\n let res: Response;\n if (isDevelopment()) {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers: {\n ...headers,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(DEV_SESSION_BODY),\n });\n } else {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers,\n });\n }\n\n if (!res.ok) {\n throw new Error(\n `Failed to establish checkout session (${res.status}). Please log into edX and try again.`,\n );\n }\n\n const data = await res.json() as Record<string, unknown> & { error?: string };\n if (data.error) throw new Error(String(data.error));\n\n return parseSessionResponse(data);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"provision.js","sourceRoot":"","sources":["../../src/utils/provision.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/provision.ts — exchange the edX session for a viax token.\n *\n * {LMS_BASE_URL}/api/edx-session\n * The LMS reads the edX JWT from browser cookies, exchanges it with viax,\n * and returns a per-user token for authenticated API calls.\n *\n * Dev uses POST with a hardcoded JWT payload (local LMS cookie exchange is\n * unavailable). Stage and prod use POST and rely on the browser session cookies.\n */\n\nimport { EnvironmentTypes, getSiteConfig } from '@openedx/frontend-base';\n\nexport interface EdxSessionResult {\n viaxToken: string, // raw JWT — add 'Bearer ' prefix before use\n customerUid: string,\n partyUid?: string,\n edxUserId?: string,\n username?: string,\n email?: string,\n}\n\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: '',\n signature: ''\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 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"]}
|