@edx/frontend-app-subscription-checkout 1.22.1 → 1.22.3
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/useCheckoutMutations.js +50 -1
- package/dist/data/hooks/useCheckoutMutations.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/auth.js +1 -0
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/cartApi.d.ts +3 -1
- package/dist/utils/cartApi.js +12 -1
- package/dist/utils/cartApi.js.map +1 -1
- package/dist/utils/cartRefusal.d.ts +16 -0
- package/dist/utils/cartRefusal.js +49 -0
- package/dist/utils/cartRefusal.js.map +1 -0
- package/dist/utils/checkout.js +1 -0
- package/dist/utils/checkout.js.map +1 -1
- package/dist/utils/graphqlClient.d.ts +16 -0
- package/dist/utils/graphqlClient.js +15 -1
- package/dist/utils/graphqlClient.js.map +1 -1
- package/dist/utils/queries.d.ts +6 -0
- package/dist/utils/queries.js +8 -0
- package/dist/utils/queries.js.map +1 -1
- package/package.json +1 -1
|
@@ -31,14 +31,63 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
31
31
|
* The backend then drives bpStatus to STRIPE_CONFIRMATION_REQUIRED and
|
|
32
32
|
* populates tPaypalApprovalUrl, which useCheckout redirects to as the
|
|
33
33
|
* hosted confirmation step for PayPal.
|
|
34
|
+
*
|
|
35
|
+
* One open cart per learner (SUBS-1127): step 2 is refused while an earlier
|
|
36
|
+
* cart is still open, e.g. one left behind when the learner came back from
|
|
37
|
+
* PayPal without approving. See createCartReplacingOpen.
|
|
34
38
|
*/
|
|
35
39
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
36
40
|
import { STATUS } from '../../constants';
|
|
37
41
|
import { checkoutKeys } from './keys';
|
|
38
42
|
import { getPlanPrice } from '../../utils/billingDetails';
|
|
39
43
|
import { buildStripeReturnUrl } from '../../utils/stripe';
|
|
44
|
+
import { parseCartRefusal, CART_IN_PROGRESS_STATES, ALREADY_SUBSCRIBED_MESSAGE, PREVIOUS_ORDER_PROCESSING_MESSAGE, PREVIOUS_ORDER_STUCK_MESSAGE, } from '../../utils/cartRefusal';
|
|
40
45
|
class CardValidationError extends Error {
|
|
41
46
|
}
|
|
47
|
+
/** A second refusal after clearing the open cart: say why, never the generic validation text. */
|
|
48
|
+
function refusalMessage(err) {
|
|
49
|
+
const refusal = parseCartRefusal(err);
|
|
50
|
+
if ((refusal === null || refusal === void 0 ? void 0 : refusal.code) === 'ACTIVE_SUBSCRIPTION')
|
|
51
|
+
return new Error(ALREADY_SUBSCRIBED_MESSAGE);
|
|
52
|
+
if ((refusal === null || refusal === void 0 ? void 0 : refusal.code) === 'OPEN_CART_EXISTS')
|
|
53
|
+
return new Error(PREVIOUS_ORDER_STUCK_MESSAGE);
|
|
54
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Create the cart. When the learner still has an earlier open cart (SUBS-1127 —
|
|
58
|
+
* e.g. they left PayPal without approving), cancel it and create the cart once
|
|
59
|
+
* more, so the new attempt starts a fresh approval flow. A cart whose payment
|
|
60
|
+
* is still under way is left alone: cancelling it could orphan a subscription.
|
|
61
|
+
*/
|
|
62
|
+
function createCartReplacingOpen(client, ids) {
|
|
63
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
try {
|
|
65
|
+
return yield client.createCart(ids);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
const refusal = parseCartRefusal(err);
|
|
69
|
+
if (!refusal)
|
|
70
|
+
throw err;
|
|
71
|
+
if (refusal.code === 'ACTIVE_SUBSCRIPTION')
|
|
72
|
+
throw new Error(ALREADY_SUBSCRIBED_MESSAGE);
|
|
73
|
+
const openCart = yield client.getCartStatus(refusal.biId).catch(() => null);
|
|
74
|
+
if ((openCart === null || openCart === void 0 ? void 0 : openCart.status) && CART_IN_PROGRESS_STATES.includes(openCart.status)) {
|
|
75
|
+
throw new Error(PREVIOUS_ORDER_PROCESSING_MESSAGE);
|
|
76
|
+
}
|
|
77
|
+
const cancelled = yield client.cancelCart(refusal.biId).catch(() => null);
|
|
78
|
+
// CART_NOT_OPEN: the cart finished on its own meanwhile, so nothing blocks the retry.
|
|
79
|
+
if (!(cancelled === null || cancelled === void 0 ? void 0 : cancelled.ok) && (cancelled === null || cancelled === void 0 ? void 0 : cancelled.error) !== 'CART_NOT_OPEN') {
|
|
80
|
+
throw new Error(PREVIOUS_ORDER_STUCK_MESSAGE);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
return yield client.createCart(ids);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
throw refusalMessage(err);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
42
91
|
function toCartAddress(b) {
|
|
43
92
|
return {
|
|
44
93
|
tFirstName: b.firstName,
|
|
@@ -106,7 +155,7 @@ export function useCheckoutMutations(ctx) {
|
|
|
106
155
|
}
|
|
107
156
|
const billingPlanUid = selectedPlan.uid;
|
|
108
157
|
// ── Step 2: Create the cart ───────────────────────────────────────────
|
|
109
|
-
const createdCart = yield client
|
|
158
|
+
const createdCart = yield createCartReplacingOpen(client, { customerUid, salableUid, billingPlanUid });
|
|
110
159
|
ctx.onCartCreated(createdCart);
|
|
111
160
|
// ── Step 3: Attach billing address + payment method to cart item ──────
|
|
112
161
|
const { cartItemUid } = yield client.getCartItem(createdCart.biId);
|
|
@@ -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, 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: Wait for the asynchronously-created workflow ──────────────\n await client.waitForCartBusinessProcess(createdCart.biId);\n\n // ── Step 6: Fire the backend workflow ─────────────────────────────────\n await client.placeOrder(createdCart.uid);\n\n return createdCart;\n },\n onSuccess: (cart) => {\n optimisticConfirm(cart.biId);\n },\n onError: (err) => {\n if (err instanceof CardValidationError) ctx.setCardError(err.message);\n else ctx.setError(err.message);\n },\n });\n\n return { placeOrder };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useCheckoutMutations.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutMutations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * query/useCheckoutMutations.ts — write-side TanStack mutations.\n *\n * The placeOrder mutation drives the checkout sequence on button click.\n * The viax token and customerUid are established on page load via\n * GET /edx-session and passed in through sessionRef.\n *\n * Card:\n * 1. getSalableUid + getBillingPlans (parallel)\n * 2. createTwouCart\n * 3. stripe.createPaymentMethod → pm_xxx\n * 4. updateTwouCartItem → billing address + pm_xxx\n * 5. updateTwouCart → consent fields (SUBS-228)\n * 6. 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 *\n * One open cart per learner (SUBS-1127): step 2 is refused while an earlier\n * cart is still open, e.g. one left behind when the learner came back from\n * PayPal without approving. See createCartReplacingOpen.\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 {\n parseCartRefusal, CART_IN_PROGRESS_STATES, ALREADY_SUBSCRIBED_MESSAGE,\n PREVIOUS_ORDER_PROCESSING_MESSAGE, PREVIOUS_ORDER_STUCK_MESSAGE,\n} from '../../utils/cartRefusal';\nimport type {\n CheckoutClient, StripeLike, StripeElementLike, Cart, CartIds, BillingInfo,\n CartBillingAddress, CartStatusResult, CheckoutConsent, BillingCycle, SupportedPaymentMethod,\n} from '../../types';\n\nclass CardValidationError extends Error {}\n\n/** A second refusal after clearing the open cart: say why, never the generic validation text. */\nfunction refusalMessage(err: unknown): Error {\n const refusal = parseCartRefusal(err);\n if (refusal?.code === 'ACTIVE_SUBSCRIPTION') return new Error(ALREADY_SUBSCRIBED_MESSAGE);\n if (refusal?.code === 'OPEN_CART_EXISTS') return new Error(PREVIOUS_ORDER_STUCK_MESSAGE);\n return err instanceof Error ? err : new Error(String(err));\n}\n\n/**\n * Create the cart. When the learner still has an earlier open cart (SUBS-1127 —\n * e.g. they left PayPal without approving), cancel it and create the cart once\n * more, so the new attempt starts a fresh approval flow. A cart whose payment\n * is still under way is left alone: cancelling it could orphan a subscription.\n */\nasync function createCartReplacingOpen(client: CheckoutClient, ids: CartIds): Promise<Cart> {\n try {\n return await client.createCart(ids);\n } catch (err) {\n const refusal = parseCartRefusal(err);\n if (!refusal) throw err;\n if (refusal.code === 'ACTIVE_SUBSCRIPTION') throw new Error(ALREADY_SUBSCRIBED_MESSAGE);\n\n const openCart = await client.getCartStatus(refusal.biId).catch(() => null);\n if (openCart?.status && CART_IN_PROGRESS_STATES.includes(openCart.status)) {\n throw new Error(PREVIOUS_ORDER_PROCESSING_MESSAGE);\n }\n\n const cancelled = await client.cancelCart(refusal.biId).catch(() => null);\n // CART_NOT_OPEN: the cart finished on its own meanwhile, so nothing blocks the retry.\n if (!cancelled?.ok && cancelled?.error !== 'CART_NOT_OPEN') {\n throw new Error(PREVIOUS_ORDER_STUCK_MESSAGE);\n }\n }\n\n try {\n return await client.createCart(ids);\n } catch (err) {\n throw refusalMessage(err);\n }\n}\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 createCartReplacingOpen(client, { 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: Wait for the asynchronously-created workflow ──────────────\n await client.waitForCartBusinessProcess(createdCart.biId);\n\n // ── Step 6: Fire the backend workflow ─────────────────────────────────\n await client.placeOrder(createdCart.uid);\n\n return createdCart;\n },\n onSuccess: (cart) => {\n optimisticConfirm(cart.biId);\n },\n onError: (err) => {\n if (err instanceof CardValidationError) ctx.setCardError(err.message);\n else ctx.setError(err.message);\n },\n });\n\n return { placeOrder };\n}\n"]}
|
package/dist/types.d.ts
CHANGED
|
@@ -153,10 +153,18 @@ export interface BillingInfo {
|
|
|
153
153
|
line1: string;
|
|
154
154
|
state: string;
|
|
155
155
|
}
|
|
156
|
+
/** Result of tCartCancel (SUBS-1127). */
|
|
157
|
+
export interface CartCancelResult {
|
|
158
|
+
ok: boolean;
|
|
159
|
+
/** NOT_AUTHENTICATED | BI_ID_REQUIRED | CART_NOT_FOUND | NOT_YOUR_CART | CART_NOT_OPEN | CANCEL_FAILED */
|
|
160
|
+
error?: string;
|
|
161
|
+
}
|
|
156
162
|
export interface CheckoutClient {
|
|
157
163
|
gql: GqlRunner;
|
|
158
164
|
setToken: (token: string) => void;
|
|
159
165
|
createCart: (vars: CartIds) => Promise<Cart>;
|
|
166
|
+
/** Abandon the learner's own open cart, cancelling any pending Stripe / PayPal subscription on it. */
|
|
167
|
+
cancelCart: (biId: string) => Promise<CartCancelResult>;
|
|
160
168
|
getCartItem: (biId: string) => Promise<{
|
|
161
169
|
cartUid: string;
|
|
162
170
|
cartItemUid: 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 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 waitForCartBusinessProcess: (biId: string) => Promise<void>,\n placeOrder: (cartUid: string) => Promise<unknown>,\n getCartStatus: (biId: string) => Promise<CartStatusResult>,\n findSubscriptionBiId: (biId: string) => Promise<SubscriptionDetails | null>,\n getSalableUid: (maId: string) => Promise<ProductInfo>,\n getBillingPlans: () => Promise<BillingPlansResult>,\n getActiveSubscriptions: () => Promise<{ active: number }>,\n isCurrentUserSubscriber: () => Promise<CurrentUserSubscriberResult | null>,\n getCourseDetail: (courseKey: string) => Promise<CourseDetail | null>,\n}\n\nexport interface DevAuth {\n iamUrl: string,\n clientId: string,\n clientSecret: string,\n}\n\n// --- Minimal Stripe.js types -------------------------------------------------\n// Just the surface this module uses. If you depend on `@stripe/stripe-js`,\n// you can replace these with its richer types.\nexport interface StripeElementChangeEvent {\n error?: { message?: string },\n [key: string]: unknown,\n}\n\nexport interface StripeElementLike {\n mount: (node: HTMLElement) => void,\n on: (event: string, handler: (e: StripeElementChangeEvent) => void) => void,\n unmount?: () => void,\n}\n\nexport interface StripeElementsLike {\n create: (type: string, options?: Record<string, unknown>) => StripeElementLike,\n}\n\nexport interface StripeError {\n message?: string,\n code?: string,\n}\n\nexport interface PaymentMethodResult {\n paymentMethod?: { id: string },\n error?: StripeError,\n}\n\nexport interface ConfirmResult {\n error?: StripeError,\n setupIntent?: unknown,\n paymentIntent?: unknown,\n}\n\nexport interface CreatePaymentMethodParams {\n type: 'card',\n card: StripeElementLike,\n billing_details?: Record<string, unknown>,\n}\n\nexport interface StripeLike {\n elements: () => StripeElementsLike,\n createPaymentMethod: (params: CreatePaymentMethodParams) => Promise<PaymentMethodResult>,\n confirmCardSetup: (clientSecret: string) => Promise<ConfirmResult>,\n confirmCardPayment: (clientSecret: string) => Promise<ConfirmResult>,\n}\n\ndeclare global {\n interface Window {\n Stripe?: (publishableKey: string) => StripeLike,\n }\n}\n\nexport interface TrialDateRange {\n /** e.g. \"June 22, 2026\" */\n displayStart: string,\n /** e.g. \"June 29, 2026\" */\n displayEnd: string,\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Status, Phase } from './constants';\n\nexport type { Status, Phase };\n\n// --- Bootstrap domain --------------------------------------------------------\nexport interface TwouCustomerContact {\n email?: string,\n}\n\nexport interface TwouCustomerParty {\n paName: string,\n indFirstName?: string,\n indLastName?: string,\n paContacts: { edges: { node: TwouCustomerContact }[] },\n}\n\nexport interface TwouCustomer {\n uid: string,\n parId: string,\n parParty: TwouCustomerParty,\n}\n\nexport type GqlRunner = (query: string, variables?: Record<string, unknown>, retry?: boolean) => Promise<any>;\n\n// --- Cart domain -------------------------------------------------------------\nexport interface Cart {\n biId: string,\n uid: string,\n}\n\nexport interface CartIds {\n customerUid: string,\n salableUid: string,\n billingPlanUid: string,\n}\n\nexport type BillingCycle = 'monthly' | 'annual';\n\nexport type PaymentMethod = 'card' | 'googlepay' | 'paypal';\n\nexport type SupportedPaymentMethod = Exclude<PaymentMethod, 'googlepay'>;\n\nexport type BillingPeriod = 'MONTHLY' | 'ANNUAL' | 'QUARTERLY' | string;\n\nexport interface BillingPlanOwnershipModel {\n uid: string,\n omName: string,\n}\n\nexport interface BillingPlan {\n uid: string,\n stripePriceId: string,\n /** Price amount e.g. \"39.99\" */\n amount: string,\n /** Currency code e.g. \"USD\" */\n currency: string,\n isTemplate: boolean,\n ownershipModel: BillingPlanOwnershipModel,\n}\n\nexport interface SubscriptionDetails {\n biId: string, // \"BI301ZDAD6CMICG2\"\n trialStart: string | null,\n trialEnd: string | null,\n renewalType: string | null, // \"Annual\" | \"Monthly\"\n cardLast4: string | null, // \"4242\"\n cardBrand: string | null, // \"visa\" | \"mastercard\" etc\n stripePaymentMethodId: string | null,\n planName: string | null, // \"Monthly Subscription\"\n}\n\nexport interface CurrentUserSubscriberResult {\n isSubscriber: boolean,\n subscriptionBiId: string | null,\n status: string | null,\n subscriptionStartDate: string | null,\n currentPeriodEnd: string | null,\n}\n\n/** Course shown on checkout / post-checkout when arriving via ?course_run_key=. */\nexport interface CourseDetail {\n maId: string,\n maName: string,\n maDescription: string | null,\n tShortDescription: string | null,\n imageUrl: string | null,\n tCourseKey: string | null,\n tBrandText: string | null,\n tPrimarySubjectArea: string | null,\n tLevelType: string | null,\n tLanguage: string | null,\n tPacingType: string | null,\n tDurationLow: number | null,\n tDurationHigh: number | null,\n tDurationUnit: string | null,\n tEffortLow: number | null,\n tEffortHigh: number | null,\n tEffortUnit: string | null,\n tUrlCourse: string | null,\n tB2cSubscriptionInclusion: boolean | null,\n tHasOfacRestrictions: boolean | null,\n tCatalogActive: boolean | null,\n}\n\n/** Returned by getBillingPlans — all available plans for the product. */\nexport interface BillingPlansResult {\n plans: BillingPlan[],\n}\n\n/** Returned by getSalableUid — the product uid and its ownership models. */\nexport interface ProductInfo {\n uid: string,\n maName: string,\n ownershipModels: { uid: string, omName: string }[],\n}\n\nexport interface CartStatusResult {\n status: Status | null,\n stripeSubscriptionId: string | null,\n /** Stripe-hosted challenge URL for the checkout confirmation step. */\n stripeConfirmationRedirectUrl: string | null,\n /** PayPal-hosted approval URL for the same checkout confirmation step. */\n paypalApprovalUrl: string | null,\n lastComment: string,\n}\n\nexport interface CartBillingAddress {\n tFirstName: string,\n tLastName: string,\n upaLine1: string,\n upaCity: string,\n upaPostcode: string,\n gadStateOrProvince: string,\n gadCountry: { iso2Code: string },\n}\n\nexport interface UpdateCartItemArgs {\n cartItemUid: string,\n billingAddress?: CartBillingAddress,\n paymentMethodId?: string,\n /** Payer email attached when the payment type is TwouPaypalPayment. */\n paypalEmail?: string | null,\n}\n\n/** Auto-renewal consent captured on TwouCart at checkout confirmation (SUBS-228). */\nexport interface CheckoutConsent {\n /** Main auto-renewal checkbox — always required at submit. */\n tConsentRecurring: boolean,\n /** UK/CA resident checkbox; null when that checkbox is not shown. */\n tConsentUkCa: boolean | null,\n /** ISO-8601 timestamp when consent was captured. */\n tConsentAt: string,\n /** Exact disclosure text shown to the learner (audit trail). */\n tConsentText: string,\n}\n\nexport interface UpdateCartConsentArgs {\n cartUid: string,\n consent: CheckoutConsent,\n /** Absolute checkout return URL stored in the backend's tStripeReturnUrl field. */\n returnUrl?: string | null,\n}\n\nexport interface BillingInfo {\n firstName: string,\n lastName: string,\n city: string,\n zip: string,\n country: string,\n line1: string,\n state: string,\n}\n\n// --- Checkout client surface -------------------------------------------------\n/** Result of tCartCancel (SUBS-1127). */\nexport interface CartCancelResult {\n ok: boolean,\n /** NOT_AUTHENTICATED | BI_ID_REQUIRED | CART_NOT_FOUND | NOT_YOUR_CART | CART_NOT_OPEN | CANCEL_FAILED */\n error?: string,\n}\n\nexport interface CheckoutClient {\n gql: GqlRunner,\n setToken: (token: string) => void,\n createCart: (vars: CartIds) => Promise<Cart>,\n /** Abandon the learner's own open cart, cancelling any pending Stripe / PayPal subscription on it. */\n cancelCart: (biId: string) => Promise<CartCancelResult>,\n getCartItem: (biId: string) => Promise<{ cartUid: string, cartItemUid: string }>,\n updateCartItem: (args: UpdateCartItemArgs) => Promise<void>,\n updateCartConsent: (args: UpdateCartConsentArgs) => Promise<void>,\n waitForCartBusinessProcess: (biId: string) => Promise<void>,\n placeOrder: (cartUid: string) => Promise<unknown>,\n getCartStatus: (biId: string) => Promise<CartStatusResult>,\n findSubscriptionBiId: (biId: string) => Promise<SubscriptionDetails | null>,\n getSalableUid: (maId: string) => Promise<ProductInfo>,\n getBillingPlans: () => Promise<BillingPlansResult>,\n getActiveSubscriptions: () => Promise<{ active: number }>,\n isCurrentUserSubscriber: () => Promise<CurrentUserSubscriberResult | null>,\n getCourseDetail: (courseKey: string) => Promise<CourseDetail | null>,\n}\n\nexport interface DevAuth {\n iamUrl: string,\n clientId: string,\n clientSecret: string,\n}\n\n// --- Minimal Stripe.js types -------------------------------------------------\n// Just the surface this module uses. If you depend on `@stripe/stripe-js`,\n// you can replace these with its richer types.\nexport interface StripeElementChangeEvent {\n error?: { message?: string },\n [key: string]: unknown,\n}\n\nexport interface StripeElementLike {\n mount: (node: HTMLElement) => void,\n on: (event: string, handler: (e: StripeElementChangeEvent) => void) => void,\n unmount?: () => void,\n}\n\nexport interface StripeElementsLike {\n create: (type: string, options?: Record<string, unknown>) => StripeElementLike,\n}\n\nexport interface StripeError {\n message?: string,\n code?: string,\n}\n\nexport interface PaymentMethodResult {\n paymentMethod?: { id: string },\n error?: StripeError,\n}\n\nexport interface ConfirmResult {\n error?: StripeError,\n setupIntent?: unknown,\n paymentIntent?: unknown,\n}\n\nexport interface CreatePaymentMethodParams {\n type: 'card',\n card: StripeElementLike,\n billing_details?: Record<string, unknown>,\n}\n\nexport interface StripeLike {\n elements: () => StripeElementsLike,\n createPaymentMethod: (params: CreatePaymentMethodParams) => Promise<PaymentMethodResult>,\n confirmCardSetup: (clientSecret: string) => Promise<ConfirmResult>,\n confirmCardPayment: (clientSecret: string) => Promise<ConfirmResult>,\n}\n\ndeclare global {\n interface Window {\n Stripe?: (publishableKey: string) => StripeLike,\n }\n}\n\nexport interface TrialDateRange {\n /** e.g. \"June 22, 2026\" */\n displayStart: string,\n /** e.g. \"June 29, 2026\" */\n displayEnd: string,\n}\n"]}
|
package/dist/utils/auth.js
CHANGED
|
@@ -12,6 +12,7 @@ export function createCheckoutClient({ apiUrl }) {
|
|
|
12
12
|
gql,
|
|
13
13
|
setToken,
|
|
14
14
|
createCart: (vars) => cart.createCart(gql, vars),
|
|
15
|
+
cancelCart: (biId) => cart.cancelCart(gql, biId),
|
|
15
16
|
getCartItem: (biId) => cart.getCartItem(gql, biId),
|
|
16
17
|
updateCartItem: (args) => cart.updateCartItem(gql, args),
|
|
17
18
|
updateCartConsent: (args) => cart.updateCartConsent(gql, args),
|
package/dist/utils/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAU5C,MAAM,UAAU,oBAAoB,CAClC,EAAE,MAAM,EAAwB;IAEhC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAEpE,OAAO;QACL,GAAG;QACH,QAAQ;QACR,UAAU,EAAE,CAAC,IAAa,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC;QACzD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC;QAClD,cAAc,EAAE,CAAC,IAAwB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC;QAC5E,iBAAiB,EAAE,CAAC,IAA2B,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC;QACrF,0BAA0B,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,0BAA0B,CAAC,GAAG,EAAE,IAAI,CAAC;QAChF,UAAU,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;QACtD,aAAa,EAAE,WAAW;QAC1B,oBAAoB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,EAAE,IAAI,CAAC;QACpE,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC;QAC3D,eAAe,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC;QACrD,sBAAsB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,sBAAsB,CAAC,GAAG,CAAC;QACnE,uBAAuB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,uBAAuB,CAAC,GAAG,CAAC;QACrE,eAAe,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,EAAE,SAAS,CAAC;KAC1E,CAAC;AACJ,CAAC","sourcesContent":["/**\n * api/index.ts — composes the pieces into a single checkout client and\n * re-exports the API utilities.\n */\nimport { createGqlClient } from './graphqlClient';\nimport * as cart from './cartApi';\nimport * as bootstrap from './bootstrapApi';\nimport type {\n CheckoutClient, CartIds, UpdateCartItemArgs, UpdateCartConsentArgs,\n} from '../types';\n\ninterface CheckoutClientConfig {\n apiUrl: string,\n confirmIntentUrl?: string,\n}\n\nexport function createCheckoutClient(\n { apiUrl }: CheckoutClientConfig,\n): CheckoutClient {\n const { gql, setToken } = createGqlClient({ apiUrl });\n const fetchStatus = (biId: string) => cart.getCartStatus(gql, biId);\n\n return {\n gql,\n setToken,\n createCart: (vars: CartIds) => cart.createCart(gql, vars),\n getCartItem: (biId) => cart.getCartItem(gql, biId),\n updateCartItem: (args: UpdateCartItemArgs) => cart.updateCartItem(gql, args),\n updateCartConsent: (args: UpdateCartConsentArgs) => cart.updateCartConsent(gql, args),\n waitForCartBusinessProcess: (biId) => cart.waitForCartBusinessProcess(gql, biId),\n placeOrder: (cartUid) => cart.placeOrder(gql, cartUid),\n getCartStatus: fetchStatus,\n findSubscriptionBiId: (biId) => cart.findSubscriptionBiId(gql, biId),\n getSalableUid: (maId) => bootstrap.getSalableUid(gql, maId),\n getBillingPlans: () => bootstrap.getBillingPlans(gql),\n getActiveSubscriptions: () => bootstrap.getActiveSubscriptions(gql),\n isCurrentUserSubscriber: () => bootstrap.isCurrentUserSubscriber(gql),\n getCourseDetail: (courseKey) => bootstrap.getCourseDetail(gql, courseKey),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAU5C,MAAM,UAAU,oBAAoB,CAClC,EAAE,MAAM,EAAwB;IAEhC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAEpE,OAAO;QACL,GAAG;QACH,QAAQ;QACR,UAAU,EAAE,CAAC,IAAa,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC;QACzD,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC;QAChD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC;QAClD,cAAc,EAAE,CAAC,IAAwB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC;QAC5E,iBAAiB,EAAE,CAAC,IAA2B,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC;QACrF,0BAA0B,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,0BAA0B,CAAC,GAAG,EAAE,IAAI,CAAC;QAChF,UAAU,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;QACtD,aAAa,EAAE,WAAW;QAC1B,oBAAoB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,EAAE,IAAI,CAAC;QACpE,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC;QAC3D,eAAe,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC;QACrD,sBAAsB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,sBAAsB,CAAC,GAAG,CAAC;QACnE,uBAAuB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,uBAAuB,CAAC,GAAG,CAAC;QACrE,eAAe,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,EAAE,SAAS,CAAC;KAC1E,CAAC;AACJ,CAAC","sourcesContent":["/**\n * api/index.ts — composes the pieces into a single checkout client and\n * re-exports the API utilities.\n */\nimport { createGqlClient } from './graphqlClient';\nimport * as cart from './cartApi';\nimport * as bootstrap from './bootstrapApi';\nimport type {\n CheckoutClient, CartIds, UpdateCartItemArgs, UpdateCartConsentArgs,\n} from '../types';\n\ninterface CheckoutClientConfig {\n apiUrl: string,\n confirmIntentUrl?: string,\n}\n\nexport function createCheckoutClient(\n { apiUrl }: CheckoutClientConfig,\n): CheckoutClient {\n const { gql, setToken } = createGqlClient({ apiUrl });\n const fetchStatus = (biId: string) => cart.getCartStatus(gql, biId);\n\n return {\n gql,\n setToken,\n createCart: (vars: CartIds) => cart.createCart(gql, vars),\n cancelCart: (biId) => cart.cancelCart(gql, biId),\n getCartItem: (biId) => cart.getCartItem(gql, biId),\n updateCartItem: (args: UpdateCartItemArgs) => cart.updateCartItem(gql, args),\n updateCartConsent: (args: UpdateCartConsentArgs) => cart.updateCartConsent(gql, args),\n waitForCartBusinessProcess: (biId) => cart.waitForCartBusinessProcess(gql, biId),\n placeOrder: (cartUid) => cart.placeOrder(gql, cartUid),\n getCartStatus: fetchStatus,\n findSubscriptionBiId: (biId) => cart.findSubscriptionBiId(gql, biId),\n getSalableUid: (maId) => bootstrap.getSalableUid(gql, maId),\n getBillingPlans: () => bootstrap.getBillingPlans(gql),\n getActiveSubscriptions: () => bootstrap.getActiveSubscriptions(gql),\n isCurrentUserSubscriber: () => bootstrap.isCurrentUserSubscriber(gql),\n getCourseDetail: (courseKey) => bootstrap.getCourseDetail(gql, courseKey),\n };\n}\n"]}
|
package/dist/utils/cartApi.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { GqlRunner, Cart, CartIds, CartStatusResult, UpdateCartItemArgs, UpdateCartConsentArgs, SubscriptionDetails } from '../types';
|
|
1
|
+
import type { GqlRunner, Cart, CartIds, CartStatusResult, UpdateCartItemArgs, UpdateCartConsentArgs, SubscriptionDetails, CartCancelResult } from '../types';
|
|
2
2
|
/** createTwouCart → { biId, uid } */
|
|
3
3
|
export declare function createCart(gql: GqlRunner, { customerUid, salableUid, billingPlanUid }: CartIds): Promise<Cart>;
|
|
4
|
+
/** tCartCancel → { ok, error? }. A missing or malformed result counts as not cancelled. */
|
|
5
|
+
export declare function cancelCart(gql: GqlRunner, biId: string): Promise<CartCancelResult>;
|
|
4
6
|
/** → { cartUid, cartItemUid } */
|
|
5
7
|
export declare function getCartItem(gql: GqlRunner, biId: string): Promise<{
|
|
6
8
|
cartUid: string;
|
package/dist/utils/cartApi.js
CHANGED
|
@@ -12,7 +12,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
12
12
|
* Each takes the `gql` runner as its first argument so they stay pure and
|
|
13
13
|
* independently testable (mock `gql`, assert the variables).
|
|
14
14
|
*/
|
|
15
|
-
import { CREATE_CART, GET_CART_ITEM, UPDATE_CART_ITEM, UPDATE_CART, PLACE_ORDER, GET_CART_BUSINESS_PROCESS, GET_CART_STATUS, FIND_SUBSCRIPTION, } from './queries';
|
|
15
|
+
import { CREATE_CART, GET_CART_ITEM, UPDATE_CART_ITEM, UPDATE_CART, PLACE_ORDER, GET_CART_BUSINESS_PROCESS, GET_CART_STATUS, FIND_SUBSCRIPTION, CANCEL_CART, } from './queries';
|
|
16
16
|
/** createTwouCart → { biId, uid } */
|
|
17
17
|
export function createCart(gql_1, _a) {
|
|
18
18
|
return __awaiter(this, arguments, void 0, function* (gql, { customerUid, salableUid, billingPlanUid }) {
|
|
@@ -25,6 +25,17 @@ export function createCart(gql_1, _a) {
|
|
|
25
25
|
return data.createTwouCart;
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
+
/** tCartCancel → { ok, error? }. A missing or malformed result counts as not cancelled. */
|
|
29
|
+
export function cancelCart(gql, biId) {
|
|
30
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
31
|
+
var _a;
|
|
32
|
+
const data = yield gql(CANCEL_CART, { biId });
|
|
33
|
+
const result = data === null || data === void 0 ? void 0 : data.tCartCancel;
|
|
34
|
+
if (!result || typeof result !== 'object')
|
|
35
|
+
return { ok: false, error: 'CANCEL_FAILED' };
|
|
36
|
+
return { ok: result.ok === true, error: (_a = result.error) !== null && _a !== void 0 ? _a : undefined };
|
|
37
|
+
});
|
|
38
|
+
}
|
|
28
39
|
/** → { cartUid, cartItemUid } */
|
|
29
40
|
export function getCartItem(gql, biId) {
|
|
30
41
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cartApi.js","sourceRoot":"","sources":["../../src/utils/cartApi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/cartApi.ts — one function per checkout operation.\n * Each takes the `gql` runner as its first argument so they stay pure and\n * independently testable (mock `gql`, assert the variables).\n */\nimport {\n CREATE_CART, GET_CART_ITEM, UPDATE_CART_ITEM, UPDATE_CART,\n PLACE_ORDER, GET_CART_BUSINESS_PROCESS, GET_CART_STATUS, FIND_SUBSCRIPTION,\n} from './queries';\nimport type {\n GqlRunner, Cart, CartIds, CartStatusResult, UpdateCartItemArgs,\n UpdateCartConsentArgs, Status, SubscriptionDetails,\n} from '../types';\n\n/** createTwouCart → { biId, uid } */\nexport async function createCart(gql: GqlRunner, { customerUid, salableUid, billingPlanUid }: CartIds): Promise<Cart> {\n const data = await gql(CREATE_CART, {\n input: {\n prPartyPrimary: { uid: customerUid },\n hiConsistsOf: [{ biiSalable: { uid: salableUid }, paybBillingPlan: { uid: billingPlanUid } }],\n },\n });\n return data.createTwouCart as Cart;\n}\n\n/** → { cartUid, cartItemUid } */\nexport async function getCartItem(gql: GqlRunner, biId: string): Promise<{ cartUid: string, cartItemUid: string }> {\n const data = await gql(GET_CART_ITEM, { biId });\n const bi = data.getBusinessInteraction;\n const cartUid = bi?.uid as string | undefined;\n const cartItemUid = bi?.hiConsistsOf?.[0]?.uid as string | undefined;\n\n if (!cartUid || !cartItemUid) {\n throw new Error(`Cart item not found for cart \"${biId}\".`);\n }\n\n return { cartUid, cartItemUid };\n}\n\nexport async function updateCartItem(\n gql: GqlRunner,\n { cartItemUid, billingAddress, paymentMethodId, paypalEmail }: UpdateCartItemArgs,\n): Promise<void> {\n const input: Record<string, unknown> = { uid: cartItemUid };\n // Interface-typed inputs accept exactly one key, named after the concrete\n // type, and all of its fields live inside it.\n if (billingAddress) input.paybBillingAddress = { _viaxUrbanPropertyAddress: { ...billingAddress } };\n if (paymentMethodId) {\n input.paybPayment = {\n _twouStripePayment: { tStripePaymentMethodId: paymentMethodId },\n };\n } else if (paypalEmail !== undefined) {\n input.paybPayment = {\n _twouPaypalPayment: paypalEmail ? { tPaypalEmail: paypalEmail } : {},\n };\n }\n await gql(UPDATE_CART_ITEM, { input });\n}\n\n/** Persist checkout consent checkboxes and the hosted-payment return URL onto the TwouCart BI. */\nexport async function updateCartConsent(\n gql: GqlRunner,\n { cartUid, consent, returnUrl }: UpdateCartConsentArgs,\n): Promise<void> {\n const input: Record<string, unknown> = {\n uid: cartUid,\n tConsentRecurring: consent.tConsentRecurring,\n tConsentUkCa: consent.tConsentUkCa,\n tConsentAt: consent.tConsentAt,\n tConsentText: consent.tConsentText,\n };\n if (returnUrl) input.tStripeReturnUrl = returnUrl;\n await gql(UPDATE_CART, { input });\n}\n\nconst CART_WORKFLOW_POLL_INTERVAL_MS = 250;\nconst CART_WORKFLOW_MAX_ATTEMPTS = 20;\n\ninterface WaitForCartBusinessProcessOptions {\n intervalMs?: number,\n maxAttempts?: number,\n}\n\n/** Wait until the asynchronously-created workflow is attached to the cart. */\nexport async function waitForCartBusinessProcess(\n gql: GqlRunner,\n biId: string,\n {\n intervalMs = CART_WORKFLOW_POLL_INTERVAL_MS,\n maxAttempts = CART_WORKFLOW_MAX_ATTEMPTS,\n }: WaitForCartBusinessProcessOptions = {},\n): Promise<void> {\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n const data = await gql(GET_CART_BUSINESS_PROCESS, { biId });\n if (data?.getBusinessInteraction?.bpBusinessProcess != null) return;\n\n if (attempt < maxAttempts - 1) {\n await new Promise(resolve => setTimeout(resolve, intervalMs));\n }\n }\n\n throw new Error('The cart is still being prepared. Please try placing your order again.');\n}\n\n/** \"Place order\" — triggers the server-side workflow. */\nexport async function placeOrder(gql: GqlRunner, cartUid: string): Promise<unknown> {\n const data = await gql(PLACE_ORDER, {\n input: {\n businessProcessable: { uid: cartUid },\n transitionCode: 'Sanction_Check',\n },\n });\n return data.startBusinessProcessableTransitionV2;\n}\n\n/** → current cart workflow status, Stripe details, and latest comment. */\nexport async function getCartStatus(gql: GqlRunner, biId: string): Promise<CartStatusResult> {\n const data = await gql(GET_CART_STATUS, { biId });\n const bi = data?.getBusinessInteraction || {};\n const comments: string[] = bi.cmComments?.edges?.map((e: { node: { comText: string } }) => e.node.comText) || [];\n return {\n status: (bi.bpStatus?.code ?? null) as Status | null,\n stripeSubscriptionId: bi.hiConsistsOf?.[0]?.tStripeSubscriptionId ?? null,\n stripeConfirmationRedirectUrl: bi.tStripeConfirmationRedirectUrl ?? null,\n paypalApprovalUrl: bi.tPaypalApprovalUrl ?? null,\n lastComment: comments.length ? comments[comments.length - 1] : '',\n };\n}\n\nexport async function findSubscriptionBiId(gql: GqlRunner, cartBiId: string): Promise<SubscriptionDetails | null> {\n try {\n const data = await gql(FIND_SUBSCRIPTION, { filter: `biRelatesTo.biId = '${cartBiId}'` });\n const node = data?.filterTwouSubscription?.edges?.[0]?.node;\n if (!node?.biId) return null;\n\n const item = node.hiConsistsOf?.[0];\n const payment = item?.paybPayment;\n\n return {\n biId: node.biId,\n // TwouSubscription exposes tTrialGranted / tTrialPeriod, not start/end dates.\n // Trial display dates are computed client-side via getTrialDateRange().\n trialStart: null,\n trialEnd: null,\n renewalType: node.tRenewalType ?? null,\n cardLast4: payment?.tPaymentMethodLast4 ?? null, // ← last four digits\n cardBrand: payment?.tPaymentMethodBrand ?? null, // ← card type (Visa, Mastercard etc)\n stripePaymentMethodId: payment?.tStripePaymentMethodId ?? null,\n planName: item?.paybBillingPlan?.bipOwnershipModel?.omName ?? null,\n };\n } catch {\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cartApi.js","sourceRoot":"","sources":["../../src/utils/cartApi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/cartApi.ts — one function per checkout operation.\n * Each takes the `gql` runner as its first argument so they stay pure and\n * independently testable (mock `gql`, assert the variables).\n */\nimport {\n CREATE_CART, GET_CART_ITEM, UPDATE_CART_ITEM, UPDATE_CART,\n PLACE_ORDER, GET_CART_BUSINESS_PROCESS, GET_CART_STATUS, FIND_SUBSCRIPTION, CANCEL_CART,\n} from './queries';\nimport type {\n GqlRunner, Cart, CartIds, CartStatusResult, UpdateCartItemArgs,\n UpdateCartConsentArgs, Status, SubscriptionDetails, CartCancelResult,\n} from '../types';\n\n/** createTwouCart → { biId, uid } */\nexport async function createCart(gql: GqlRunner, { customerUid, salableUid, billingPlanUid }: CartIds): Promise<Cart> {\n const data = await gql(CREATE_CART, {\n input: {\n prPartyPrimary: { uid: customerUid },\n hiConsistsOf: [{ biiSalable: { uid: salableUid }, paybBillingPlan: { uid: billingPlanUid } }],\n },\n });\n return data.createTwouCart as Cart;\n}\n\n/** tCartCancel → { ok, error? }. A missing or malformed result counts as not cancelled. */\nexport async function cancelCart(gql: GqlRunner, biId: string): Promise<CartCancelResult> {\n const data = await gql(CANCEL_CART, { biId });\n const result = data?.tCartCancel;\n if (!result || typeof result !== 'object') return { ok: false, error: 'CANCEL_FAILED' };\n return { ok: result.ok === true, error: result.error ?? undefined };\n}\n\n/** → { cartUid, cartItemUid } */\nexport async function getCartItem(gql: GqlRunner, biId: string): Promise<{ cartUid: string, cartItemUid: string }> {\n const data = await gql(GET_CART_ITEM, { biId });\n const bi = data.getBusinessInteraction;\n const cartUid = bi?.uid as string | undefined;\n const cartItemUid = bi?.hiConsistsOf?.[0]?.uid as string | undefined;\n\n if (!cartUid || !cartItemUid) {\n throw new Error(`Cart item not found for cart \"${biId}\".`);\n }\n\n return { cartUid, cartItemUid };\n}\n\nexport async function updateCartItem(\n gql: GqlRunner,\n { cartItemUid, billingAddress, paymentMethodId, paypalEmail }: UpdateCartItemArgs,\n): Promise<void> {\n const input: Record<string, unknown> = { uid: cartItemUid };\n // Interface-typed inputs accept exactly one key, named after the concrete\n // type, and all of its fields live inside it.\n if (billingAddress) input.paybBillingAddress = { _viaxUrbanPropertyAddress: { ...billingAddress } };\n if (paymentMethodId) {\n input.paybPayment = {\n _twouStripePayment: { tStripePaymentMethodId: paymentMethodId },\n };\n } else if (paypalEmail !== undefined) {\n input.paybPayment = {\n _twouPaypalPayment: paypalEmail ? { tPaypalEmail: paypalEmail } : {},\n };\n }\n await gql(UPDATE_CART_ITEM, { input });\n}\n\n/** Persist checkout consent checkboxes and the hosted-payment return URL onto the TwouCart BI. */\nexport async function updateCartConsent(\n gql: GqlRunner,\n { cartUid, consent, returnUrl }: UpdateCartConsentArgs,\n): Promise<void> {\n const input: Record<string, unknown> = {\n uid: cartUid,\n tConsentRecurring: consent.tConsentRecurring,\n tConsentUkCa: consent.tConsentUkCa,\n tConsentAt: consent.tConsentAt,\n tConsentText: consent.tConsentText,\n };\n if (returnUrl) input.tStripeReturnUrl = returnUrl;\n await gql(UPDATE_CART, { input });\n}\n\nconst CART_WORKFLOW_POLL_INTERVAL_MS = 250;\nconst CART_WORKFLOW_MAX_ATTEMPTS = 20;\n\ninterface WaitForCartBusinessProcessOptions {\n intervalMs?: number,\n maxAttempts?: number,\n}\n\n/** Wait until the asynchronously-created workflow is attached to the cart. */\nexport async function waitForCartBusinessProcess(\n gql: GqlRunner,\n biId: string,\n {\n intervalMs = CART_WORKFLOW_POLL_INTERVAL_MS,\n maxAttempts = CART_WORKFLOW_MAX_ATTEMPTS,\n }: WaitForCartBusinessProcessOptions = {},\n): Promise<void> {\n for (let attempt = 0; attempt < maxAttempts; attempt += 1) {\n const data = await gql(GET_CART_BUSINESS_PROCESS, { biId });\n if (data?.getBusinessInteraction?.bpBusinessProcess != null) return;\n\n if (attempt < maxAttempts - 1) {\n await new Promise(resolve => setTimeout(resolve, intervalMs));\n }\n }\n\n throw new Error('The cart is still being prepared. Please try placing your order again.');\n}\n\n/** \"Place order\" — triggers the server-side workflow. */\nexport async function placeOrder(gql: GqlRunner, cartUid: string): Promise<unknown> {\n const data = await gql(PLACE_ORDER, {\n input: {\n businessProcessable: { uid: cartUid },\n transitionCode: 'Sanction_Check',\n },\n });\n return data.startBusinessProcessableTransitionV2;\n}\n\n/** → current cart workflow status, Stripe details, and latest comment. */\nexport async function getCartStatus(gql: GqlRunner, biId: string): Promise<CartStatusResult> {\n const data = await gql(GET_CART_STATUS, { biId });\n const bi = data?.getBusinessInteraction || {};\n const comments: string[] = bi.cmComments?.edges?.map((e: { node: { comText: string } }) => e.node.comText) || [];\n return {\n status: (bi.bpStatus?.code ?? null) as Status | null,\n stripeSubscriptionId: bi.hiConsistsOf?.[0]?.tStripeSubscriptionId ?? null,\n stripeConfirmationRedirectUrl: bi.tStripeConfirmationRedirectUrl ?? null,\n paypalApprovalUrl: bi.tPaypalApprovalUrl ?? null,\n lastComment: comments.length ? comments[comments.length - 1] : '',\n };\n}\n\nexport async function findSubscriptionBiId(gql: GqlRunner, cartBiId: string): Promise<SubscriptionDetails | null> {\n try {\n const data = await gql(FIND_SUBSCRIPTION, { filter: `biRelatesTo.biId = '${cartBiId}'` });\n const node = data?.filterTwouSubscription?.edges?.[0]?.node;\n if (!node?.biId) return null;\n\n const item = node.hiConsistsOf?.[0];\n const payment = item?.paybPayment;\n\n return {\n biId: node.biId,\n // TwouSubscription exposes tTrialGranted / tTrialPeriod, not start/end dates.\n // Trial display dates are computed client-side via getTrialDateRange().\n trialStart: null,\n trialEnd: null,\n renewalType: node.tRenewalType ?? null,\n cardLast4: payment?.tPaymentMethodLast4 ?? null, // ← last four digits\n cardBrand: payment?.tPaymentMethodBrand ?? null, // ← card type (Visa, Mastercard etc)\n stripePaymentMethodId: payment?.tStripePaymentMethodId ?? null,\n planName: item?.paybBillingPlan?.bipOwnershipModel?.omName ?? null,\n };\n } catch {\n return null;\n }\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type CartRefusalCode = 'OPEN_CART_EXISTS' | 'ACTIVE_SUBSCRIPTION';
|
|
2
|
+
export interface CartRefusal {
|
|
3
|
+
code: CartRefusalCode;
|
|
4
|
+
biId: string;
|
|
5
|
+
}
|
|
6
|
+
/** The refusal behind a failed createCart, or null when the error is something else. */
|
|
7
|
+
export declare function parseCartRefusal(err: unknown): CartRefusal | null;
|
|
8
|
+
/**
|
|
9
|
+
* Open-cart states whose payment is still being worked on. Cancelling one of
|
|
10
|
+
* these could orphan a subscription that is half-made or already approved, so
|
|
11
|
+
* the learner waits instead; the stale-cart job abandons it if it never finishes.
|
|
12
|
+
*/
|
|
13
|
+
export declare const CART_IN_PROGRESS_STATES: readonly string[];
|
|
14
|
+
export declare const ALREADY_SUBSCRIBED_MESSAGE = "You already have an active subscription. Go to Manage subscription to view it.";
|
|
15
|
+
export declare const PREVIOUS_ORDER_PROCESSING_MESSAGE = "Your previous order is still being processed. Please wait a few minutes and try again.";
|
|
16
|
+
export declare const PREVIOUS_ORDER_STUCK_MESSAGE = "We could not clear your previous checkout. Please refresh the page and try again.";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* utils/cartRefusal.ts — read why createTwouCart was refused (SUBS-1127).
|
|
3
|
+
*
|
|
4
|
+
* The backend allows one open subscription cart per learner. A refused
|
|
5
|
+
* create comes back as the generic "Validation check is failed" message, with
|
|
6
|
+
* the reason as `<CODE>:<biId>` in `extensions` — on the error entry or at the
|
|
7
|
+
* top of the response (`extensions.validation.errors`, which is where it arrives today):
|
|
8
|
+
* OPEN_CART_EXISTS:<cart biId> — an earlier cart is still open
|
|
9
|
+
* ACTIVE_SUBSCRIPTION:<subscription biId> — the learner already subscribes
|
|
10
|
+
* The code is searched for anywhere in the error, so a change in where the
|
|
11
|
+
* platform nests it does not break the lookup.
|
|
12
|
+
*/
|
|
13
|
+
import { GqlError } from './graphqlClient';
|
|
14
|
+
const REFUSAL_PATTERN = /(OPEN_CART_EXISTS|ACTIVE_SUBSCRIPTION):([A-Za-z0-9_-]+)/;
|
|
15
|
+
/** The refusal behind a failed createCart, or null when the error is something else. */
|
|
16
|
+
export function parseCartRefusal(err) {
|
|
17
|
+
var _a;
|
|
18
|
+
if (!(err instanceof Error))
|
|
19
|
+
return null;
|
|
20
|
+
let text = err.message;
|
|
21
|
+
if (err instanceof GqlError) {
|
|
22
|
+
try {
|
|
23
|
+
text += ` ${JSON.stringify(err.errors)} ${JSON.stringify((_a = err.extensions) !== null && _a !== void 0 ? _a : null)}`;
|
|
24
|
+
}
|
|
25
|
+
catch (_b) {
|
|
26
|
+
// Unserialisable extensions — the message alone is all there is to search.
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const match = REFUSAL_PATTERN.exec(text);
|
|
30
|
+
return match ? { code: match[1], biId: match[2] } : null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Open-cart states whose payment is still being worked on. Cancelling one of
|
|
34
|
+
* these could orphan a subscription that is half-made or already approved, so
|
|
35
|
+
* the learner waits instead; the stale-cart job abandons it if it never finishes.
|
|
36
|
+
*/
|
|
37
|
+
export const CART_IN_PROGRESS_STATES = [
|
|
38
|
+
'CONFIRM',
|
|
39
|
+
'SANCTION_STARTED',
|
|
40
|
+
'SANCTION_PASSED',
|
|
41
|
+
'STRIPE_STARTED',
|
|
42
|
+
'STRIPE_SUBSCRIBED',
|
|
43
|
+
'PAYPAL_STARTED',
|
|
44
|
+
'PAYPAL_SUBSCRIBED',
|
|
45
|
+
];
|
|
46
|
+
export const ALREADY_SUBSCRIBED_MESSAGE = 'You already have an active subscription. Go to Manage subscription to view it.';
|
|
47
|
+
export const PREVIOUS_ORDER_PROCESSING_MESSAGE = 'Your previous order is still being processed. Please wait a few minutes and try again.';
|
|
48
|
+
export const PREVIOUS_ORDER_STUCK_MESSAGE = 'We could not clear your previous checkout. Please refresh the page and try again.';
|
|
49
|
+
//# sourceMappingURL=cartRefusal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cartRefusal.js","sourceRoot":"","sources":["../../src/utils/cartRefusal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAS3C,MAAM,eAAe,GAAG,yDAAyD,CAAC;AAElF,wFAAwF;AACxF,MAAM,UAAU,gBAAgB,CAAC,GAAY;;IAC3C,IAAI,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,IAAI,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC;IACvB,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,IAAI,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,MAAA,GAAG,CAAC,UAAU,mCAAI,IAAI,CAAC,EAAE,CAAC;QACrF,CAAC;QAAC,WAAM,CAAC;YACP,2EAA2E;QAC7E,CAAC;IACH,CAAC;IACD,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAoB,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAsB;IACxD,SAAS;IACT,kBAAkB;IAClB,iBAAiB;IACjB,gBAAgB;IAChB,mBAAmB;IACnB,gBAAgB;IAChB,mBAAmB;CACpB,CAAC;AAEF,MAAM,CAAC,MAAM,0BAA0B,GAAG,gFAAgF,CAAC;AAC3H,MAAM,CAAC,MAAM,iCAAiC,GAAG,wFAAwF,CAAC;AAC1I,MAAM,CAAC,MAAM,4BAA4B,GAAG,mFAAmF,CAAC","sourcesContent":["/**\n * utils/cartRefusal.ts — read why createTwouCart was refused (SUBS-1127).\n *\n * The backend allows one open subscription cart per learner. A refused\n * create comes back as the generic \"Validation check is failed\" message, with\n * the reason as `<CODE>:<biId>` in `extensions` — on the error entry or at the\n * top of the response (`extensions.validation.errors`, which is where it arrives today):\n * OPEN_CART_EXISTS:<cart biId> — an earlier cart is still open\n * ACTIVE_SUBSCRIPTION:<subscription biId> — the learner already subscribes\n * The code is searched for anywhere in the error, so a change in where the\n * platform nests it does not break the lookup.\n */\nimport { GqlError } from './graphqlClient';\n\nexport type CartRefusalCode = 'OPEN_CART_EXISTS' | 'ACTIVE_SUBSCRIPTION';\n\nexport interface CartRefusal {\n code: CartRefusalCode,\n biId: string,\n}\n\nconst REFUSAL_PATTERN = /(OPEN_CART_EXISTS|ACTIVE_SUBSCRIPTION):([A-Za-z0-9_-]+)/;\n\n/** The refusal behind a failed createCart, or null when the error is something else. */\nexport function parseCartRefusal(err: unknown): CartRefusal | null {\n if (!(err instanceof Error)) return null;\n let text = err.message;\n if (err instanceof GqlError) {\n try {\n text += ` ${JSON.stringify(err.errors)} ${JSON.stringify(err.extensions ?? null)}`;\n } catch {\n // Unserialisable extensions — the message alone is all there is to search.\n }\n }\n const match = REFUSAL_PATTERN.exec(text);\n return match ? { code: match[1] as CartRefusalCode, biId: match[2] } : null;\n}\n\n/**\n * Open-cart states whose payment is still being worked on. Cancelling one of\n * these could orphan a subscription that is half-made or already approved, so\n * the learner waits instead; the stale-cart job abandons it if it never finishes.\n */\nexport const CART_IN_PROGRESS_STATES: readonly string[] = [\n 'CONFIRM',\n 'SANCTION_STARTED',\n 'SANCTION_PASSED',\n 'STRIPE_STARTED',\n 'STRIPE_SUBSCRIBED',\n 'PAYPAL_STARTED',\n 'PAYPAL_SUBSCRIBED',\n];\n\nexport const ALREADY_SUBSCRIBED_MESSAGE = 'You already have an active subscription. Go to Manage subscription to view it.';\nexport const PREVIOUS_ORDER_PROCESSING_MESSAGE = 'Your previous order is still being processed. Please wait a few minutes and try again.';\nexport const PREVIOUS_ORDER_STUCK_MESSAGE = 'We could not clear your previous checkout. Please refresh the page and try again.';\n"]}
|
package/dist/utils/checkout.js
CHANGED
|
@@ -12,6 +12,7 @@ export function createCheckoutClient({ apiUrl }) {
|
|
|
12
12
|
gql,
|
|
13
13
|
setToken,
|
|
14
14
|
createCart: (vars) => cart.createCart(gql, vars),
|
|
15
|
+
cancelCart: (biId) => cart.cancelCart(gql, biId),
|
|
15
16
|
getCartItem: (biId) => cart.getCartItem(gql, biId),
|
|
16
17
|
updateCartItem: (args) => cart.updateCartItem(gql, args),
|
|
17
18
|
updateCartConsent: (args) => cart.updateCartConsent(gql, args),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkout.js","sourceRoot":"","sources":["../../src/utils/checkout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAS5C,MAAM,UAAU,oBAAoB,CAClC,EAAE,MAAM,EAAwB;IAEhC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAEpE,OAAO;QACL,GAAG;QACH,QAAQ;QACR,UAAU,EAAE,CAAC,IAAa,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC;QACzD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC;QAClD,cAAc,EAAE,CAAC,IAAwB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC;QAC5E,iBAAiB,EAAE,CAAC,IAA2B,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC;QACrF,0BAA0B,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,0BAA0B,CAAC,GAAG,EAAE,IAAI,CAAC;QAChF,UAAU,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;QACtD,aAAa,EAAE,WAAW;QAC1B,oBAAoB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,EAAE,IAAI,CAAC;QACpE,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC;QAC3D,eAAe,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC;QACrD,sBAAsB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,sBAAsB,CAAC,GAAG,CAAC;QACnE,uBAAuB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,uBAAuB,CAAC,GAAG,CAAC;QACrE,eAAe,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,EAAE,SAAS,CAAC;KAC1E,CAAC;AACJ,CAAC","sourcesContent":["/**\n * api/index.ts — composes the pieces into a single checkout client and\n * re-exports the API utilities.\n */\nimport { createGqlClient } from './graphqlClient';\nimport * as cart from './cartApi';\nimport * as bootstrap from './bootstrapApi';\nimport type {\n CheckoutClient, CartIds, UpdateCartItemArgs, UpdateCartConsentArgs,\n} from '../types';\n\ninterface CheckoutClientConfig {\n apiUrl: string,\n}\n\nexport function createCheckoutClient(\n { apiUrl }: CheckoutClientConfig,\n): CheckoutClient {\n const { gql, setToken } = createGqlClient({ apiUrl });\n const fetchStatus = (biId: string) => cart.getCartStatus(gql, biId);\n\n return {\n gql,\n setToken,\n createCart: (vars: CartIds) => cart.createCart(gql, vars),\n getCartItem: (biId) => cart.getCartItem(gql, biId),\n updateCartItem: (args: UpdateCartItemArgs) => cart.updateCartItem(gql, args),\n updateCartConsent: (args: UpdateCartConsentArgs) => cart.updateCartConsent(gql, args),\n waitForCartBusinessProcess: (biId) => cart.waitForCartBusinessProcess(gql, biId),\n placeOrder: (cartUid) => cart.placeOrder(gql, cartUid),\n getCartStatus: fetchStatus,\n findSubscriptionBiId: (biId) => cart.findSubscriptionBiId(gql, biId),\n getSalableUid: (maId) => bootstrap.getSalableUid(gql, maId),\n getBillingPlans: () => bootstrap.getBillingPlans(gql),\n getActiveSubscriptions: () => bootstrap.getActiveSubscriptions(gql),\n isCurrentUserSubscriber: () => bootstrap.isCurrentUserSubscriber(gql),\n getCourseDetail: (courseKey) => bootstrap.getCourseDetail(gql, courseKey),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"checkout.js","sourceRoot":"","sources":["../../src/utils/checkout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAS5C,MAAM,UAAU,oBAAoB,CAClC,EAAE,MAAM,EAAwB;IAEhC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAEpE,OAAO;QACL,GAAG;QACH,QAAQ;QACR,UAAU,EAAE,CAAC,IAAa,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC;QACzD,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC;QAChD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC;QAClD,cAAc,EAAE,CAAC,IAAwB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC;QAC5E,iBAAiB,EAAE,CAAC,IAA2B,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC;QACrF,0BAA0B,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,0BAA0B,CAAC,GAAG,EAAE,IAAI,CAAC;QAChF,UAAU,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;QACtD,aAAa,EAAE,WAAW;QAC1B,oBAAoB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,EAAE,IAAI,CAAC;QACpE,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC;QAC3D,eAAe,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC;QACrD,sBAAsB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,sBAAsB,CAAC,GAAG,CAAC;QACnE,uBAAuB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,uBAAuB,CAAC,GAAG,CAAC;QACrE,eAAe,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,EAAE,SAAS,CAAC;KAC1E,CAAC;AACJ,CAAC","sourcesContent":["/**\n * api/index.ts — composes the pieces into a single checkout client and\n * re-exports the API utilities.\n */\nimport { createGqlClient } from './graphqlClient';\nimport * as cart from './cartApi';\nimport * as bootstrap from './bootstrapApi';\nimport type {\n CheckoutClient, CartIds, UpdateCartItemArgs, UpdateCartConsentArgs,\n} from '../types';\n\ninterface CheckoutClientConfig {\n apiUrl: string,\n}\n\nexport function createCheckoutClient(\n { apiUrl }: CheckoutClientConfig,\n): CheckoutClient {\n const { gql, setToken } = createGqlClient({ apiUrl });\n const fetchStatus = (biId: string) => cart.getCartStatus(gql, biId);\n\n return {\n gql,\n setToken,\n createCart: (vars: CartIds) => cart.createCart(gql, vars),\n cancelCart: (biId) => cart.cancelCart(gql, biId),\n getCartItem: (biId) => cart.getCartItem(gql, biId),\n updateCartItem: (args: UpdateCartItemArgs) => cart.updateCartItem(gql, args),\n updateCartConsent: (args: UpdateCartConsentArgs) => cart.updateCartConsent(gql, args),\n waitForCartBusinessProcess: (biId) => cart.waitForCartBusinessProcess(gql, biId),\n placeOrder: (cartUid) => cart.placeOrder(gql, cartUid),\n getCartStatus: fetchStatus,\n findSubscriptionBiId: (biId) => cart.findSubscriptionBiId(gql, biId),\n getSalableUid: (maId) => bootstrap.getSalableUid(gql, maId),\n getBillingPlans: () => bootstrap.getBillingPlans(gql),\n getActiveSubscriptions: () => bootstrap.getActiveSubscriptions(gql),\n isCurrentUserSubscriber: () => bootstrap.isCurrentUserSubscriber(gql),\n getCourseDetail: (courseKey) => bootstrap.getCourseDetail(gql, courseKey),\n };\n}\n"]}
|
|
@@ -8,6 +8,22 @@
|
|
|
8
8
|
* surface the error and the user can retry.
|
|
9
9
|
*/
|
|
10
10
|
import type { GqlRunner } from '../types';
|
|
11
|
+
export interface GqlErrorEntry {
|
|
12
|
+
message?: string;
|
|
13
|
+
/** Server detail behind the message, e.g. a validator's refusal code (SUBS-1127). */
|
|
14
|
+
extensions?: unknown;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A GraphQL error response. Keeps the full `errors` list and the response-level
|
|
18
|
+
* `extensions`: a refused validation comes back as a generic message, with its
|
|
19
|
+
* reason in `extensions` — either on the error entry or, as createTwouCart does,
|
|
20
|
+
* at the top of the response (`extensions.validation.errors`).
|
|
21
|
+
*/
|
|
22
|
+
export declare class GqlError extends Error {
|
|
23
|
+
readonly errors: GqlErrorEntry[];
|
|
24
|
+
readonly extensions: unknown;
|
|
25
|
+
constructor(message: string, errors: GqlErrorEntry[], extensions?: unknown);
|
|
26
|
+
}
|
|
11
27
|
export declare function createGqlClient({ apiUrl }: {
|
|
12
28
|
apiUrl: string;
|
|
13
29
|
}): {
|
|
@@ -7,6 +7,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
+
/**
|
|
11
|
+
* A GraphQL error response. Keeps the full `errors` list and the response-level
|
|
12
|
+
* `extensions`: a refused validation comes back as a generic message, with its
|
|
13
|
+
* reason in `extensions` — either on the error entry or, as createTwouCart does,
|
|
14
|
+
* at the top of the response (`extensions.validation.errors`).
|
|
15
|
+
*/
|
|
16
|
+
export class GqlError extends Error {
|
|
17
|
+
constructor(message, errors, extensions) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'GqlError';
|
|
20
|
+
this.errors = errors;
|
|
21
|
+
this.extensions = extensions;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
10
24
|
export function createGqlClient({ apiUrl }) {
|
|
11
25
|
if (!apiUrl)
|
|
12
26
|
throw new Error('createGqlClient: apiUrl is required');
|
|
@@ -30,7 +44,7 @@ export function createGqlClient({ apiUrl }) {
|
|
|
30
44
|
const json = yield res.json();
|
|
31
45
|
if ((_a = json.errors) === null || _a === void 0 ? void 0 : _a.length) {
|
|
32
46
|
const msg = ((_b = json.errors[0]) === null || _b === void 0 ? void 0 : _b.message) || 'GraphQL error';
|
|
33
|
-
throw new
|
|
47
|
+
throw new GqlError(msg, json.errors, json.extensions);
|
|
34
48
|
}
|
|
35
49
|
return json.data;
|
|
36
50
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graphqlClient.js","sourceRoot":"","sources":["../../src/utils/graphqlClient.ts"],"names":[],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"graphqlClient.js","sourceRoot":"","sources":["../../src/utils/graphqlClient.ts"],"names":[],"mappings":";;;;;;;;;AAyBA;;;;;GAKG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IAKjC,YAAY,OAAe,EAAE,MAAuB,EAAE,UAAoB;QACxE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,UAAU,eAAe,CAC7B,EAAE,MAAM,EAAsB;IAE9B,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAEpE,IAAI,KAAK,GAAkB,IAAI,CAAC;IAEhC,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAQ,EAAE;QACrC,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,GAAG,EAAE,CAAC;IAC5D,CAAC,CAAC;IAEF,MAAM,GAAG,GAAc,kCAAwC,EAAE,uEAAnC,KAAK,EAAE,SAAS,EAAE,KAAK,GAAG,KAAK;;QAC3D,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QAE7F,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE;YAC9B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,KAAK,EAAE;YACrE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;SAC3C,CAAC,CAAC;QAEH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YACjC,KAAK,GAAG,IAAI,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;QAC3E,CAAC;QAED,MAAM,IAAI,GAAgB,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3C,IAAI,MAAA,IAAI,CAAC,MAAM,0CAAE,MAAM,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,CAAA,MAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,0CAAE,OAAO,KAAI,eAAe,CAAC;YACvD,MAAM,IAAI,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QACxD,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC,CAAA,CAAC;IAEF,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["/**\n * api/graphqlClient.ts — authed GraphQL client.\n *\n * No token is required at construction. Call setToken(viaxToken) after\n * GET /edx-session returns — typically on checkout page mount.\n *\n * On 401 the token is cleared and the request fails — the mutation will\n * surface the error and the user can retry.\n */\nimport type { GqlRunner } from '../types';\n\nexport interface GqlErrorEntry {\n message?: string,\n /** Server detail behind the message, e.g. a validator's refusal code (SUBS-1127). */\n extensions?: unknown,\n}\n\ninterface GqlResponse {\n\n data?: any,\n errors?: GqlErrorEntry[],\n /** Response-level detail; the createTwouCart validator puts its refusal code here. */\n extensions?: unknown,\n}\n\n/**\n * A GraphQL error response. Keeps the full `errors` list and the response-level\n * `extensions`: a refused validation comes back as a generic message, with its\n * reason in `extensions` — either on the error entry or, as createTwouCart does,\n * at the top of the response (`extensions.validation.errors`).\n */\nexport class GqlError extends Error {\n readonly errors: GqlErrorEntry[];\n\n readonly extensions: unknown;\n\n constructor(message: string, errors: GqlErrorEntry[], extensions?: unknown) {\n super(message);\n this.name = 'GqlError';\n this.errors = errors;\n this.extensions = extensions;\n }\n}\n\nexport function createGqlClient(\n { apiUrl }: { apiUrl: string },\n): { gql: GqlRunner, setToken: (token: string) => void } {\n if (!apiUrl) throw new Error('createGqlClient: apiUrl is required');\n\n let token: string | null = null;\n\n const setToken = (raw: string): void => {\n token = raw.startsWith('Bearer ') ? raw : `Bearer ${raw}`;\n };\n\n const gql: GqlRunner = async (query, variables, retry = false) => {\n if (!token) throw new Error('No viax token available. Please try placing your order again.');\n\n const res = await fetch(apiUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: token },\n body: JSON.stringify({ query, variables }),\n });\n\n if (res.status === 401 && !retry) {\n token = null;\n throw new Error('Session expired. Please try placing your order again.');\n }\n\n const json: GqlResponse = await res.json();\n if (json.errors?.length) {\n const msg = json.errors[0]?.message || 'GraphQL error';\n throw new GqlError(msg, json.errors, json.extensions);\n }\n return json.data;\n };\n\n return { gql, setToken };\n}\n"]}
|
package/dist/utils/queries.d.ts
CHANGED
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
*/
|
|
13
13
|
/** Creates a new cart during checkout. */
|
|
14
14
|
export declare const CREATE_CART = "mutation CreateCart($input: TwouCartInput!) {\n createTwouCart(input: $input) { biId uid }\n}";
|
|
15
|
+
/**
|
|
16
|
+
* Cancels the learner's own open cart: abandons it and cancels any Stripe /
|
|
17
|
+
* PayPal subscription still pending on it (SUBS-1127). Object scalar — no
|
|
18
|
+
* subfield selection; returns { ok, error? }.
|
|
19
|
+
*/
|
|
20
|
+
export declare const CANCEL_CART = "mutation CartCancel($biId: String!) {\n tCartCancel(biId: $biId)\n}";
|
|
15
21
|
/** Updates a cart item — attach billing plan / payment method. */
|
|
16
22
|
export declare const UPDATE_CART_ITEM = "mutation UpdateCartItem($input: TwouCartItemInput!) {\n updateTwouCartItem(input: $input) { uid }\n}";
|
|
17
23
|
/** Persists auto-renewal consent fields on the TwouCart BI (SUBS-228). */
|
package/dist/utils/queries.js
CHANGED
|
@@ -15,6 +15,14 @@
|
|
|
15
15
|
export const CREATE_CART = `mutation CreateCart($input: TwouCartInput!) {
|
|
16
16
|
createTwouCart(input: $input) { biId uid }
|
|
17
17
|
}`;
|
|
18
|
+
/**
|
|
19
|
+
* Cancels the learner's own open cart: abandons it and cancels any Stripe /
|
|
20
|
+
* PayPal subscription still pending on it (SUBS-1127). Object scalar — no
|
|
21
|
+
* subfield selection; returns { ok, error? }.
|
|
22
|
+
*/
|
|
23
|
+
export const CANCEL_CART = `mutation CartCancel($biId: String!) {
|
|
24
|
+
tCartCancel(biId: $biId)
|
|
25
|
+
}`;
|
|
18
26
|
/** Updates a cart item — attach billing plan / payment method. */
|
|
19
27
|
export const UPDATE_CART_ITEM = `mutation UpdateCartItem($input: TwouCartItemInput!) {
|
|
20
28
|
updateTwouCartItem(input: $input) { uid }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/utils/queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,iFAAiF;AAEjF,0CAA0C;AAC1C,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH,kEAAkE;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAG;;EAE9B,CAAC;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH,iFAAiF;AAEjF,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;EAK3B,CAAC;AAEH,sEAAsE;AACtE,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;EAIvC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;EAU7B,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;EAwB/B,CAAC;AAEH,qEAAqE;AACrE,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;EAyB1B,CAAC;AAEH,iFAAiF;AACjF,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;EAWzB,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;EAe/B,CAAC;AAEH,kEAAkE;AAClE,MAAM,CAAC,MAAM,8BAA8B,GAAG;;EAE5C,CAAC;AAEH;0GAC0G;AAC1G,MAAM,CAAC,MAAM,8BAA8B,GAAG;;EAE5C,CAAC;AAEH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4B/B,CAAC","sourcesContent":["/**\n * api/queries.ts — GraphQL documents for the checkout flow.\n * Kept as plain strings so they can be reused, logged, or tested in isolation.\n *\n * Mutation reference (from API doc):\n * updateTwouCustomer — toggle Stripe test clock on/off\n * createTwouCart — user proceeds to payment\n * updateTwouCartItem — attach billing plan / payment method to cart\n * updateTwouCart — persist auto-renewal consent (SUBS-228)\n * startBusinessProcessableTransitionV2 — place order\n * updateTwouCartItem — retry: attach new payment method\n */\n\n// ─── Mutations ────────────────────────────────────────────────────────────────\n\n/** Creates a new cart during checkout. */\nexport const CREATE_CART = `mutation CreateCart($input: TwouCartInput!) {\n createTwouCart(input: $input) { biId uid }\n}`;\n\n/** Updates a cart item — attach billing plan / payment method. */\nexport const UPDATE_CART_ITEM = `mutation UpdateCartItem($input: TwouCartItemInput!) {\n updateTwouCartItem(input: $input) { uid }\n}`;\n\n/** Persists auto-renewal consent fields on the TwouCart BI (SUBS-228). */\nexport const UPDATE_CART = `mutation UpdateCart($input: TwouCartInput!) {\n updateTwouCart(input: $input) { uid }\n}`;\n\n/** Triggers the backend workflow to place the order. */\nexport const PLACE_ORDER = `mutation PlaceOrder($input: StartBusinessProcessableTransitionInputV2!) {\n startBusinessProcessableTransitionV2(input: $input) { uid bpStatus { code } }\n}`;\n\n// ─── Queries ──────────────────────────────────────────────────────────────────\n\nexport const GET_CART_ITEM = `query GetCartItem($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n uid\n hiConsistsOf { ... on TwouCartItem { uid } }\n }\n}`;\n\n/** Checks whether the server has attached the cart's workflow yet. */\nexport const GET_CART_BUSINESS_PROCESS = `query GetCartBusinessProcess($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n bpBusinessProcess { uid }\n }\n}`;\n\nexport const GET_CART_STATUS = `query GetCartStatus($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n bpStatus { code }\n hiConsistsOf { ... on TwouCartItem { tStripeSubscriptionId } }\n ... on TwouCart {\n tStripeConfirmationRedirectUrl\n tPaypalApprovalUrl\n cmComments { edges { node { comText } } }\n }\n }\n}`;\n\nexport const FIND_SUBSCRIPTION = `query FindSubscription($filter: String!) {\n filterTwouSubscription(_first: 1, _filter: $filter) {\n edges {\n node {\n biId\n tRenewalType\n hiConsistsOf {\n ... on TwouSubscriptionItem {\n paybBillingPlan {\n tStripePriceId\n bipOwnershipModel { omName }\n }\n paybPayment {\n ... on TwouStripePayment {\n tStripePaymentMethodId\n tPaymentMethodLast4\n tPaymentMethodBrand\n }\n }\n }\n }\n }\n }\n }\n}`;\n\n/** Fetch the viax customer uid by email using filterTwouCustomer. */\nexport const GET_CUSTOMER = `query GetCustomer($filter: String!) {\n filterTwouCustomer(_first: 1, _filter: $filter) {\n edges {\n node {\n uid\n parId\n parParty {\n paName\n paContacts {\n edges {\n node {\n ... on EmailAddressContactMedium {\n email\n }\n }\n }\n }\n ... on Individual {\n indFirstName\n indLastName\n }\n }\n }\n }\n }\n}`;\n\n/** Fetch the TwouSubscriptionPlan uid to use as biiSalable in createTwouCart. */\nexport const GET_PRODUCT = `query GetProduct($maId: String!) {\n getProduct(maId: $maId) {\n maName\n uid\n ... on TwouSubscriptionPlan {\n saOwnershipModels {\n uid\n omName\n }\n }\n }\n}`;\n\nexport const GET_BILLING_PLANS = `query GetBillingPlans {\n filterCustomerBillingPlan(_first: 10) {\n edges {\n node {\n uid\n tStripePriceId\n tStripeAmount {\n amount\n units { code }\n }\n bipIsTemplate\n bipOwnershipModel { uid omName }\n }\n }\n }\n}`;\n\n/** Fetch the number of active subscriptions — unauthenticated. */\nexport const GET_ACTIVE_SUBSCRIPTIONS_STATE = `query activeSubscriptions {\n tActiveSubscriptionsState\n}`;\n\n/** Check whether the currently authenticated user already has an active subscription.\n * tIsCurrentUserSubscriber is a JSON scalar — returns the full object, no subfield selection allowed. */\nexport const GET_IS_CURRENT_USER_SUBSCRIBER = `query IsCurrentUserSubscriber {\n tIsCurrentUserSubscriber\n}`;\n\n/** Fetch a course for the enrollment card when ?course_run_key= is present. */\nexport const GET_COURSE_DETAIL = `query CourseDetail($filter: String!) {\n filterTwouCourse(_first: 1, _filter: $filter) {\n edges {\n node {\n maId\n maName\n maDescription\n tShortDescription\n imageUrl\n tCourseKey\n tBrandText\n tPrimarySubjectArea\n tLevelType\n tLanguage\n tPacingType { code }\n tDurationLow\n tDurationHigh\n tDurationUnit { code }\n tEffortLow\n tEffortHigh\n tEffortUnit { code }\n tUrlCourse\n tB2cSubscriptionInclusion\n tHasOfacRestrictions\n tCatalogActive\n }\n }\n }\n}`;\n"]}
|
|
1
|
+
{"version":3,"file":"queries.js","sourceRoot":"","sources":["../../src/utils/queries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,iFAAiF;AAEjF,0CAA0C;AAC1C,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH,kEAAkE;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAG;;EAE9B,CAAC;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,WAAW,GAAG;;EAEzB,CAAC;AAEH,iFAAiF;AAEjF,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;EAK3B,CAAC;AAEH,sEAAsE;AACtE,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;EAIvC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;EAU7B,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;EAwB/B,CAAC;AAEH,qEAAqE;AACrE,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;EAyB1B,CAAC;AAEH,iFAAiF;AACjF,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;EAWzB,CAAC;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;EAe/B,CAAC;AAEH,kEAAkE;AAClE,MAAM,CAAC,MAAM,8BAA8B,GAAG;;EAE5C,CAAC;AAEH;0GAC0G;AAC1G,MAAM,CAAC,MAAM,8BAA8B,GAAG;;EAE5C,CAAC;AAEH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4B/B,CAAC","sourcesContent":["/**\n * api/queries.ts — GraphQL documents for the checkout flow.\n * Kept as plain strings so they can be reused, logged, or tested in isolation.\n *\n * Mutation reference (from API doc):\n * updateTwouCustomer — toggle Stripe test clock on/off\n * createTwouCart — user proceeds to payment\n * updateTwouCartItem — attach billing plan / payment method to cart\n * updateTwouCart — persist auto-renewal consent (SUBS-228)\n * startBusinessProcessableTransitionV2 — place order\n * updateTwouCartItem — retry: attach new payment method\n */\n\n// ─── Mutations ────────────────────────────────────────────────────────────────\n\n/** Creates a new cart during checkout. */\nexport const CREATE_CART = `mutation CreateCart($input: TwouCartInput!) {\n createTwouCart(input: $input) { biId uid }\n}`;\n\n/**\n * Cancels the learner's own open cart: abandons it and cancels any Stripe /\n * PayPal subscription still pending on it (SUBS-1127). Object scalar — no\n * subfield selection; returns { ok, error? }.\n */\nexport const CANCEL_CART = `mutation CartCancel($biId: String!) {\n tCartCancel(biId: $biId)\n}`;\n\n/** Updates a cart item — attach billing plan / payment method. */\nexport const UPDATE_CART_ITEM = `mutation UpdateCartItem($input: TwouCartItemInput!) {\n updateTwouCartItem(input: $input) { uid }\n}`;\n\n/** Persists auto-renewal consent fields on the TwouCart BI (SUBS-228). */\nexport const UPDATE_CART = `mutation UpdateCart($input: TwouCartInput!) {\n updateTwouCart(input: $input) { uid }\n}`;\n\n/** Triggers the backend workflow to place the order. */\nexport const PLACE_ORDER = `mutation PlaceOrder($input: StartBusinessProcessableTransitionInputV2!) {\n startBusinessProcessableTransitionV2(input: $input) { uid bpStatus { code } }\n}`;\n\n// ─── Queries ──────────────────────────────────────────────────────────────────\n\nexport const GET_CART_ITEM = `query GetCartItem($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n uid\n hiConsistsOf { ... on TwouCartItem { uid } }\n }\n}`;\n\n/** Checks whether the server has attached the cart's workflow yet. */\nexport const GET_CART_BUSINESS_PROCESS = `query GetCartBusinessProcess($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n bpBusinessProcess { uid }\n }\n}`;\n\nexport const GET_CART_STATUS = `query GetCartStatus($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n bpStatus { code }\n hiConsistsOf { ... on TwouCartItem { tStripeSubscriptionId } }\n ... on TwouCart {\n tStripeConfirmationRedirectUrl\n tPaypalApprovalUrl\n cmComments { edges { node { comText } } }\n }\n }\n}`;\n\nexport const FIND_SUBSCRIPTION = `query FindSubscription($filter: String!) {\n filterTwouSubscription(_first: 1, _filter: $filter) {\n edges {\n node {\n biId\n tRenewalType\n hiConsistsOf {\n ... on TwouSubscriptionItem {\n paybBillingPlan {\n tStripePriceId\n bipOwnershipModel { omName }\n }\n paybPayment {\n ... on TwouStripePayment {\n tStripePaymentMethodId\n tPaymentMethodLast4\n tPaymentMethodBrand\n }\n }\n }\n }\n }\n }\n }\n}`;\n\n/** Fetch the viax customer uid by email using filterTwouCustomer. */\nexport const GET_CUSTOMER = `query GetCustomer($filter: String!) {\n filterTwouCustomer(_first: 1, _filter: $filter) {\n edges {\n node {\n uid\n parId\n parParty {\n paName\n paContacts {\n edges {\n node {\n ... on EmailAddressContactMedium {\n email\n }\n }\n }\n }\n ... on Individual {\n indFirstName\n indLastName\n }\n }\n }\n }\n }\n}`;\n\n/** Fetch the TwouSubscriptionPlan uid to use as biiSalable in createTwouCart. */\nexport const GET_PRODUCT = `query GetProduct($maId: String!) {\n getProduct(maId: $maId) {\n maName\n uid\n ... on TwouSubscriptionPlan {\n saOwnershipModels {\n uid\n omName\n }\n }\n }\n}`;\n\nexport const GET_BILLING_PLANS = `query GetBillingPlans {\n filterCustomerBillingPlan(_first: 10) {\n edges {\n node {\n uid\n tStripePriceId\n tStripeAmount {\n amount\n units { code }\n }\n bipIsTemplate\n bipOwnershipModel { uid omName }\n }\n }\n }\n}`;\n\n/** Fetch the number of active subscriptions — unauthenticated. */\nexport const GET_ACTIVE_SUBSCRIPTIONS_STATE = `query activeSubscriptions {\n tActiveSubscriptionsState\n}`;\n\n/** Check whether the currently authenticated user already has an active subscription.\n * tIsCurrentUserSubscriber is a JSON scalar — returns the full object, no subfield selection allowed. */\nexport const GET_IS_CURRENT_USER_SUBSCRIBER = `query IsCurrentUserSubscriber {\n tIsCurrentUserSubscriber\n}`;\n\n/** Fetch a course for the enrollment card when ?course_run_key= is present. */\nexport const GET_COURSE_DETAIL = `query CourseDetail($filter: String!) {\n filterTwouCourse(_first: 1, _filter: $filter) {\n edges {\n node {\n maId\n maName\n maDescription\n tShortDescription\n imageUrl\n tCourseKey\n tBrandText\n tPrimarySubjectArea\n tLevelType\n tLanguage\n tPacingType { code }\n tDurationLow\n tDurationHigh\n tDurationUnit { code }\n tEffortLow\n tEffortHigh\n tEffortUnit { code }\n tUrlCourse\n tB2cSubscriptionInclusion\n tHasOfacRestrictions\n tCatalogActive\n }\n }\n }\n}`;\n"]}
|