@edx/frontend-app-alacarte-checkout 1.23.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data/hooks/placedOrderPoll.d.ts +38 -0
- package/dist/data/hooks/placedOrderPoll.js +81 -0
- package/dist/data/hooks/placedOrderPoll.js.map +1 -0
- package/dist/data/hooks/useAlacarteCheckout.d.ts +4 -0
- package/dist/data/hooks/useAlacarteCheckout.js +49 -23
- package/dist/data/hooks/useAlacarteCheckout.js.map +1 -1
- package/dist/data/hooks/useAlacarteQueries.d.ts +177 -4
- package/dist/data/hooks/useAlacarteQueries.js +41 -18
- package/dist/data/hooks/useAlacarteQueries.js.map +1 -1
- package/dist/types/alacarte.d.ts +1 -1
- package/dist/types/alacarte.js.map +1 -1
- package/dist/utils/alacartePayment.d.ts +1 -1
- package/dist/utils/alacartePayment.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polling rules for tPlacedOrder / getTwouOrder after the learner pays.
|
|
3
|
+
*
|
|
4
|
+
* PAYMENT_PENDING means the payment is still being confirmed (including a
|
|
5
|
+
* 3D Secure check). Poll for up to 30 seconds. The next read is one of:
|
|
6
|
+
* - ok: true with the order — payment went through. Show confirmation for
|
|
7
|
+
* ORDER_PLACED, ORDER_FULFILLING, ORDER_COMPLETED, and
|
|
8
|
+
* ORDER_FULFILMENT_FAILED. All four are paid; status only tracks enrollment.
|
|
9
|
+
* - error: NOT_PLACED — the payment did not go through. The cart is editable
|
|
10
|
+
* again (tCheckout includes paymentDecline). Leave the poll and return
|
|
11
|
+
* the learner to checkout.
|
|
12
|
+
*/
|
|
13
|
+
import type { OrderDatesResult, PlacedOrder, PlacedOrderResult } from '../../types/alacarte';
|
|
14
|
+
export declare const PLACED_ORDER_POLL_INTERVAL_MS = 2000;
|
|
15
|
+
export declare const PLACED_ORDER_POLL_TIMEOUT_MS = 30000;
|
|
16
|
+
/** Paid placements. Fulfillment progress does not change the confirmation. */
|
|
17
|
+
export declare const PAID_PLACED_ORDER_STATUSES: readonly ["ORDER_PLACED", "ORDER_FULFILLING", "ORDER_COMPLETED", "ORDER_FULFILMENT_FAILED"];
|
|
18
|
+
export declare function isPaidPlacedOrder(result: PlacedOrderResult | undefined): result is PlacedOrderResult & {
|
|
19
|
+
order: PlacedOrder;
|
|
20
|
+
};
|
|
21
|
+
/** How long to wait before the next tPlacedOrder read, or false to stop. */
|
|
22
|
+
export declare function placedOrderRefetchInterval(result: PlacedOrderResult | undefined, elapsedMs: number): number | false;
|
|
23
|
+
/** Keep reading getTwouOrder until tPlacedAt is set, or the same 30s window ends. */
|
|
24
|
+
export declare function orderDatesRefetchInterval(result: OrderDatesResult | undefined, elapsedMs: number): number | false;
|
|
25
|
+
export interface PlacedOrderView {
|
|
26
|
+
placedOrder: PlacedOrder | null;
|
|
27
|
+
placedOrderError: string | null;
|
|
28
|
+
awaitingPlacedOrder: boolean;
|
|
29
|
+
/** NOT_PLACED: stop waiting and send the learner back to the editable cart. */
|
|
30
|
+
returnToCheckout: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare function derivePlacedOrderView(args: {
|
|
33
|
+
orderBiId: string | null;
|
|
34
|
+
result: PlacedOrderResult | undefined;
|
|
35
|
+
pollTimedOut: boolean;
|
|
36
|
+
isLoading: boolean;
|
|
37
|
+
isFetching: boolean;
|
|
38
|
+
}): PlacedOrderView;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export const PLACED_ORDER_POLL_INTERVAL_MS = 2000;
|
|
2
|
+
export const PLACED_ORDER_POLL_TIMEOUT_MS = 30000;
|
|
3
|
+
/** Paid placements. Fulfillment progress does not change the confirmation. */
|
|
4
|
+
export const PAID_PLACED_ORDER_STATUSES = [
|
|
5
|
+
'ORDER_PLACED',
|
|
6
|
+
'ORDER_FULFILLING',
|
|
7
|
+
'ORDER_COMPLETED',
|
|
8
|
+
'ORDER_FULFILMENT_FAILED',
|
|
9
|
+
];
|
|
10
|
+
const TRANSIENT_PLACED_ORDER_ERRORS = new Set([
|
|
11
|
+
'PAYMENT_PENDING',
|
|
12
|
+
'ORDER_READ_FAILED',
|
|
13
|
+
]);
|
|
14
|
+
export function isPaidPlacedOrder(result) {
|
|
15
|
+
return !!(result === null || result === void 0 ? void 0 : result.ok) && !!result.order;
|
|
16
|
+
}
|
|
17
|
+
/** How long to wait before the next tPlacedOrder read, or false to stop. */
|
|
18
|
+
export function placedOrderRefetchInterval(result, elapsedMs) {
|
|
19
|
+
if (isPaidPlacedOrder(result))
|
|
20
|
+
return false;
|
|
21
|
+
// Declined (or otherwise not placed): cart is editable again. Do not keep polling.
|
|
22
|
+
if (result && !result.ok && result.error === 'NOT_PLACED')
|
|
23
|
+
return false;
|
|
24
|
+
if (elapsedMs >= PLACED_ORDER_POLL_TIMEOUT_MS)
|
|
25
|
+
return false;
|
|
26
|
+
if (!result || (result.ok && !result.order))
|
|
27
|
+
return PLACED_ORDER_POLL_INTERVAL_MS;
|
|
28
|
+
if (!result.ok && result.error && TRANSIENT_PLACED_ORDER_ERRORS.has(result.error)) {
|
|
29
|
+
return PLACED_ORDER_POLL_INTERVAL_MS;
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
/** Keep reading getTwouOrder until tPlacedAt is set, or the same 30s window ends. */
|
|
34
|
+
export function orderDatesRefetchInterval(result, elapsedMs) {
|
|
35
|
+
var _a;
|
|
36
|
+
if ((result === null || result === void 0 ? void 0 : result.ok) && ((_a = result.dates) === null || _a === void 0 ? void 0 : _a.tPlacedAt))
|
|
37
|
+
return false;
|
|
38
|
+
if (elapsedMs >= PLACED_ORDER_POLL_TIMEOUT_MS)
|
|
39
|
+
return false;
|
|
40
|
+
return PLACED_ORDER_POLL_INTERVAL_MS;
|
|
41
|
+
}
|
|
42
|
+
export function derivePlacedOrderView(args) {
|
|
43
|
+
var _a, _b;
|
|
44
|
+
const { orderBiId, result, pollTimedOut, isLoading, isFetching, } = args;
|
|
45
|
+
const placedOrder = isPaidPlacedOrder(result) ? result.order : null;
|
|
46
|
+
const returnToCheckout = !!orderBiId
|
|
47
|
+
&& !placedOrder
|
|
48
|
+
&& (result === null || result === void 0 ? void 0 : result.ok) === false
|
|
49
|
+
&& result.error === 'NOT_PLACED';
|
|
50
|
+
let placedOrderError = null;
|
|
51
|
+
if (orderBiId && !placedOrder && !returnToCheckout && result && !result.ok) {
|
|
52
|
+
const error = (_a = result.error) !== null && _a !== void 0 ? _a : 'ORDER_READ_FAILED';
|
|
53
|
+
const stillWaiting = TRANSIENT_PLACED_ORDER_ERRORS.has(error) && !pollTimedOut;
|
|
54
|
+
if (!stillWaiting)
|
|
55
|
+
placedOrderError = error;
|
|
56
|
+
}
|
|
57
|
+
else if (orderBiId
|
|
58
|
+
&& !placedOrder
|
|
59
|
+
&& !returnToCheckout
|
|
60
|
+
&& pollTimedOut
|
|
61
|
+
&& !(result === null || result === void 0 ? void 0 : result.ok)) {
|
|
62
|
+
placedOrderError = (_b = result === null || result === void 0 ? void 0 : result.error) !== null && _b !== void 0 ? _b : 'PAYMENT_PENDING';
|
|
63
|
+
}
|
|
64
|
+
const stillPending = !!result
|
|
65
|
+
&& !result.ok
|
|
66
|
+
&& !!result.error
|
|
67
|
+
&& TRANSIENT_PLACED_ORDER_ERRORS.has(result.error);
|
|
68
|
+
const awaitingPlacedOrder = !!orderBiId && !placedOrder && !placedOrderError && (returnToCheckout
|
|
69
|
+
|| isLoading
|
|
70
|
+
|| isFetching
|
|
71
|
+
|| !result
|
|
72
|
+
|| (result.ok && !result.order)
|
|
73
|
+
|| stillPending);
|
|
74
|
+
return {
|
|
75
|
+
placedOrder,
|
|
76
|
+
placedOrderError,
|
|
77
|
+
awaitingPlacedOrder,
|
|
78
|
+
returnToCheckout,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=placedOrderPoll.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"placedOrderPoll.js","sourceRoot":"","sources":["../../../src/data/hooks/placedOrderPoll.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Polling rules for tPlacedOrder / getTwouOrder after the learner pays.\n *\n * PAYMENT_PENDING means the payment is still being confirmed (including a\n * 3D Secure check). Poll for up to 30 seconds. The next read is one of:\n * - ok: true with the order — payment went through. Show confirmation for\n * ORDER_PLACED, ORDER_FULFILLING, ORDER_COMPLETED, and\n * ORDER_FULFILMENT_FAILED. All four are paid; status only tracks enrollment.\n * - error: NOT_PLACED — the payment did not go through. The cart is editable\n * again (tCheckout includes paymentDecline). Leave the poll and return\n * the learner to checkout.\n */\nimport type { OrderDatesResult, PlacedOrder, PlacedOrderResult } from '../../types/alacarte';\n\nexport const PLACED_ORDER_POLL_INTERVAL_MS = 2000;\nexport const PLACED_ORDER_POLL_TIMEOUT_MS = 30_000;\n\n/** Paid placements. Fulfillment progress does not change the confirmation. */\nexport const PAID_PLACED_ORDER_STATUSES = [\n 'ORDER_PLACED',\n 'ORDER_FULFILLING',\n 'ORDER_COMPLETED',\n 'ORDER_FULFILMENT_FAILED',\n] as const;\n\nconst TRANSIENT_PLACED_ORDER_ERRORS = new Set([\n 'PAYMENT_PENDING',\n 'ORDER_READ_FAILED',\n]);\n\nexport function isPaidPlacedOrder(result: PlacedOrderResult | undefined): result is PlacedOrderResult & { order: PlacedOrder } {\n return !!result?.ok && !!result.order;\n}\n\n/** How long to wait before the next tPlacedOrder read, or false to stop. */\nexport function placedOrderRefetchInterval(\n result: PlacedOrderResult | undefined,\n elapsedMs: number,\n): number | false {\n if (isPaidPlacedOrder(result)) return false;\n // Declined (or otherwise not placed): cart is editable again. Do not keep polling.\n if (result && !result.ok && result.error === 'NOT_PLACED') return false;\n if (elapsedMs >= PLACED_ORDER_POLL_TIMEOUT_MS) return false;\n if (!result || (result.ok && !result.order)) return PLACED_ORDER_POLL_INTERVAL_MS;\n if (!result.ok && result.error && TRANSIENT_PLACED_ORDER_ERRORS.has(result.error)) {\n return PLACED_ORDER_POLL_INTERVAL_MS;\n }\n return false;\n}\n\n/** Keep reading getTwouOrder until tPlacedAt is set, or the same 30s window ends. */\nexport function orderDatesRefetchInterval(\n result: OrderDatesResult | undefined,\n elapsedMs: number,\n): number | false {\n if (result?.ok && result.dates?.tPlacedAt) return false;\n if (elapsedMs >= PLACED_ORDER_POLL_TIMEOUT_MS) return false;\n return PLACED_ORDER_POLL_INTERVAL_MS;\n}\n\nexport interface PlacedOrderView {\n placedOrder: PlacedOrder | null,\n placedOrderError: string | null,\n awaitingPlacedOrder: boolean,\n /** NOT_PLACED: stop waiting and send the learner back to the editable cart. */\n returnToCheckout: boolean,\n}\n\nexport function derivePlacedOrderView(args: {\n orderBiId: string | null,\n result: PlacedOrderResult | undefined,\n pollTimedOut: boolean,\n isLoading: boolean,\n isFetching: boolean,\n}): PlacedOrderView {\n const {\n orderBiId, result, pollTimedOut, isLoading, isFetching,\n } = args;\n const placedOrder = isPaidPlacedOrder(result) ? result.order : null;\n const returnToCheckout = !!orderBiId\n && !placedOrder\n && result?.ok === false\n && result.error === 'NOT_PLACED';\n\n let placedOrderError: string | null = null;\n if (orderBiId && !placedOrder && !returnToCheckout && result && !result.ok) {\n const error = result.error ?? 'ORDER_READ_FAILED';\n const stillWaiting = TRANSIENT_PLACED_ORDER_ERRORS.has(error) && !pollTimedOut;\n if (!stillWaiting) placedOrderError = error;\n } else if (\n orderBiId\n && !placedOrder\n && !returnToCheckout\n && pollTimedOut\n && !result?.ok\n ) {\n placedOrderError = result?.error ?? 'PAYMENT_PENDING';\n }\n\n const stillPending = !!result\n && !result.ok\n && !!result.error\n && TRANSIENT_PLACED_ORDER_ERRORS.has(result.error);\n\n const awaitingPlacedOrder = !!orderBiId && !placedOrder && !placedOrderError && (\n returnToCheckout\n || isLoading\n || isFetching\n || !result\n || (result.ok && !result.order)\n || stillPending\n );\n\n return {\n placedOrder,\n placedOrderError,\n awaitingPlacedOrder,\n returnToCheckout,\n };\n}\n"]}
|
|
@@ -58,6 +58,10 @@ export interface UseAlacarteCheckoutResult extends AlacartePhaseState {
|
|
|
58
58
|
paymentDecline: AlacartePaymentDecline | null;
|
|
59
59
|
/** When ?order_bi_id= is present, the placed order (or null while polling). */
|
|
60
60
|
placedOrder: PlacedOrder | null;
|
|
61
|
+
/**
|
|
62
|
+
* True while tPlacedOrder is still PAYMENT_PENDING (payment confirming,
|
|
63
|
+
* including 3DS) or while we reload tCheckout after NOT_PLACED.
|
|
64
|
+
*/
|
|
61
65
|
awaitingPlacedOrder: boolean;
|
|
62
66
|
placedOrderError: string | null;
|
|
63
67
|
/** ISO timestamp the order was placed (tPlacedAt, falling back to
|
|
@@ -22,7 +22,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
22
22
|
* See /alacarte-checkout-api.md and /alacarte-owned-products-api.md at the
|
|
23
23
|
* repo root for the underlying API contract.
|
|
24
24
|
*/
|
|
25
|
-
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
25
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
26
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
26
27
|
import { useQueryStates, useQueryState, parseAsString } from 'nuqs';
|
|
27
28
|
import { createAlacarteCheckoutClient } from '../../utils/alacarteCheckout';
|
|
28
29
|
import { getCouponErrorMessage, getCouponRecheckMessage } from '../../utils/couponErrors';
|
|
@@ -34,6 +35,8 @@ import { useSubscriptionCourseKeyQuery, useViaxSessionQuery } from './useCheckou
|
|
|
34
35
|
import { courseKeyFromCourseRunKey, lmsOriginFromSessionUrl, isCourseNotFoundError, } from '../../utils/provision';
|
|
35
36
|
import { getCourseRunKeyFromUrl } from '../../utils/url';
|
|
36
37
|
import { useOwnedProductQuery, useAlacarteCheckoutQuery, useSelectProductMutation, useProductDetailQuery, useUpdateCheckoutMutation, usePlacedOrderQuery, useOrderDatesQuery, } from './useAlacarteQueries';
|
|
38
|
+
import { alacarteKeys } from './keys';
|
|
39
|
+
import { derivePlacedOrderView } from './placedOrderPoll';
|
|
37
40
|
/**
|
|
38
41
|
* Read the product selector from the URL: course_uid+course_run_key, or
|
|
39
42
|
* program_uid, or neither. Both course params must be present together; if a
|
|
@@ -236,8 +239,9 @@ export function deriveAlacartePhase(input) {
|
|
|
236
239
|
return Object.assign(Object.assign({ phase: 'noActiveCheckout', noticeMessageId: 'noActiveCheckout' }, empty), { isAlreadyOwned: false, ownedProductName: null });
|
|
237
240
|
}
|
|
238
241
|
export function useAlacarteCheckout(options) {
|
|
239
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q
|
|
242
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
240
243
|
const { apiUrl, edxSessionUrl, skipSubscriptionCourseRuns = false } = options;
|
|
244
|
+
const queryClient = useQueryClient();
|
|
241
245
|
const selector = useProductSelector();
|
|
242
246
|
const discountCode = useDiscountCode();
|
|
243
247
|
const [orderBiId, setOrderBiId] = useQueryState('order_bi_id', parseAsString);
|
|
@@ -540,31 +544,53 @@ export function useAlacarteCheckout(options) {
|
|
|
540
544
|
return;
|
|
541
545
|
placeOrderCore(opts).catch(() => { });
|
|
542
546
|
}, [placeOrderCore, placingOrder, paymentTimedOut]);
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
547
|
+
const placedView = derivePlacedOrderView({
|
|
548
|
+
orderBiId,
|
|
549
|
+
result: placedOrderQuery.data,
|
|
550
|
+
pollTimedOut: placedOrderQuery.pollTimedOut,
|
|
551
|
+
isLoading: placedOrderQuery.isLoading,
|
|
552
|
+
isFetching: placedOrderQuery.isFetching,
|
|
553
|
+
});
|
|
554
|
+
const { placedOrder } = placedView;
|
|
555
|
+
const dates = ((_l = orderDatesQuery.data) === null || _l === void 0 ? void 0 : _l.ok) ? orderDatesQuery.data.dates : null;
|
|
556
|
+
const placedOrderPlacedAt = (_o = (_m = dates === null || dates === void 0 ? void 0 : dates.tPlacedAt) !== null && _m !== void 0 ? _m : dates === null || dates === void 0 ? void 0 : dates.biCreatedAt) !== null && _o !== void 0 ? _o : null;
|
|
557
|
+
// NOT_PLACED: the payment did not go through and the cart is editable again.
|
|
558
|
+
// Reload tCheckout (it carries paymentDecline) and drop ?order_bi_id= so the
|
|
559
|
+
// learner can try another card instead of sitting on the confirmation error.
|
|
560
|
+
// The ref blocks a second reload for the same attempt. It is not cleared in
|
|
561
|
+
// cleanup — a re-run must not cancel the reload that is already in flight.
|
|
562
|
+
const returnedToCheckoutFor = useRef(null);
|
|
563
|
+
useEffect(() => {
|
|
564
|
+
if (!orderBiId) {
|
|
565
|
+
returnedToCheckoutFor.current = null;
|
|
566
|
+
return undefined;
|
|
555
567
|
}
|
|
556
|
-
|
|
557
|
-
|
|
568
|
+
if (!placedView.returnToCheckout)
|
|
569
|
+
return undefined;
|
|
570
|
+
if (returnedToCheckoutFor.current === orderBiId)
|
|
571
|
+
return undefined;
|
|
572
|
+
returnedToCheckoutFor.current = orderBiId;
|
|
573
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
574
|
+
if (client) {
|
|
575
|
+
try {
|
|
576
|
+
const fresh = yield client.getCheckout();
|
|
577
|
+
if (fresh.ok && fresh.checkout) {
|
|
578
|
+
setCheckoutOverride(fresh.checkout);
|
|
579
|
+
queryClient.setQueryData(alacarteKeys.checkout(), fresh);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
catch (_a) {
|
|
583
|
+
// Still leave the poll — the cart is editable even if this read fails.
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
yield setOrderBiId(null);
|
|
587
|
+
}))();
|
|
588
|
+
return undefined;
|
|
589
|
+
}, [orderBiId, placedView.returnToCheckout, client, setOrderBiId, queryClient]);
|
|
558
590
|
return Object.assign(Object.assign({}, derived), { couponError, couponApplying: manualUpdateMutation.isPending, applyCoupon,
|
|
559
591
|
removeCoupon, clearCouponError: () => setCouponError(null), removedCouponCode, clearRemovedCouponCode: () => setRemovedCouponCode(null), placingOrder,
|
|
560
592
|
placeOrderError,
|
|
561
|
-
placeOrder, placeOrderAsync: placeOrderCore, clearPlaceOrderError: () => setPlaceOrderError(null), paymentTimedOut, paymentDecline: (
|
|
562
|
-
|| placedOrderQuery.isFetching
|
|
563
|
-
|| ((_s = placedOrderQuery.data) === null || _s === void 0 ? void 0 : _s.error) === 'NOT_PLACED'
|
|
564
|
-
|| ((_t = placedOrderQuery.data) === null || _t === void 0 ? void 0 : _t.error) === 'ORDER_READ_FAILED'
|
|
565
|
-
|| (((_u = placedOrderQuery.data) === null || _u === void 0 ? void 0 : _u.ok) === true && !placedOrderQuery.data.order)
|
|
566
|
-
|| !placedOrderQuery.data), placedOrderError,
|
|
567
|
-
placedOrderPlacedAt });
|
|
593
|
+
placeOrder, placeOrderAsync: placeOrderCore, clearPlaceOrderError: () => setPlaceOrderError(null), paymentTimedOut, paymentDecline: (_q = (_p = derived.checkout) === null || _p === void 0 ? void 0 : _p.paymentDecline) !== null && _q !== void 0 ? _q : null, placedOrder, awaitingPlacedOrder: placedView.awaitingPlacedOrder, placedOrderError: placedView.placedOrderError, placedOrderPlacedAt });
|
|
568
594
|
}
|
|
569
595
|
export default useAlacarteCheckout;
|
|
570
596
|
//# sourceMappingURL=useAlacarteCheckout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAlacarteCheckout.js","sourceRoot":"","sources":["../../../src/data/hooks/useAlacarteCheckout.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * data/hooks/useAlacarteCheckout.ts — page-level orchestration for the à la\n * carte checkout flow:\n *\n * 1. Read a product selector (course or program) from the URL via nuqs.\n * 2. If a product is selected: check ownership (tOwnedProducts). Already\n * owned -> 'alreadyOwned'. Otherwise select it into the checkout\n * (tCheckoutSelect) and fetch its display details in parallel.\n * 3. If no product is selected: look for an existing open checkout\n * (tCheckout). Found -> 'ready'. Not found -> 'noActiveCheckout'.\n * 4. Coupon apply/remove and payment prep via tCheckoutUpdate.\n *\n * See /alacarte-checkout-api.md and /alacarte-owned-products-api.md at the\n * repo root for the underlying API contract.\n */\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { useQueryStates, useQueryState, parseAsString } from 'nuqs';\nimport { createAlacarteCheckoutClient } from '../../utils/alacarteCheckout';\nimport { getCouponErrorMessage, getCouponRecheckMessage } from '../../utils/couponErrors';\nimport {\n placeAlacarteOrder,\n toAlacarteBillingAddress,\n} from '../../utils/alacartePayment';\nimport { AlacartePlaceOrderError } from '../../utils/placeOrderErrors';\nimport { CardValidationError } from '../../utils/stripeCard';\nimport { isRequestTimeoutError, RequestTimeoutError } from '../../utils/timeoutErrors';\nimport { useSubscriptionCourseKeyQuery, useViaxSessionQuery } from './useCheckoutQueries';\nimport {\n courseKeyFromCourseRunKey, lmsOriginFromSessionUrl, isCourseNotFoundError,\n} from '../../utils/provision';\nimport { getCourseRunKeyFromUrl } from '../../utils/url';\nimport {\n useOwnedProductQuery, useAlacarteCheckoutQuery, useSelectProductMutation, useProductDetailQuery,\n useUpdateCheckoutMutation, usePlacedOrderQuery, useOrderDatesQuery,\n} from './useAlacarteQueries';\nimport type {\n AlacarteCheckout, AlacarteCheckoutResult, AlacarteLine, AlacarteMoney, AlacartePaymentDecline,\n AlacartePriceChanged, OwnedProduct, OwnedProductResult, PlacedOrder, ProductDetail, ProductSelector,\n} from '../../types/alacarte';\nimport type { BillingInfo } from '../../types';\n\nexport type AlacartePhase = 'loading' | 'alreadyOwned' | 'noActiveCheckout' | 'notPurchasable' | 'ready' | 'error';\n\nexport type AlacarteNoticeMessageId = 'alreadyOwnedCourse' | 'alreadyOwnedProgram' | 'noActiveCheckout';\n\n/** Pure phase decision output — no action callbacks. */\nexport interface AlacartePhaseState {\n phase: AlacartePhase,\n noticeMessageId?: AlacarteNoticeMessageId,\n selector: ProductSelector,\n checkout: AlacarteCheckout | null,\n line: AlacarteLine | null,\n productDetail: ProductDetail | null,\n error: string | null,\n priceChanged: AlacartePriceChanged | null,\n priceAccepted: boolean,\n /** True when the learner already owns this product. The checkout data may\n * still be populated so the UI can render the full page under an overlay. */\n isAlreadyOwned: boolean,\n /** Product name from the owned-products query — used as a fallback when the\n * checkout line (which normally carries the name) is not available. */\n ownedProductName: string | null,\n}\n\nexport interface UseAlacarteCheckoutResult extends AlacartePhaseState {\n couponError: string | null,\n couponApplying: boolean,\n applyCoupon: (code: string) => void,\n removeCoupon: () => void,\n clearCouponError: () => void,\n /** Code of the coupon just removed, set only once the removal round-trip\n * completes — kept in sync with when the discount actually leaves the line. */\n removedCouponCode: string | null,\n clearRemovedCouponCode: () => void,\n placingOrder: boolean,\n placeOrderError: string | null,\n /**\n * Accepts price (if needed) + terms, calls tCheckoutPlaceOrder with billing,\n * confirms the card via Stripe.js, then sets ?order_bi_id= to poll tPlacedOrder.\n * Fire-and-forget — errors go to placeOrderError state.\n */\n placeOrder: (opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => void,\n /**\n * Same as placeOrder but returns a Promise — used by ExpressCheckout\n * so the wallet sheet can await the result and call ev.complete().\n */\n placeOrderAsync: (opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => Promise<void>,\n clearPlaceOrderError: () => void,\n /** True once the payment provider timed out during place order (HTTP 408 /\n * Stripe network error). The order may still go through, so further\n * attempts are blocked and the UI shows the timeout warning instead. */\n paymentTimedOut: boolean,\n /** Decline details from the last card attempt on this checkout, if any. */\n paymentDecline: AlacartePaymentDecline | null,\n /** When ?order_bi_id= is present, the placed order (or null while polling). */\n placedOrder: PlacedOrder | null,\n awaitingPlacedOrder: boolean,\n placedOrderError: string | null,\n /** ISO timestamp the order was placed (tPlacedAt, falling back to\n * biCreatedAt); null while the order-dates query hasn't resolved yet. */\n placedOrderPlacedAt: string | null,\n}\n\n/**\n * Read the product selector from the URL: course_uid+course_run_key, or\n * program_uid, or neither. Both course params must be present together; if a\n * course param and a program param are both present, that's\n * CONFLICTING_SELECTION.\n *\n * NOTE: course_run_key values look like \"course-v1:HarvardX+CS50+X\" — nuqs\n * decodes query values the standards-compliant way (literal `+` -> space),\n * same as native URLSearchParams. runKey is therefore read from the raw query\n * string via getCourseRunKeyFromUrl(), which preserves literal `+`.\n */\nexport function useProductSelector(): ProductSelector {\n const [{ courseUid, runKey: nuqsRunKey, programUid }] = useQueryStates({\n courseUid: parseAsString,\n runKey: parseAsString,\n programUid: parseAsString,\n }, {\n urlKeys: {\n courseUid: 'course_uid',\n runKey: 'course_run_key',\n programUid: 'program_uid',\n },\n });\n const runKey = nuqsRunKey ? (getCourseRunKeyFromUrl() ?? nuqsRunKey) : null;\n\n return useMemo<ProductSelector>(() => {\n const hasCourse = !!courseUid || !!runKey;\n const hasProgram = !!programUid;\n if (hasCourse && hasProgram) return { kind: 'invalid' };\n if (courseUid && runKey) return { kind: 'course', courseUid, runKey };\n if (programUid) return { kind: 'program', programUid };\n return { kind: 'none' };\n }, [courseUid, runKey, programUid]);\n}\n\n/** Reads ?discount_code= — applied to the checkout right after it's created. */\nexport function useDiscountCode(): string | null {\n const [discountCode] = useQueryState('discount_code', parseAsString);\n return discountCode;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : 'Unknown error';\n}\n\n/**\n * Build a synthetic AlacarteLine from the owned-product pricing data so the\n * checkout overlay can show subtotal / deduction / total even when the backend\n * blocks tCheckoutSelect and returns no checkout object.\n */\nfunction buildLineFromOwnedProduct(product: OwnedProduct): AlacarteLine | null {\n if (!product.price) return null;\n const { full, deduction, amountDue, currency } = product.price;\n const toMoney = (amount: number): AlacarteMoney => ({ amount, currency });\n\n if (product.kind === 'program') {\n return {\n biiId: '',\n kind: 'program',\n programId: product.productId,\n programName: product.name,\n price: toMoney(full),\n deduction: deduction ? toMoney(deduction) : null,\n ownedCourses: product.ownedCourses.map((c) => ({\n courseId: c.courseId, name: c.name, via: c.via,\n })),\n discount: null,\n amountDue: toMoney(amountDue),\n billingAddressSet: false,\n };\n }\n\n return {\n biiId: '',\n kind: 'course',\n courseName: product.name,\n courseUid: product.productId,\n courseRunKey: '',\n seatType: 'verified',\n price: toMoney(full),\n discount: null,\n amountDue: toMoney(amountDue),\n billingAddressSet: false,\n };\n}\n\ninterface QueryLikeState<T> {\n isLoading: boolean,\n isError: boolean,\n error?: unknown,\n data?: T,\n}\n\ninterface MutationLikeState<T> {\n isIdle: boolean,\n isPending: boolean,\n isError: boolean,\n error?: unknown,\n data?: T,\n}\n\nexport interface DeriveAlacartePhaseInput {\n selector: ProductSelector,\n sessionReady: boolean,\n sessionIsError: boolean,\n sessionErrorMessage?: string,\n ownedQuery: QueryLikeState<OwnedProductResult>,\n selectMutation: MutationLikeState<AlacarteCheckoutResult>,\n checkoutQuery: QueryLikeState<AlacarteCheckoutResult>,\n productDetail: ProductDetail | null,\n /** Coupon (?discount_code=) applied right after the checkout is created —\n * when it succeeds, its checkout (with the coupon's discount reflected)\n * is shown in place of the pre-coupon one from selectMutation. */\n couponMutation?: MutationLikeState<AlacarteCheckoutResult>,\n /** True when a URL discount code still needs to be applied — the phase\n * stays 'loading' until the coupon mutation resolves so the user never sees\n * the pre-discount price flash. */\n urlCouponPending?: boolean,\n /** Latest successful manual update (coupon / acceptPrice / terms). Wins over\n * select + URL coupon when present. */\n checkoutOverride?: AlacarteCheckout | null,\n /** Course-runs lookup for a course selector. Empty `elements` is notFound. */\n courseRunLookup?: { pending: boolean, notFound: boolean },\n}\n\n/**\n * Pure decision function — no hooks, no React Query/nuqs types beyond plain\n * data — so every branch combination can be unit-tested directly.\n */\nexport function deriveAlacartePhase(input: DeriveAlacartePhaseInput): AlacartePhaseState {\n const {\n selector, sessionReady, sessionIsError, sessionErrorMessage,\n ownedQuery, selectMutation, checkoutQuery, productDetail, couponMutation,\n checkoutOverride = null, urlCouponPending: urlCouponPendingProp = false,\n courseRunLookup = { pending: false, notFound: false },\n } = input;\n\n const ownedProductName = ownedQuery.data?.product?.name ?? null;\n\n const empty: Omit<AlacartePhaseState, 'phase'> = {\n selector,\n checkout: null,\n line: null,\n productDetail: null,\n error: null,\n priceChanged: null,\n priceAccepted: false,\n isAlreadyOwned: false,\n ownedProductName: null,\n };\n\n const withCheckout = (\n checkout: AlacarteCheckout | null,\n priceChanged: AlacartePriceChanged | null = null,\n isAlreadyOwned = false,\n ): AlacartePhaseState => ({\n phase: isAlreadyOwned ? 'alreadyOwned' : 'ready',\n selector,\n checkout,\n line: checkout?.line ?? null,\n productDetail,\n error: null,\n priceChanged: priceChanged ?? null,\n priceAccepted: checkout?.priceAccepted === true,\n isAlreadyOwned,\n ownedProductName: isAlreadyOwned ? ownedProductName : null,\n });\n\n if (selector.kind === 'invalid') {\n return { phase: 'error', ...empty, error: 'CONFLICTING_SELECTION' };\n }\n if (sessionIsError) {\n return { phase: 'error', ...empty, error: sessionErrorMessage ?? 'Session error' };\n }\n\n const hasSelector = selector.kind === 'course' || selector.kind === 'program';\n\n if (hasSelector) {\n if (selector.kind === 'course' && courseRunLookup.notFound) {\n return {\n phase: 'error',\n ...empty,\n error: 'This course could not be found.',\n };\n }\n if (!sessionReady || ownedQuery.isLoading || courseRunLookup.pending) {\n return { phase: 'loading', ...empty };\n }\n if (ownedQuery.isError) {\n return { phase: 'error', ...empty, error: errorMessage(ownedQuery.error) };\n }\n\n const isOwned = ownedQuery.data?.ok === true && ownedQuery.data.product?.status === 'full';\n\n if (selectMutation.isIdle || selectMutation.isPending) {\n return { phase: 'loading', ...empty };\n }\n if (selectMutation.isError) {\n return { phase: 'error', ...empty, error: errorMessage(selectMutation.error) };\n }\n if (selectMutation.data && !selectMutation.data.ok) {\n // The backend may block ALREADY_OWNED at select time. When the\n // blocked response still includes checkout data (line, pricing), use\n // it so the overlay page shows the real totals. Fall back to the\n // standalone tCheckout query, then to an empty state.\n if (selectMutation.data.blocked?.reason === 'ALREADY_OWNED') {\n const blockedCheckout = selectMutation.data.checkout\n ?? (checkoutQuery.data?.ok ? checkoutQuery.data.checkout : null);\n if (blockedCheckout) {\n return withCheckout(blockedCheckout, selectMutation.data.priceChanged ?? null, true);\n }\n // Build a synthetic line from the owned-product pricing so the\n // overlay can show subtotal / deduction / total.\n const ownedProduct = ownedQuery.data?.product;\n const syntheticLine = ownedProduct ? buildLineFromOwnedProduct(ownedProduct) : null;\n return {\n phase: 'alreadyOwned',\n noticeMessageId: selectMutation.data.blocked.kind === 'course' ? 'alreadyOwnedCourse' : 'alreadyOwnedProgram',\n ...empty,\n line: syntheticLine,\n productDetail,\n isAlreadyOwned: true,\n ownedProductName,\n };\n }\n if (selectMutation.data.blocked?.reason === 'NOT_PURCHASABLE') {\n return {\n phase: 'notPurchasable',\n ...empty,\n productDetail,\n error: selectMutation.data.blocked.kind === 'program'\n ? 'This program is not eligible for checkout.'\n : 'This course is not eligible for checkout.',\n };\n }\n const selectError = selectMutation.data.error;\n if (selectError === 'COURSE_NOT_FOUND' || selectError === 'PRODUCT_NOT_FOUND') {\n return {\n phase: 'error',\n ...empty,\n error: 'This course could not be found.',\n };\n }\n if (selectError === 'PROGRAM_NOT_FOUND') {\n return {\n phase: 'error',\n ...empty,\n error: 'This program could not be found.',\n };\n }\n const blockedDetail = selectMutation.data.blocked?.detail;\n return {\n phase: 'error',\n ...empty,\n error: blockedDetail ?? selectError ?? 'unknown',\n };\n }\n\n // When a URL discount code still needs to be applied, stay in 'loading'\n // so the user never sees the pre-discount price flash.\n if (urlCouponPendingProp && !checkoutOverride) {\n return { phase: 'loading', ...empty };\n }\n\n // Once the coupon-apply (?discount_code=) mutation succeeds, its checkout\n // (with the discount reflected) supersedes the pre-coupon one. Manual\n // updates (override) win over both.\n const finalCheckout = checkoutOverride\n ?? (couponMutation?.data?.ok\n ? couponMutation.data.checkout\n : selectMutation.data?.checkout ?? null);\n\n // When select succeeds but the owned-products query says the learner\n // already has this product, show the full checkout under an overlay.\n return withCheckout(\n finalCheckout,\n selectMutation.data?.priceChanged\n ?? (couponMutation?.data?.ok ? couponMutation.data.priceChanged : undefined)\n ?? null,\n isOwned,\n );\n }\n\n // No selector: fall back to the learner's existing open checkout, if any.\n if (!sessionReady || checkoutQuery.isLoading) {\n return { phase: 'loading', ...empty };\n }\n if (checkoutQuery.isError) {\n return { phase: 'error', ...empty, error: errorMessage(checkoutQuery.error) };\n }\n if (checkoutOverride || checkoutQuery.data?.checkout) {\n const checkout = checkoutOverride ?? checkoutQuery.data!.checkout;\n return withCheckout(checkout, checkoutQuery.data?.priceChanged ?? null);\n }\n\n return { phase: 'noActiveCheckout', noticeMessageId: 'noActiveCheckout', ...empty, isAlreadyOwned: false, ownedProductName: null };\n}\n\nexport function useAlacarteCheckout(\n options: {\n apiUrl?: string,\n edxSessionUrl?: string,\n /**\n * When true, skip GET /api/subscription/course_runs/ and call Viax with a\n * course key derived from ?course_run_key= instead.\n */\n skipSubscriptionCourseRuns?: boolean,\n },\n): UseAlacarteCheckoutResult {\n const { apiUrl, edxSessionUrl, skipSubscriptionCourseRuns = false } = options;\n const selector = useProductSelector();\n const discountCode = useDiscountCode();\n const [orderBiId, setOrderBiId] = useQueryState('order_bi_id', parseAsString);\n\n const client = useMemo(\n () => (apiUrl ? createAlacarteCheckoutClient({ apiUrl }) : null),\n [apiUrl],\n );\n\n // Reuses the same query key as useCheckout's session exchange — React\n // Query dedupes the network call regardless of how many hooks read it.\n const sessionQuery = useViaxSessionQuery(edxSessionUrl ?? null);\n const sessionReady = !!sessionQuery.data;\n\n useEffect(() => {\n if (sessionQuery.data && client) {\n client.setToken(sessionQuery.data.viaxToken);\n }\n }, [sessionQuery.data, client]);\n\n const hasSelector = selector.kind === 'course' || selector.kind === 'program';\n const productId = selector.kind === 'course'\n ? selector.courseUid\n : selector.kind === 'program' ? selector.programUid : null;\n\n const ownedQuery = useOwnedProductQuery(client, sessionReady && hasSelector, productId);\n const isOwned = ownedQuery.data?.ok === true && ownedQuery.data.product?.status === 'full';\n\n const selectMutation = useSelectProductMutation(client);\n const {\n isIdle: selectIsIdle, mutate: selectMutate, reset: selectReset,\n } = selectMutation;\n\n const courseRunKey = selector.kind === 'course' ? selector.runKey : null;\n const lmsBaseUrl = useMemo(\n () => lmsOriginFromSessionUrl(edxSessionUrl),\n [edxSessionUrl],\n );\n const courseLookupEnabled = !skipSubscriptionCourseRuns\n && selector.kind === 'course'\n && !!lmsBaseUrl\n && !!courseRunKey;\n const courseKeyQuery = useSubscriptionCourseKeyQuery(\n skipSubscriptionCourseRuns ? null : lmsBaseUrl,\n skipSubscriptionCourseRuns ? null : courseRunKey,\n );\n const courseKey = skipSubscriptionCourseRuns\n ? (courseRunKey ? courseKeyFromCourseRunKey(courseRunKey) : null)\n : (courseKeyQuery.data ?? null);\n const courseNotFound = courseLookupEnabled\n && courseKeyQuery.isError\n && isCourseNotFoundError(courseKeyQuery.error);\n\n // Mutations aren't called during render — fire once the owned-products\n // query resolves (regardless of result) so we always get checkout/line data.\n // A course is not selected until the course-runs lookup returns a course.\n // Pass viewedPrice from tOwnedProducts.amountDue (ALC-159) when available.\n useEffect(() => {\n if (!hasSelector || ownedQuery.isLoading || ownedQuery.isError) return;\n if (selector.kind === 'course' && !courseKey) return;\n if (!selectIsIdle) return;\n const amountDue = ownedQuery.data?.product?.price?.amountDue;\n selectMutate({\n selector,\n viewedPrice: typeof amountDue === 'number' ? amountDue : undefined,\n });\n }, [\n hasSelector,\n ownedQuery.isLoading,\n ownedQuery.isError,\n ownedQuery.data?.product?.price?.amountDue,\n selector,\n courseKey,\n selectIsIdle,\n selectMutate,\n ]);\n\n const productDetailEnabled = hasSelector && sessionReady && !ownedQuery.isLoading\n && (selector.kind !== 'course' || !!courseKey);\n const productDetailQuery = useProductDetailQuery(client, productDetailEnabled, selector, courseKey);\n\n // Run the checkout query when there's no selector (standard path) OR when\n // the product is already owned — after the select mutation is blocked with\n // ALREADY_OWNED the server may still have an open checkout with pricing data.\n const selectBlocked = !selectMutation.isIdle && !selectMutation.isPending\n && selectMutation.data && !selectMutation.data.ok;\n const checkoutQuery = useAlacarteCheckoutQuery(\n client,\n sessionReady && (!hasSelector || (isOwned && !!selectBlocked)),\n );\n\n // Apply ?discount_code= to the freshly-created checkout — fired once the\n // select mutation succeeds with a checkout that doesn't already carry it.\n const urlCouponMutation = useUpdateCheckoutMutation(client);\n const {\n isIdle: couponIsIdle, mutate: couponMutate, reset: urlCouponReset,\n } = urlCouponMutation;\n const createdCheckout = selectMutation.data?.ok ? selectMutation.data.checkout : null;\n\n useEffect(() => {\n if (!createdCheckout || !discountCode) return;\n if (createdCheckout.couponCode === discountCode) return;\n if (couponIsIdle) {\n couponMutate({ biId: createdCheckout.biId, couponCode: discountCode });\n }\n }, [createdCheckout, discountCode, couponIsIdle, couponMutate]);\n\n const [checkoutOverride, setCheckoutOverride] = useState<AlacarteCheckout | null>(null);\n const [couponError, setCouponError] = useState<string | null>(null);\n const [removedCouponCode, setRemovedCouponCode] = useState<string | null>(null);\n const [placeOrderError, setPlaceOrderError] = useState<string | null>(null);\n const [placingOrder, setPlacingOrder] = useState(false);\n const [paymentTimedOut, setPaymentTimedOut] = useState(false);\n // biId of a checkout that's locked (CART_PAYMENT_PENDING) from a prior\n // tCheckoutPlaceOrder call whose Stripe confirmation was rejected\n // client-side. The next attempt must skip tCheckoutUpdate (it would fail\n // with CHECKOUT_NOT_OPEN) and go straight to tCheckoutPlaceOrder again.\n const [cardRetryBiId, setCardRetryBiId] = useState<string | null>(null);\n\n // Reset local overrides and in-flight select/coupon when the product changes\n // so viewedPrice / couponRecheck run again for the new selection.\n useEffect(() => {\n setCheckoutOverride(null);\n setCouponError(null);\n setRemovedCouponCode(null);\n setPlaceOrderError(null);\n setPaymentTimedOut(false);\n setCardRetryBiId(null);\n selectReset();\n urlCouponReset();\n }, [selector, selectReset, urlCouponReset]);\n\n // Surface couponRecheck from product switch (section 2 of the API doc).\n useEffect(() => {\n const result = selectMutation.data;\n if (!result?.ok) return;\n const message = getCouponRecheckMessage(\n result.couponRecheck,\n result.checkout?.line?.discountSource,\n );\n if (message) setCouponError(message);\n }, [selectMutation.data]);\n\n // Surface ?discount_code= apply failures (domain ok:false or transport error).\n useEffect(() => {\n if (!discountCode) return;\n if (urlCouponMutation.isIdle || urlCouponMutation.isPending) return;\n if (urlCouponMutation.isError) {\n setCouponError(errorMessage(urlCouponMutation.error));\n return;\n }\n const result = urlCouponMutation.data;\n if (result && !result.ok) {\n setCouponError(getCouponErrorMessage(result.error, discountCode));\n }\n }, [\n discountCode,\n urlCouponMutation.isIdle,\n urlCouponMutation.isPending,\n urlCouponMutation.isError,\n urlCouponMutation.error,\n urlCouponMutation.data,\n ]);\n\n const manualUpdateMutation = useUpdateCheckoutMutation(client);\n\n const placedOrderQuery = usePlacedOrderQuery(\n client,\n orderBiId,\n sessionReady && !!orderBiId,\n );\n\n const orderDatesQuery = useOrderDatesQuery(\n client,\n orderBiId,\n sessionReady && !!orderBiId,\n );\n\n const derived = deriveAlacartePhase({\n selector,\n sessionReady,\n sessionIsError: sessionQuery.isError,\n sessionErrorMessage: sessionQuery.error?.message,\n ownedQuery: {\n isLoading: ownedQuery.isLoading,\n isError: ownedQuery.isError,\n error: ownedQuery.error,\n data: ownedQuery.data,\n },\n selectMutation: {\n isIdle: selectMutation.isIdle,\n isPending: selectMutation.isPending,\n isError: selectMutation.isError,\n error: selectMutation.error,\n data: selectMutation.data,\n },\n checkoutQuery: {\n isLoading: checkoutQuery.isLoading,\n isError: checkoutQuery.isError,\n error: checkoutQuery.error,\n data: checkoutQuery.data,\n },\n productDetail: productDetailQuery.data ?? null,\n courseRunLookup: {\n pending: courseLookupEnabled && courseKeyQuery.isLoading,\n notFound: courseNotFound,\n },\n couponMutation: {\n isIdle: urlCouponMutation.isIdle,\n isPending: urlCouponMutation.isPending,\n isError: urlCouponMutation.isError,\n error: urlCouponMutation.error,\n data: urlCouponMutation.data,\n },\n checkoutOverride,\n // The URL coupon is pending only when the checkout exists, doesn't already\n // carry the code, and the mutation hasn't completed yet. When the backend\n // returns the coupon pre-applied (couponCode === discountCode) or when\n // there's no discount_code param at all, this is false from the start.\n urlCouponPending: !!discountCode\n && !!createdCheckout\n && createdCheckout.couponCode !== discountCode\n && (urlCouponMutation.isIdle || urlCouponMutation.isPending),\n });\n\n const applyCoupon = useCallback((code: string) => {\n const biId = derived.checkout?.biId;\n if (!biId || !client) return;\n const trimmed = code.trim();\n if (!trimmed) return;\n\n setCouponError(null);\n manualUpdateMutation.mutate(\n { biId, couponCode: trimmed },\n {\n onSuccess: (result) => {\n if (result.ok && result.checkout) {\n setCheckoutOverride(result.checkout);\n setCouponError(null);\n setRemovedCouponCode(null);\n } else {\n setCouponError(getCouponErrorMessage(result.error, trimmed));\n }\n },\n onError: (err) => {\n setCouponError(errorMessage(err));\n },\n },\n );\n }, [derived.checkout?.biId, client, manualUpdateMutation]);\n\n const removeCoupon = useCallback(() => {\n const currentCheckout = derived.checkout;\n if (!currentCheckout?.biId || !client) return;\n const removedCode = currentCheckout.couponCode?.trim() || null;\n\n setCouponError(null);\n manualUpdateMutation.mutate(\n { biId: currentCheckout.biId, couponCode: '' },\n {\n onSuccess: (result) => {\n if (!result.ok) {\n setCouponError(getCouponErrorMessage(result.error));\n return;\n }\n // The backend may return a checkout whose line still carries the\n // old discount/amountDue. Always patch the line to clear the\n // coupon so the cart summary reflects the removal immediately.\n const base = result.checkout ?? currentCheckout;\n const line = base.line;\n const patchedLine = line\n ? {\n ...line,\n discount: null,\n amountDue: line.price && line.kind === 'program' && 'deduction' in line && line.deduction\n ? { amount: line.price.amount - line.deduction.amount, currency: line.price.currency }\n : line.price,\n } as typeof line\n : null;\n setCheckoutOverride({\n ...base,\n couponCode: null,\n line: patchedLine,\n });\n setCouponError(null);\n if (removedCode) setRemovedCouponCode(removedCode);\n },\n onError: (err) => {\n setCouponError(errorMessage(err));\n },\n },\n );\n }, [derived.checkout, client, manualUpdateMutation]);\n\n const placeOrderCore = useCallback(async (opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => {\n const checkout = derived.checkout;\n if (!checkout || !client) throw new Error('Checkout not ready');\n // A timed-out attempt may still complete — never start a second one.\n if (paymentTimedOut) throw new RequestTimeoutError();\n\n setPlacingOrder(true);\n setPlaceOrderError(null);\n\n try {\n const { checkout: updated } = await placeAlacarteOrder({\n updateCheckout: (args) => client.updateCheckout(args),\n placeCheckoutOrder: (biId, billing) => client.placeCheckoutOrder(biId, billing),\n checkout,\n billing: toAlacarteBillingAddress(opts.billing),\n confirmCardPayment: opts.confirmCardPayment,\n skipPrepare: cardRetryBiId === checkout.biId,\n });\n\n setCardRetryBiId(null);\n setCheckoutOverride(updated);\n await setOrderBiId(updated.biId);\n } catch (err: unknown) {\n if (err instanceof CardValidationError) {\n // tCheckoutPlaceOrder already succeeded and locked the checkout\n // (CART_PAYMENT_PENDING); Stripe rejected the card before it ever\n // reached the backend. Next attempt must retry tCheckoutPlaceOrder\n // directly, not tCheckoutUpdate.\n setCardRetryBiId(checkout.biId);\n } else {\n setCardRetryBiId(null);\n }\n if (err instanceof AlacartePlaceOrderError && err.checkout) {\n setCheckoutOverride(err.checkout);\n }\n // The timeout warning replaces the generic error banner.\n if (isRequestTimeoutError(err)) {\n setPaymentTimedOut(true);\n } else {\n setPlaceOrderError(errorMessage(err));\n }\n throw err;\n } finally {\n setPlacingOrder(false);\n }\n }, [derived.checkout, client, setOrderBiId, cardRetryBiId, paymentTimedOut]);\n\n /** Fire-and-forget wrapper for card payments. */\n const placeOrder = useCallback((opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => {\n if (placingOrder || paymentTimedOut) return;\n placeOrderCore(opts).catch(() => { /* error already in state */ });\n }, [placeOrderCore, placingOrder, paymentTimedOut]);\n\n const placedOrder = placedOrderQuery.data?.ok ? placedOrderQuery.data.order : null;\n\n const dates = orderDatesQuery.data?.ok ? orderDatesQuery.data.dates : null;\n const placedOrderPlacedAt = dates?.tPlacedAt ?? dates?.biCreatedAt ?? null;\n\n const placedOrderError = (() => {\n if (!orderBiId) return null;\n if (\n placedOrderQuery.data\n && !placedOrderQuery.data.ok\n && placedOrderQuery.data.error !== 'NOT_PLACED'\n && placedOrderQuery.data.error !== 'ORDER_READ_FAILED'\n ) {\n return placedOrderQuery.data.error ?? 'ORDER_READ_FAILED';\n }\n return null;\n })();\n\n return {\n ...derived,\n couponError,\n couponApplying: manualUpdateMutation.isPending,\n applyCoupon,\n removeCoupon,\n clearCouponError: () => setCouponError(null),\n removedCouponCode,\n clearRemovedCouponCode: () => setRemovedCouponCode(null),\n placingOrder,\n placeOrderError,\n placeOrder,\n placeOrderAsync: placeOrderCore,\n clearPlaceOrderError: () => setPlaceOrderError(null),\n paymentTimedOut,\n paymentDecline: derived.checkout?.paymentDecline ?? null,\n placedOrder,\n awaitingPlacedOrder: !!orderBiId && !placedOrder && !placedOrderError && (\n placedOrderQuery.isLoading\n || placedOrderQuery.isFetching\n || placedOrderQuery.data?.error === 'NOT_PLACED'\n || placedOrderQuery.data?.error === 'ORDER_READ_FAILED'\n || (placedOrderQuery.data?.ok === true && !placedOrderQuery.data.order)\n || !placedOrderQuery.data\n ),\n placedOrderError,\n placedOrderPlacedAt,\n };\n}\n\nexport default useAlacarteCheckout;\n"]}
|
|
1
|
+
{"version":3,"file":"useAlacarteCheckout.js","sourceRoot":"","sources":["../../../src/data/hooks/useAlacarteCheckout.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * data/hooks/useAlacarteCheckout.ts — page-level orchestration for the à la\n * carte checkout flow:\n *\n * 1. Read a product selector (course or program) from the URL via nuqs.\n * 2. If a product is selected: check ownership (tOwnedProducts). Already\n * owned -> 'alreadyOwned'. Otherwise select it into the checkout\n * (tCheckoutSelect) and fetch its display details in parallel.\n * 3. If no product is selected: look for an existing open checkout\n * (tCheckout). Found -> 'ready'. Not found -> 'noActiveCheckout'.\n * 4. Coupon apply/remove and payment prep via tCheckoutUpdate.\n *\n * See /alacarte-checkout-api.md and /alacarte-owned-products-api.md at the\n * repo root for the underlying API contract.\n */\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useQueryClient } from '@tanstack/react-query';\nimport { useQueryStates, useQueryState, parseAsString } from 'nuqs';\nimport { createAlacarteCheckoutClient } from '../../utils/alacarteCheckout';\nimport { getCouponErrorMessage, getCouponRecheckMessage } from '../../utils/couponErrors';\nimport {\n placeAlacarteOrder,\n toAlacarteBillingAddress,\n} from '../../utils/alacartePayment';\nimport { AlacartePlaceOrderError } from '../../utils/placeOrderErrors';\nimport { CardValidationError } from '../../utils/stripeCard';\nimport { isRequestTimeoutError, RequestTimeoutError } from '../../utils/timeoutErrors';\nimport { useSubscriptionCourseKeyQuery, useViaxSessionQuery } from './useCheckoutQueries';\nimport {\n courseKeyFromCourseRunKey, lmsOriginFromSessionUrl, isCourseNotFoundError,\n} from '../../utils/provision';\nimport { getCourseRunKeyFromUrl } from '../../utils/url';\nimport {\n useOwnedProductQuery, useAlacarteCheckoutQuery, useSelectProductMutation, useProductDetailQuery,\n useUpdateCheckoutMutation, usePlacedOrderQuery, useOrderDatesQuery,\n} from './useAlacarteQueries';\nimport { alacarteKeys } from './keys';\nimport { derivePlacedOrderView } from './placedOrderPoll';\nimport type {\n AlacarteCheckout, AlacarteCheckoutResult, AlacarteLine, AlacarteMoney, AlacartePaymentDecline,\n AlacartePriceChanged, OwnedProduct, OwnedProductResult, PlacedOrder, ProductDetail, ProductSelector,\n} from '../../types/alacarte';\nimport type { BillingInfo } from '../../types';\n\nexport type AlacartePhase = 'loading' | 'alreadyOwned' | 'noActiveCheckout' | 'notPurchasable' | 'ready' | 'error';\n\nexport type AlacarteNoticeMessageId = 'alreadyOwnedCourse' | 'alreadyOwnedProgram' | 'noActiveCheckout';\n\n/** Pure phase decision output — no action callbacks. */\nexport interface AlacartePhaseState {\n phase: AlacartePhase,\n noticeMessageId?: AlacarteNoticeMessageId,\n selector: ProductSelector,\n checkout: AlacarteCheckout | null,\n line: AlacarteLine | null,\n productDetail: ProductDetail | null,\n error: string | null,\n priceChanged: AlacartePriceChanged | null,\n priceAccepted: boolean,\n /** True when the learner already owns this product. The checkout data may\n * still be populated so the UI can render the full page under an overlay. */\n isAlreadyOwned: boolean,\n /** Product name from the owned-products query — used as a fallback when the\n * checkout line (which normally carries the name) is not available. */\n ownedProductName: string | null,\n}\n\nexport interface UseAlacarteCheckoutResult extends AlacartePhaseState {\n couponError: string | null,\n couponApplying: boolean,\n applyCoupon: (code: string) => void,\n removeCoupon: () => void,\n clearCouponError: () => void,\n /** Code of the coupon just removed, set only once the removal round-trip\n * completes — kept in sync with when the discount actually leaves the line. */\n removedCouponCode: string | null,\n clearRemovedCouponCode: () => void,\n placingOrder: boolean,\n placeOrderError: string | null,\n /**\n * Accepts price (if needed) + terms, calls tCheckoutPlaceOrder with billing,\n * confirms the card via Stripe.js, then sets ?order_bi_id= to poll tPlacedOrder.\n * Fire-and-forget — errors go to placeOrderError state.\n */\n placeOrder: (opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => void,\n /**\n * Same as placeOrder but returns a Promise — used by ExpressCheckout\n * so the wallet sheet can await the result and call ev.complete().\n */\n placeOrderAsync: (opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => Promise<void>,\n clearPlaceOrderError: () => void,\n /** True once the payment provider timed out during place order (HTTP 408 /\n * Stripe network error). The order may still go through, so further\n * attempts are blocked and the UI shows the timeout warning instead. */\n paymentTimedOut: boolean,\n /** Decline details from the last card attempt on this checkout, if any. */\n paymentDecline: AlacartePaymentDecline | null,\n /** When ?order_bi_id= is present, the placed order (or null while polling). */\n placedOrder: PlacedOrder | null,\n /**\n * True while tPlacedOrder is still PAYMENT_PENDING (payment confirming,\n * including 3DS) or while we reload tCheckout after NOT_PLACED.\n */\n awaitingPlacedOrder: boolean,\n placedOrderError: string | null,\n /** ISO timestamp the order was placed (tPlacedAt, falling back to\n * biCreatedAt); null while the order-dates query hasn't resolved yet. */\n placedOrderPlacedAt: string | null,\n}\n\n/**\n * Read the product selector from the URL: course_uid+course_run_key, or\n * program_uid, or neither. Both course params must be present together; if a\n * course param and a program param are both present, that's\n * CONFLICTING_SELECTION.\n *\n * NOTE: course_run_key values look like \"course-v1:HarvardX+CS50+X\" — nuqs\n * decodes query values the standards-compliant way (literal `+` -> space),\n * same as native URLSearchParams. runKey is therefore read from the raw query\n * string via getCourseRunKeyFromUrl(), which preserves literal `+`.\n */\nexport function useProductSelector(): ProductSelector {\n const [{ courseUid, runKey: nuqsRunKey, programUid }] = useQueryStates({\n courseUid: parseAsString,\n runKey: parseAsString,\n programUid: parseAsString,\n }, {\n urlKeys: {\n courseUid: 'course_uid',\n runKey: 'course_run_key',\n programUid: 'program_uid',\n },\n });\n const runKey = nuqsRunKey ? (getCourseRunKeyFromUrl() ?? nuqsRunKey) : null;\n\n return useMemo<ProductSelector>(() => {\n const hasCourse = !!courseUid || !!runKey;\n const hasProgram = !!programUid;\n if (hasCourse && hasProgram) return { kind: 'invalid' };\n if (courseUid && runKey) return { kind: 'course', courseUid, runKey };\n if (programUid) return { kind: 'program', programUid };\n return { kind: 'none' };\n }, [courseUid, runKey, programUid]);\n}\n\n/** Reads ?discount_code= — applied to the checkout right after it's created. */\nexport function useDiscountCode(): string | null {\n const [discountCode] = useQueryState('discount_code', parseAsString);\n return discountCode;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : 'Unknown error';\n}\n\n/**\n * Build a synthetic AlacarteLine from the owned-product pricing data so the\n * checkout overlay can show subtotal / deduction / total even when the backend\n * blocks tCheckoutSelect and returns no checkout object.\n */\nfunction buildLineFromOwnedProduct(product: OwnedProduct): AlacarteLine | null {\n if (!product.price) return null;\n const { full, deduction, amountDue, currency } = product.price;\n const toMoney = (amount: number): AlacarteMoney => ({ amount, currency });\n\n if (product.kind === 'program') {\n return {\n biiId: '',\n kind: 'program',\n programId: product.productId,\n programName: product.name,\n price: toMoney(full),\n deduction: deduction ? toMoney(deduction) : null,\n ownedCourses: product.ownedCourses.map((c) => ({\n courseId: c.courseId, name: c.name, via: c.via,\n })),\n discount: null,\n amountDue: toMoney(amountDue),\n billingAddressSet: false,\n };\n }\n\n return {\n biiId: '',\n kind: 'course',\n courseName: product.name,\n courseUid: product.productId,\n courseRunKey: '',\n seatType: 'verified',\n price: toMoney(full),\n discount: null,\n amountDue: toMoney(amountDue),\n billingAddressSet: false,\n };\n}\n\ninterface QueryLikeState<T> {\n isLoading: boolean,\n isError: boolean,\n error?: unknown,\n data?: T,\n}\n\ninterface MutationLikeState<T> {\n isIdle: boolean,\n isPending: boolean,\n isError: boolean,\n error?: unknown,\n data?: T,\n}\n\nexport interface DeriveAlacartePhaseInput {\n selector: ProductSelector,\n sessionReady: boolean,\n sessionIsError: boolean,\n sessionErrorMessage?: string,\n ownedQuery: QueryLikeState<OwnedProductResult>,\n selectMutation: MutationLikeState<AlacarteCheckoutResult>,\n checkoutQuery: QueryLikeState<AlacarteCheckoutResult>,\n productDetail: ProductDetail | null,\n /** Coupon (?discount_code=) applied right after the checkout is created —\n * when it succeeds, its checkout (with the coupon's discount reflected)\n * is shown in place of the pre-coupon one from selectMutation. */\n couponMutation?: MutationLikeState<AlacarteCheckoutResult>,\n /** True when a URL discount code still needs to be applied — the phase\n * stays 'loading' until the coupon mutation resolves so the user never sees\n * the pre-discount price flash. */\n urlCouponPending?: boolean,\n /** Latest successful manual update (coupon / acceptPrice / terms). Wins over\n * select + URL coupon when present. */\n checkoutOverride?: AlacarteCheckout | null,\n /** Course-runs lookup for a course selector. Empty `elements` is notFound. */\n courseRunLookup?: { pending: boolean, notFound: boolean },\n}\n\n/**\n * Pure decision function — no hooks, no React Query/nuqs types beyond plain\n * data — so every branch combination can be unit-tested directly.\n */\nexport function deriveAlacartePhase(input: DeriveAlacartePhaseInput): AlacartePhaseState {\n const {\n selector, sessionReady, sessionIsError, sessionErrorMessage,\n ownedQuery, selectMutation, checkoutQuery, productDetail, couponMutation,\n checkoutOverride = null, urlCouponPending: urlCouponPendingProp = false,\n courseRunLookup = { pending: false, notFound: false },\n } = input;\n\n const ownedProductName = ownedQuery.data?.product?.name ?? null;\n\n const empty: Omit<AlacartePhaseState, 'phase'> = {\n selector,\n checkout: null,\n line: null,\n productDetail: null,\n error: null,\n priceChanged: null,\n priceAccepted: false,\n isAlreadyOwned: false,\n ownedProductName: null,\n };\n\n const withCheckout = (\n checkout: AlacarteCheckout | null,\n priceChanged: AlacartePriceChanged | null = null,\n isAlreadyOwned = false,\n ): AlacartePhaseState => ({\n phase: isAlreadyOwned ? 'alreadyOwned' : 'ready',\n selector,\n checkout,\n line: checkout?.line ?? null,\n productDetail,\n error: null,\n priceChanged: priceChanged ?? null,\n priceAccepted: checkout?.priceAccepted === true,\n isAlreadyOwned,\n ownedProductName: isAlreadyOwned ? ownedProductName : null,\n });\n\n if (selector.kind === 'invalid') {\n return { phase: 'error', ...empty, error: 'CONFLICTING_SELECTION' };\n }\n if (sessionIsError) {\n return { phase: 'error', ...empty, error: sessionErrorMessage ?? 'Session error' };\n }\n\n const hasSelector = selector.kind === 'course' || selector.kind === 'program';\n\n if (hasSelector) {\n if (selector.kind === 'course' && courseRunLookup.notFound) {\n return {\n phase: 'error',\n ...empty,\n error: 'This course could not be found.',\n };\n }\n if (!sessionReady || ownedQuery.isLoading || courseRunLookup.pending) {\n return { phase: 'loading', ...empty };\n }\n if (ownedQuery.isError) {\n return { phase: 'error', ...empty, error: errorMessage(ownedQuery.error) };\n }\n\n const isOwned = ownedQuery.data?.ok === true && ownedQuery.data.product?.status === 'full';\n\n if (selectMutation.isIdle || selectMutation.isPending) {\n return { phase: 'loading', ...empty };\n }\n if (selectMutation.isError) {\n return { phase: 'error', ...empty, error: errorMessage(selectMutation.error) };\n }\n if (selectMutation.data && !selectMutation.data.ok) {\n // The backend may block ALREADY_OWNED at select time. When the\n // blocked response still includes checkout data (line, pricing), use\n // it so the overlay page shows the real totals. Fall back to the\n // standalone tCheckout query, then to an empty state.\n if (selectMutation.data.blocked?.reason === 'ALREADY_OWNED') {\n const blockedCheckout = selectMutation.data.checkout\n ?? (checkoutQuery.data?.ok ? checkoutQuery.data.checkout : null);\n if (blockedCheckout) {\n return withCheckout(blockedCheckout, selectMutation.data.priceChanged ?? null, true);\n }\n // Build a synthetic line from the owned-product pricing so the\n // overlay can show subtotal / deduction / total.\n const ownedProduct = ownedQuery.data?.product;\n const syntheticLine = ownedProduct ? buildLineFromOwnedProduct(ownedProduct) : null;\n return {\n phase: 'alreadyOwned',\n noticeMessageId: selectMutation.data.blocked.kind === 'course' ? 'alreadyOwnedCourse' : 'alreadyOwnedProgram',\n ...empty,\n line: syntheticLine,\n productDetail,\n isAlreadyOwned: true,\n ownedProductName,\n };\n }\n if (selectMutation.data.blocked?.reason === 'NOT_PURCHASABLE') {\n return {\n phase: 'notPurchasable',\n ...empty,\n productDetail,\n error: selectMutation.data.blocked.kind === 'program'\n ? 'This program is not eligible for checkout.'\n : 'This course is not eligible for checkout.',\n };\n }\n const selectError = selectMutation.data.error;\n if (selectError === 'COURSE_NOT_FOUND' || selectError === 'PRODUCT_NOT_FOUND') {\n return {\n phase: 'error',\n ...empty,\n error: 'This course could not be found.',\n };\n }\n if (selectError === 'PROGRAM_NOT_FOUND') {\n return {\n phase: 'error',\n ...empty,\n error: 'This program could not be found.',\n };\n }\n const blockedDetail = selectMutation.data.blocked?.detail;\n return {\n phase: 'error',\n ...empty,\n error: blockedDetail ?? selectError ?? 'unknown',\n };\n }\n\n // When a URL discount code still needs to be applied, stay in 'loading'\n // so the user never sees the pre-discount price flash.\n if (urlCouponPendingProp && !checkoutOverride) {\n return { phase: 'loading', ...empty };\n }\n\n // Once the coupon-apply (?discount_code=) mutation succeeds, its checkout\n // (with the discount reflected) supersedes the pre-coupon one. Manual\n // updates (override) win over both.\n const finalCheckout = checkoutOverride\n ?? (couponMutation?.data?.ok\n ? couponMutation.data.checkout\n : selectMutation.data?.checkout ?? null);\n\n // When select succeeds but the owned-products query says the learner\n // already has this product, show the full checkout under an overlay.\n return withCheckout(\n finalCheckout,\n selectMutation.data?.priceChanged\n ?? (couponMutation?.data?.ok ? couponMutation.data.priceChanged : undefined)\n ?? null,\n isOwned,\n );\n }\n\n // No selector: fall back to the learner's existing open checkout, if any.\n if (!sessionReady || checkoutQuery.isLoading) {\n return { phase: 'loading', ...empty };\n }\n if (checkoutQuery.isError) {\n return { phase: 'error', ...empty, error: errorMessage(checkoutQuery.error) };\n }\n if (checkoutOverride || checkoutQuery.data?.checkout) {\n const checkout = checkoutOverride ?? checkoutQuery.data!.checkout;\n return withCheckout(checkout, checkoutQuery.data?.priceChanged ?? null);\n }\n\n return { phase: 'noActiveCheckout', noticeMessageId: 'noActiveCheckout', ...empty, isAlreadyOwned: false, ownedProductName: null };\n}\n\nexport function useAlacarteCheckout(\n options: {\n apiUrl?: string,\n edxSessionUrl?: string,\n /**\n * When true, skip GET /api/subscription/course_runs/ and call Viax with a\n * course key derived from ?course_run_key= instead.\n */\n skipSubscriptionCourseRuns?: boolean,\n },\n): UseAlacarteCheckoutResult {\n const { apiUrl, edxSessionUrl, skipSubscriptionCourseRuns = false } = options;\n const queryClient = useQueryClient();\n const selector = useProductSelector();\n const discountCode = useDiscountCode();\n const [orderBiId, setOrderBiId] = useQueryState('order_bi_id', parseAsString);\n\n const client = useMemo(\n () => (apiUrl ? createAlacarteCheckoutClient({ apiUrl }) : null),\n [apiUrl],\n );\n\n // Reuses the same query key as useCheckout's session exchange — React\n // Query dedupes the network call regardless of how many hooks read it.\n const sessionQuery = useViaxSessionQuery(edxSessionUrl ?? null);\n const sessionReady = !!sessionQuery.data;\n\n useEffect(() => {\n if (sessionQuery.data && client) {\n client.setToken(sessionQuery.data.viaxToken);\n }\n }, [sessionQuery.data, client]);\n\n const hasSelector = selector.kind === 'course' || selector.kind === 'program';\n const productId = selector.kind === 'course'\n ? selector.courseUid\n : selector.kind === 'program' ? selector.programUid : null;\n\n const ownedQuery = useOwnedProductQuery(client, sessionReady && hasSelector, productId);\n const isOwned = ownedQuery.data?.ok === true && ownedQuery.data.product?.status === 'full';\n\n const selectMutation = useSelectProductMutation(client);\n const {\n isIdle: selectIsIdle, mutate: selectMutate, reset: selectReset,\n } = selectMutation;\n\n const courseRunKey = selector.kind === 'course' ? selector.runKey : null;\n const lmsBaseUrl = useMemo(\n () => lmsOriginFromSessionUrl(edxSessionUrl),\n [edxSessionUrl],\n );\n const courseLookupEnabled = !skipSubscriptionCourseRuns\n && selector.kind === 'course'\n && !!lmsBaseUrl\n && !!courseRunKey;\n const courseKeyQuery = useSubscriptionCourseKeyQuery(\n skipSubscriptionCourseRuns ? null : lmsBaseUrl,\n skipSubscriptionCourseRuns ? null : courseRunKey,\n );\n const courseKey = skipSubscriptionCourseRuns\n ? (courseRunKey ? courseKeyFromCourseRunKey(courseRunKey) : null)\n : (courseKeyQuery.data ?? null);\n const courseNotFound = courseLookupEnabled\n && courseKeyQuery.isError\n && isCourseNotFoundError(courseKeyQuery.error);\n\n // Mutations aren't called during render — fire once the owned-products\n // query resolves (regardless of result) so we always get checkout/line data.\n // A course is not selected until the course-runs lookup returns a course.\n // Pass viewedPrice from tOwnedProducts.amountDue (ALC-159) when available.\n useEffect(() => {\n if (!hasSelector || ownedQuery.isLoading || ownedQuery.isError) return;\n if (selector.kind === 'course' && !courseKey) return;\n if (!selectIsIdle) return;\n const amountDue = ownedQuery.data?.product?.price?.amountDue;\n selectMutate({\n selector,\n viewedPrice: typeof amountDue === 'number' ? amountDue : undefined,\n });\n }, [\n hasSelector,\n ownedQuery.isLoading,\n ownedQuery.isError,\n ownedQuery.data?.product?.price?.amountDue,\n selector,\n courseKey,\n selectIsIdle,\n selectMutate,\n ]);\n\n const productDetailEnabled = hasSelector && sessionReady && !ownedQuery.isLoading\n && (selector.kind !== 'course' || !!courseKey);\n const productDetailQuery = useProductDetailQuery(client, productDetailEnabled, selector, courseKey);\n\n // Run the checkout query when there's no selector (standard path) OR when\n // the product is already owned — after the select mutation is blocked with\n // ALREADY_OWNED the server may still have an open checkout with pricing data.\n const selectBlocked = !selectMutation.isIdle && !selectMutation.isPending\n && selectMutation.data && !selectMutation.data.ok;\n const checkoutQuery = useAlacarteCheckoutQuery(\n client,\n sessionReady && (!hasSelector || (isOwned && !!selectBlocked)),\n );\n\n // Apply ?discount_code= to the freshly-created checkout — fired once the\n // select mutation succeeds with a checkout that doesn't already carry it.\n const urlCouponMutation = useUpdateCheckoutMutation(client);\n const {\n isIdle: couponIsIdle, mutate: couponMutate, reset: urlCouponReset,\n } = urlCouponMutation;\n const createdCheckout = selectMutation.data?.ok ? selectMutation.data.checkout : null;\n\n useEffect(() => {\n if (!createdCheckout || !discountCode) return;\n if (createdCheckout.couponCode === discountCode) return;\n if (couponIsIdle) {\n couponMutate({ biId: createdCheckout.biId, couponCode: discountCode });\n }\n }, [createdCheckout, discountCode, couponIsIdle, couponMutate]);\n\n const [checkoutOverride, setCheckoutOverride] = useState<AlacarteCheckout | null>(null);\n const [couponError, setCouponError] = useState<string | null>(null);\n const [removedCouponCode, setRemovedCouponCode] = useState<string | null>(null);\n const [placeOrderError, setPlaceOrderError] = useState<string | null>(null);\n const [placingOrder, setPlacingOrder] = useState(false);\n const [paymentTimedOut, setPaymentTimedOut] = useState(false);\n // biId of a checkout that's locked (CART_PAYMENT_PENDING) from a prior\n // tCheckoutPlaceOrder call whose Stripe confirmation was rejected\n // client-side. The next attempt must skip tCheckoutUpdate (it would fail\n // with CHECKOUT_NOT_OPEN) and go straight to tCheckoutPlaceOrder again.\n const [cardRetryBiId, setCardRetryBiId] = useState<string | null>(null);\n\n // Reset local overrides and in-flight select/coupon when the product changes\n // so viewedPrice / couponRecheck run again for the new selection.\n useEffect(() => {\n setCheckoutOverride(null);\n setCouponError(null);\n setRemovedCouponCode(null);\n setPlaceOrderError(null);\n setPaymentTimedOut(false);\n setCardRetryBiId(null);\n selectReset();\n urlCouponReset();\n }, [selector, selectReset, urlCouponReset]);\n\n // Surface couponRecheck from product switch (section 2 of the API doc).\n useEffect(() => {\n const result = selectMutation.data;\n if (!result?.ok) return;\n const message = getCouponRecheckMessage(\n result.couponRecheck,\n result.checkout?.line?.discountSource,\n );\n if (message) setCouponError(message);\n }, [selectMutation.data]);\n\n // Surface ?discount_code= apply failures (domain ok:false or transport error).\n useEffect(() => {\n if (!discountCode) return;\n if (urlCouponMutation.isIdle || urlCouponMutation.isPending) return;\n if (urlCouponMutation.isError) {\n setCouponError(errorMessage(urlCouponMutation.error));\n return;\n }\n const result = urlCouponMutation.data;\n if (result && !result.ok) {\n setCouponError(getCouponErrorMessage(result.error, discountCode));\n }\n }, [\n discountCode,\n urlCouponMutation.isIdle,\n urlCouponMutation.isPending,\n urlCouponMutation.isError,\n urlCouponMutation.error,\n urlCouponMutation.data,\n ]);\n\n const manualUpdateMutation = useUpdateCheckoutMutation(client);\n\n const placedOrderQuery = usePlacedOrderQuery(\n client,\n orderBiId,\n sessionReady && !!orderBiId,\n );\n\n const orderDatesQuery = useOrderDatesQuery(\n client,\n orderBiId,\n sessionReady && !!orderBiId,\n );\n\n const derived = deriveAlacartePhase({\n selector,\n sessionReady,\n sessionIsError: sessionQuery.isError,\n sessionErrorMessage: sessionQuery.error?.message,\n ownedQuery: {\n isLoading: ownedQuery.isLoading,\n isError: ownedQuery.isError,\n error: ownedQuery.error,\n data: ownedQuery.data,\n },\n selectMutation: {\n isIdle: selectMutation.isIdle,\n isPending: selectMutation.isPending,\n isError: selectMutation.isError,\n error: selectMutation.error,\n data: selectMutation.data,\n },\n checkoutQuery: {\n isLoading: checkoutQuery.isLoading,\n isError: checkoutQuery.isError,\n error: checkoutQuery.error,\n data: checkoutQuery.data,\n },\n productDetail: productDetailQuery.data ?? null,\n courseRunLookup: {\n pending: courseLookupEnabled && courseKeyQuery.isLoading,\n notFound: courseNotFound,\n },\n couponMutation: {\n isIdle: urlCouponMutation.isIdle,\n isPending: urlCouponMutation.isPending,\n isError: urlCouponMutation.isError,\n error: urlCouponMutation.error,\n data: urlCouponMutation.data,\n },\n checkoutOverride,\n // The URL coupon is pending only when the checkout exists, doesn't already\n // carry the code, and the mutation hasn't completed yet. When the backend\n // returns the coupon pre-applied (couponCode === discountCode) or when\n // there's no discount_code param at all, this is false from the start.\n urlCouponPending: !!discountCode\n && !!createdCheckout\n && createdCheckout.couponCode !== discountCode\n && (urlCouponMutation.isIdle || urlCouponMutation.isPending),\n });\n\n const applyCoupon = useCallback((code: string) => {\n const biId = derived.checkout?.biId;\n if (!biId || !client) return;\n const trimmed = code.trim();\n if (!trimmed) return;\n\n setCouponError(null);\n manualUpdateMutation.mutate(\n { biId, couponCode: trimmed },\n {\n onSuccess: (result) => {\n if (result.ok && result.checkout) {\n setCheckoutOverride(result.checkout);\n setCouponError(null);\n setRemovedCouponCode(null);\n } else {\n setCouponError(getCouponErrorMessage(result.error, trimmed));\n }\n },\n onError: (err) => {\n setCouponError(errorMessage(err));\n },\n },\n );\n }, [derived.checkout?.biId, client, manualUpdateMutation]);\n\n const removeCoupon = useCallback(() => {\n const currentCheckout = derived.checkout;\n if (!currentCheckout?.biId || !client) return;\n const removedCode = currentCheckout.couponCode?.trim() || null;\n\n setCouponError(null);\n manualUpdateMutation.mutate(\n { biId: currentCheckout.biId, couponCode: '' },\n {\n onSuccess: (result) => {\n if (!result.ok) {\n setCouponError(getCouponErrorMessage(result.error));\n return;\n }\n // The backend may return a checkout whose line still carries the\n // old discount/amountDue. Always patch the line to clear the\n // coupon so the cart summary reflects the removal immediately.\n const base = result.checkout ?? currentCheckout;\n const line = base.line;\n const patchedLine = line\n ? {\n ...line,\n discount: null,\n amountDue: line.price && line.kind === 'program' && 'deduction' in line && line.deduction\n ? { amount: line.price.amount - line.deduction.amount, currency: line.price.currency }\n : line.price,\n } as typeof line\n : null;\n setCheckoutOverride({\n ...base,\n couponCode: null,\n line: patchedLine,\n });\n setCouponError(null);\n if (removedCode) setRemovedCouponCode(removedCode);\n },\n onError: (err) => {\n setCouponError(errorMessage(err));\n },\n },\n );\n }, [derived.checkout, client, manualUpdateMutation]);\n\n const placeOrderCore = useCallback(async (opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => {\n const checkout = derived.checkout;\n if (!checkout || !client) throw new Error('Checkout not ready');\n // A timed-out attempt may still complete — never start a second one.\n if (paymentTimedOut) throw new RequestTimeoutError();\n\n setPlacingOrder(true);\n setPlaceOrderError(null);\n\n try {\n const { checkout: updated } = await placeAlacarteOrder({\n updateCheckout: (args) => client.updateCheckout(args),\n placeCheckoutOrder: (biId, billing) => client.placeCheckoutOrder(biId, billing),\n checkout,\n billing: toAlacarteBillingAddress(opts.billing),\n confirmCardPayment: opts.confirmCardPayment,\n skipPrepare: cardRetryBiId === checkout.biId,\n });\n\n setCardRetryBiId(null);\n setCheckoutOverride(updated);\n await setOrderBiId(updated.biId);\n } catch (err: unknown) {\n if (err instanceof CardValidationError) {\n // tCheckoutPlaceOrder already succeeded and locked the checkout\n // (CART_PAYMENT_PENDING); Stripe rejected the card before it ever\n // reached the backend. Next attempt must retry tCheckoutPlaceOrder\n // directly, not tCheckoutUpdate.\n setCardRetryBiId(checkout.biId);\n } else {\n setCardRetryBiId(null);\n }\n if (err instanceof AlacartePlaceOrderError && err.checkout) {\n setCheckoutOverride(err.checkout);\n }\n // The timeout warning replaces the generic error banner.\n if (isRequestTimeoutError(err)) {\n setPaymentTimedOut(true);\n } else {\n setPlaceOrderError(errorMessage(err));\n }\n throw err;\n } finally {\n setPlacingOrder(false);\n }\n }, [derived.checkout, client, setOrderBiId, cardRetryBiId, paymentTimedOut]);\n\n /** Fire-and-forget wrapper for card payments. */\n const placeOrder = useCallback((opts: {\n billing: BillingInfo,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n }) => {\n if (placingOrder || paymentTimedOut) return;\n placeOrderCore(opts).catch(() => { /* error already in state */ });\n }, [placeOrderCore, placingOrder, paymentTimedOut]);\n\n const placedView = derivePlacedOrderView({\n orderBiId,\n result: placedOrderQuery.data,\n pollTimedOut: placedOrderQuery.pollTimedOut,\n isLoading: placedOrderQuery.isLoading,\n isFetching: placedOrderQuery.isFetching,\n });\n const { placedOrder } = placedView;\n\n const dates = orderDatesQuery.data?.ok ? orderDatesQuery.data.dates : null;\n const placedOrderPlacedAt = dates?.tPlacedAt ?? dates?.biCreatedAt ?? null;\n\n // NOT_PLACED: the payment did not go through and the cart is editable again.\n // Reload tCheckout (it carries paymentDecline) and drop ?order_bi_id= so the\n // learner can try another card instead of sitting on the confirmation error.\n // The ref blocks a second reload for the same attempt. It is not cleared in\n // cleanup — a re-run must not cancel the reload that is already in flight.\n const returnedToCheckoutFor = useRef<string | null>(null);\n useEffect(() => {\n if (!orderBiId) {\n returnedToCheckoutFor.current = null;\n return undefined;\n }\n if (!placedView.returnToCheckout) return undefined;\n if (returnedToCheckoutFor.current === orderBiId) return undefined;\n returnedToCheckoutFor.current = orderBiId;\n\n (async () => {\n if (client) {\n try {\n const fresh = await client.getCheckout();\n if (fresh.ok && fresh.checkout) {\n setCheckoutOverride(fresh.checkout);\n queryClient.setQueryData(alacarteKeys.checkout(), fresh);\n }\n } catch {\n // Still leave the poll — the cart is editable even if this read fails.\n }\n }\n await setOrderBiId(null);\n })();\n\n return undefined;\n }, [orderBiId, placedView.returnToCheckout, client, setOrderBiId, queryClient]);\n\n return {\n ...derived,\n couponError,\n couponApplying: manualUpdateMutation.isPending,\n applyCoupon,\n removeCoupon,\n clearCouponError: () => setCouponError(null),\n removedCouponCode,\n clearRemovedCouponCode: () => setRemovedCouponCode(null),\n placingOrder,\n placeOrderError,\n placeOrder,\n placeOrderAsync: placeOrderCore,\n clearPlaceOrderError: () => setPlaceOrderError(null),\n paymentTimedOut,\n paymentDecline: derived.checkout?.paymentDecline ?? null,\n placedOrder,\n awaitingPlacedOrder: placedView.awaitingPlacedOrder,\n placedOrderError: placedView.placedOrderError,\n placedOrderPlacedAt,\n };\n}\n\nexport default useAlacarteCheckout;\n"]}
|
|
@@ -22,9 +22,182 @@ export declare function useUpdateCheckoutMutation(client: AlacarteCheckoutClient
|
|
|
22
22
|
* subscription course-run API (not the course-run key or the URL param).
|
|
23
23
|
* A program uses filterTwouProgram by tProgramUuid. */
|
|
24
24
|
export declare function useProductDetailQuery(client: AlacarteCheckoutClient | null, enabled: boolean, selector: ProductSelector, courseKey?: string | null): import("@tanstack/react-query").UseQueryResult<ProductDetail | null, Error>;
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
27
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Fetches a placed order for the confirmation page.
|
|
27
|
+
* Polls for 30s while the payment is still confirming (PAYMENT_PENDING,
|
|
28
|
+
* including 3DS). Stops on a placed order, or immediately on NOT_PLACED
|
|
29
|
+
* (payment failed — caller returns the learner to checkout).
|
|
30
|
+
*/
|
|
31
|
+
export declare function usePlacedOrderQuery(client: AlacarteCheckoutClient | null, biId: string | null, enabled: boolean): {
|
|
32
|
+
pollTimedOut: boolean;
|
|
33
|
+
data: PlacedOrderResult;
|
|
34
|
+
error: Error;
|
|
35
|
+
isError: true;
|
|
36
|
+
isPending: false;
|
|
37
|
+
isLoading: false;
|
|
38
|
+
isLoadingError: false;
|
|
39
|
+
isRefetchError: true;
|
|
40
|
+
isSuccess: false;
|
|
41
|
+
isPlaceholderData: false;
|
|
42
|
+
status: "error";
|
|
43
|
+
dataUpdatedAt: number;
|
|
44
|
+
errorUpdatedAt: number;
|
|
45
|
+
failureCount: number;
|
|
46
|
+
failureReason: Error | null;
|
|
47
|
+
errorUpdateCount: number;
|
|
48
|
+
isFetched: boolean;
|
|
49
|
+
isFetchedAfterMount: boolean;
|
|
50
|
+
isFetching: boolean;
|
|
51
|
+
isInitialLoading: boolean;
|
|
52
|
+
isPaused: boolean;
|
|
53
|
+
isRefetching: boolean;
|
|
54
|
+
isStale: boolean;
|
|
55
|
+
isEnabled: boolean;
|
|
56
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<PlacedOrderResult, Error>>;
|
|
57
|
+
fetchStatus: import("@tanstack/query-core").FetchStatus;
|
|
58
|
+
promise: Promise<PlacedOrderResult>;
|
|
59
|
+
} | {
|
|
60
|
+
pollTimedOut: boolean;
|
|
61
|
+
data: PlacedOrderResult;
|
|
62
|
+
error: null;
|
|
63
|
+
isError: false;
|
|
64
|
+
isPending: false;
|
|
65
|
+
isLoading: false;
|
|
66
|
+
isLoadingError: false;
|
|
67
|
+
isRefetchError: false;
|
|
68
|
+
isSuccess: true;
|
|
69
|
+
isPlaceholderData: false;
|
|
70
|
+
status: "success";
|
|
71
|
+
dataUpdatedAt: number;
|
|
72
|
+
errorUpdatedAt: number;
|
|
73
|
+
failureCount: number;
|
|
74
|
+
failureReason: Error | null;
|
|
75
|
+
errorUpdateCount: number;
|
|
76
|
+
isFetched: boolean;
|
|
77
|
+
isFetchedAfterMount: boolean;
|
|
78
|
+
isFetching: boolean;
|
|
79
|
+
isInitialLoading: boolean;
|
|
80
|
+
isPaused: boolean;
|
|
81
|
+
isRefetching: boolean;
|
|
82
|
+
isStale: boolean;
|
|
83
|
+
isEnabled: boolean;
|
|
84
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<PlacedOrderResult, Error>>;
|
|
85
|
+
fetchStatus: import("@tanstack/query-core").FetchStatus;
|
|
86
|
+
promise: Promise<PlacedOrderResult>;
|
|
87
|
+
} | {
|
|
88
|
+
pollTimedOut: boolean;
|
|
89
|
+
data: undefined;
|
|
90
|
+
error: Error;
|
|
91
|
+
isError: true;
|
|
92
|
+
isPending: false;
|
|
93
|
+
isLoading: false;
|
|
94
|
+
isLoadingError: true;
|
|
95
|
+
isRefetchError: false;
|
|
96
|
+
isSuccess: false;
|
|
97
|
+
isPlaceholderData: false;
|
|
98
|
+
status: "error";
|
|
99
|
+
dataUpdatedAt: number;
|
|
100
|
+
errorUpdatedAt: number;
|
|
101
|
+
failureCount: number;
|
|
102
|
+
failureReason: Error | null;
|
|
103
|
+
errorUpdateCount: number;
|
|
104
|
+
isFetched: boolean;
|
|
105
|
+
isFetchedAfterMount: boolean;
|
|
106
|
+
isFetching: boolean;
|
|
107
|
+
isInitialLoading: boolean;
|
|
108
|
+
isPaused: boolean;
|
|
109
|
+
isRefetching: boolean;
|
|
110
|
+
isStale: boolean;
|
|
111
|
+
isEnabled: boolean;
|
|
112
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<PlacedOrderResult, Error>>;
|
|
113
|
+
fetchStatus: import("@tanstack/query-core").FetchStatus;
|
|
114
|
+
promise: Promise<PlacedOrderResult>;
|
|
115
|
+
} | {
|
|
116
|
+
pollTimedOut: boolean;
|
|
117
|
+
data: undefined;
|
|
118
|
+
error: null;
|
|
119
|
+
isError: false;
|
|
120
|
+
isPending: true;
|
|
121
|
+
isLoading: true;
|
|
122
|
+
isLoadingError: false;
|
|
123
|
+
isRefetchError: false;
|
|
124
|
+
isSuccess: false;
|
|
125
|
+
isPlaceholderData: false;
|
|
126
|
+
status: "pending";
|
|
127
|
+
dataUpdatedAt: number;
|
|
128
|
+
errorUpdatedAt: number;
|
|
129
|
+
failureCount: number;
|
|
130
|
+
failureReason: Error | null;
|
|
131
|
+
errorUpdateCount: number;
|
|
132
|
+
isFetched: boolean;
|
|
133
|
+
isFetchedAfterMount: boolean;
|
|
134
|
+
isFetching: boolean;
|
|
135
|
+
isInitialLoading: boolean;
|
|
136
|
+
isPaused: boolean;
|
|
137
|
+
isRefetching: boolean;
|
|
138
|
+
isStale: boolean;
|
|
139
|
+
isEnabled: boolean;
|
|
140
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<PlacedOrderResult, Error>>;
|
|
141
|
+
fetchStatus: import("@tanstack/query-core").FetchStatus;
|
|
142
|
+
promise: Promise<PlacedOrderResult>;
|
|
143
|
+
} | {
|
|
144
|
+
pollTimedOut: boolean;
|
|
145
|
+
data: undefined;
|
|
146
|
+
error: null;
|
|
147
|
+
isError: false;
|
|
148
|
+
isPending: true;
|
|
149
|
+
isLoadingError: false;
|
|
150
|
+
isRefetchError: false;
|
|
151
|
+
isSuccess: false;
|
|
152
|
+
isPlaceholderData: false;
|
|
153
|
+
status: "pending";
|
|
154
|
+
dataUpdatedAt: number;
|
|
155
|
+
errorUpdatedAt: number;
|
|
156
|
+
failureCount: number;
|
|
157
|
+
failureReason: Error | null;
|
|
158
|
+
errorUpdateCount: number;
|
|
159
|
+
isFetched: boolean;
|
|
160
|
+
isFetchedAfterMount: boolean;
|
|
161
|
+
isFetching: boolean;
|
|
162
|
+
isLoading: boolean;
|
|
163
|
+
isInitialLoading: boolean;
|
|
164
|
+
isPaused: boolean;
|
|
165
|
+
isRefetching: boolean;
|
|
166
|
+
isStale: boolean;
|
|
167
|
+
isEnabled: boolean;
|
|
168
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<PlacedOrderResult, Error>>;
|
|
169
|
+
fetchStatus: import("@tanstack/query-core").FetchStatus;
|
|
170
|
+
promise: Promise<PlacedOrderResult>;
|
|
171
|
+
} | {
|
|
172
|
+
pollTimedOut: boolean;
|
|
173
|
+
data: PlacedOrderResult;
|
|
174
|
+
isError: false;
|
|
175
|
+
error: null;
|
|
176
|
+
isPending: false;
|
|
177
|
+
isLoading: false;
|
|
178
|
+
isLoadingError: false;
|
|
179
|
+
isRefetchError: false;
|
|
180
|
+
isSuccess: true;
|
|
181
|
+
isPlaceholderData: true;
|
|
182
|
+
status: "success";
|
|
183
|
+
dataUpdatedAt: number;
|
|
184
|
+
errorUpdatedAt: number;
|
|
185
|
+
failureCount: number;
|
|
186
|
+
failureReason: Error | null;
|
|
187
|
+
errorUpdateCount: number;
|
|
188
|
+
isFetched: boolean;
|
|
189
|
+
isFetchedAfterMount: boolean;
|
|
190
|
+
isFetching: boolean;
|
|
191
|
+
isInitialLoading: boolean;
|
|
192
|
+
isPaused: boolean;
|
|
193
|
+
isRefetching: boolean;
|
|
194
|
+
isStale: boolean;
|
|
195
|
+
isEnabled: boolean;
|
|
196
|
+
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<PlacedOrderResult, Error>>;
|
|
197
|
+
fetchStatus: import("@tanstack/query-core").FetchStatus;
|
|
198
|
+
promise: Promise<PlacedOrderResult>;
|
|
199
|
+
};
|
|
28
200
|
/** Fetches the order's placement/creation timestamps — used for the
|
|
29
|
-
* confirmation page's purchase date and refund-window deadline.
|
|
201
|
+
* confirmation page's purchase date and refund-window deadline.
|
|
202
|
+
* Polls until tPlacedAt is set (it stays null while payment is pending). */
|
|
30
203
|
export declare function useOrderDatesQuery(client: AlacarteCheckoutClient | null, biId: string | null, enabled: boolean): import("@tanstack/react-query").UseQueryResult<OrderDatesResult, Error>;
|
|
@@ -2,8 +2,28 @@
|
|
|
2
2
|
* data/hooks/useAlacarteQueries.ts — read/write-side TanStack Query hooks for
|
|
3
3
|
* the à la carte checkout flow. Mirrors useCheckoutQueries.ts's conventions.
|
|
4
4
|
*/
|
|
5
|
+
import { useEffect, useRef, useState } from 'react';
|
|
5
6
|
import { useQuery, useMutation } from '@tanstack/react-query';
|
|
6
7
|
import { alacarteKeys } from './keys';
|
|
8
|
+
import { PLACED_ORDER_POLL_TIMEOUT_MS, orderDatesRefetchInterval, placedOrderRefetchInterval, } from './placedOrderPoll';
|
|
9
|
+
/** 30s window starting when a post-payment poll becomes active. */
|
|
10
|
+
function usePollWindow(active) {
|
|
11
|
+
const startedAtRef = useRef(null);
|
|
12
|
+
const [timedOut, setTimedOut] = useState(false);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!active) {
|
|
15
|
+
startedAtRef.current = null;
|
|
16
|
+
setTimedOut(false);
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
startedAtRef.current = Date.now();
|
|
20
|
+
setTimedOut(false);
|
|
21
|
+
const timer = window.setTimeout(() => setTimedOut(true), PLACED_ORDER_POLL_TIMEOUT_MS);
|
|
22
|
+
return () => window.clearTimeout(timer);
|
|
23
|
+
}, [active]);
|
|
24
|
+
const elapsedMs = () => (startedAtRef.current == null ? 0 : Date.now() - startedAtRef.current);
|
|
25
|
+
return { elapsedMs, timedOut };
|
|
26
|
+
}
|
|
7
27
|
/** Checks ownership of the product identified by the query params. */
|
|
8
28
|
export function useOwnedProductQuery(client, enabled, productId) {
|
|
9
29
|
return useQuery({
|
|
@@ -80,39 +100,42 @@ export function useProductDetailQuery(client, enabled, selector, courseKey = nul
|
|
|
80
100
|
retry: 1,
|
|
81
101
|
});
|
|
82
102
|
}
|
|
83
|
-
/**
|
|
84
|
-
*
|
|
103
|
+
/**
|
|
104
|
+
* Fetches a placed order for the confirmation page.
|
|
105
|
+
* Polls for 30s while the payment is still confirming (PAYMENT_PENDING,
|
|
106
|
+
* including 3DS). Stops on a placed order, or immediately on NOT_PLACED
|
|
107
|
+
* (payment failed — caller returns the learner to checkout).
|
|
108
|
+
*/
|
|
85
109
|
export function usePlacedOrderQuery(client, biId, enabled) {
|
|
86
|
-
|
|
110
|
+
const active = enabled && !!client && !!biId;
|
|
111
|
+
const poll = usePollWindow(active);
|
|
112
|
+
const query = useQuery({
|
|
87
113
|
queryKey: alacarteKeys.placedOrder(biId !== null && biId !== void 0 ? biId : 'none'),
|
|
88
|
-
enabled:
|
|
114
|
+
enabled: active,
|
|
89
115
|
queryFn: () => client.getPlacedOrder(biId),
|
|
90
116
|
staleTime: 0,
|
|
91
117
|
refetchOnWindowFocus: false,
|
|
118
|
+
refetchIntervalInBackground: true,
|
|
92
119
|
retry: 1,
|
|
93
|
-
refetchInterval: (
|
|
94
|
-
const result = query.state.data;
|
|
95
|
-
// Keep polling while payment is still settling on the backend.
|
|
96
|
-
if (!result)
|
|
97
|
-
return 2000;
|
|
98
|
-
if (result.ok && result.order)
|
|
99
|
-
return false;
|
|
100
|
-
if (result.error === 'NOT_PLACED' || result.error === 'ORDER_READ_FAILED')
|
|
101
|
-
return 2000;
|
|
102
|
-
return false;
|
|
103
|
-
},
|
|
120
|
+
refetchInterval: (q) => placedOrderRefetchInterval(q.state.data, poll.elapsedMs()),
|
|
104
121
|
});
|
|
122
|
+
return Object.assign(Object.assign({}, query), { pollTimedOut: poll.timedOut });
|
|
105
123
|
}
|
|
106
124
|
/** Fetches the order's placement/creation timestamps — used for the
|
|
107
|
-
* confirmation page's purchase date and refund-window deadline.
|
|
125
|
+
* confirmation page's purchase date and refund-window deadline.
|
|
126
|
+
* Polls until tPlacedAt is set (it stays null while payment is pending). */
|
|
108
127
|
export function useOrderDatesQuery(client, biId, enabled) {
|
|
128
|
+
const active = enabled && !!client && !!biId;
|
|
129
|
+
const poll = usePollWindow(active);
|
|
109
130
|
return useQuery({
|
|
110
131
|
queryKey: alacarteKeys.orderDates(biId !== null && biId !== void 0 ? biId : 'none'),
|
|
111
|
-
enabled:
|
|
132
|
+
enabled: active,
|
|
112
133
|
queryFn: () => client.getOrderDates(biId),
|
|
113
|
-
staleTime:
|
|
134
|
+
staleTime: 0,
|
|
114
135
|
refetchOnWindowFocus: false,
|
|
136
|
+
refetchIntervalInBackground: true,
|
|
115
137
|
retry: 1,
|
|
138
|
+
refetchInterval: (q) => orderDatesRefetchInterval(q.state.data, poll.elapsedMs()),
|
|
116
139
|
});
|
|
117
140
|
}
|
|
118
141
|
//# sourceMappingURL=useAlacarteQueries.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAlacarteQueries.js","sourceRoot":"","sources":["../../../src/data/hooks/useAlacarteQueries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * data/hooks/useAlacarteQueries.ts — read/write-side TanStack Query hooks for\n * the à la carte checkout flow. Mirrors useCheckoutQueries.ts's conventions.\n */\nimport { useQuery, useMutation } from '@tanstack/react-query';\nimport { alacarteKeys } from './keys';\nimport type {\n AlacarteCheckoutClient, AlacarteCheckoutResult, OwnedProductResult, ProductSelector, ProductDetail,\n PlacedOrderResult, OrderDatesResult,\n} from '../../types/alacarte';\n\n/** Checks ownership of the product identified by the query params. */\nexport function useOwnedProductQuery(\n client: AlacarteCheckoutClient | null,\n enabled: boolean,\n productId: string | null,\n) {\n return useQuery({\n queryKey: alacarteKeys.ownedProduct(productId ?? 'none'),\n enabled: enabled && !!client && !!productId,\n queryFn: (): Promise<OwnedProductResult> => client!.getOwnedProduct(productId!),\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/** Reads the learner's existing open checkout (used when there's no product selector). */\nexport function useAlacarteCheckoutQuery(client: AlacarteCheckoutClient | null, enabled: boolean) {\n return useQuery({\n queryKey: alacarteKeys.checkout(),\n enabled: enabled && !!client,\n queryFn: (): Promise<AlacarteCheckoutResult> => client!.getCheckout(),\n // Keep the open checkout stable while the tab is backgrounded — a focus\n // refetch would remount payment UI / re-run select for no user action.\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/** Selects a course or program into the learner's checkout — fired once ownership is confirmed not-full.\n * `viewedPrice` is `tOwnedProducts.price.amountDue` (USD, before any coupon) for ALC-159. */\nexport function useSelectProductMutation(client: AlacarteCheckoutClient | null) {\n return useMutation({\n mutationFn: (args: {\n selector: ProductSelector,\n viewedPrice?: number,\n }): Promise<AlacarteCheckoutResult> => {\n if (!client) return Promise.reject(new Error('Alacarte client not ready.'));\n const { selector, viewedPrice } = args;\n if (selector.kind === 'course') {\n return client.selectCourse(selector.courseUid, selector.runKey, viewedPrice);\n }\n if (selector.kind === 'program') {\n return client.selectProgram(selector.programUid, viewedPrice);\n }\n return Promise.reject(new Error('selectMutation called with no product selector.'));\n },\n });\n}\n\n/** Updates the learner's checkout — used here to apply ?discount_code= right\n * after the checkout is created. */\nexport function useUpdateCheckoutMutation(client: AlacarteCheckoutClient | null) {\n return useMutation({\n mutationFn: (args: {\n biId?: string, couponCode?: string, termsAccepted?: boolean, acceptPrice?: boolean,\n }): Promise<AlacarteCheckoutResult> => {\n if (!client) return Promise.reject(new Error('Alacarte client not ready.'));\n return client.updateCheckout(args);\n },\n });\n}\n\n/** Fetches display details for the selected product.\n * A course is looked up in Viax by elements[0].course from the\n * subscription course-run API (not the course-run key or the URL param).\n * A program uses filterTwouProgram by tProgramUuid. */\nexport function useProductDetailQuery(\n client: AlacarteCheckoutClient | null,\n enabled: boolean,\n selector: ProductSelector,\n courseKey: string | null = null,\n) {\n const productKey = selector.kind === 'course'\n ? (courseKey ?? 'none')\n : selector.kind === 'program' ? selector.programUid : 'none';\n\n return useQuery({\n queryKey: alacarteKeys.productDetail(`${selector.kind}:${productKey}`),\n enabled: enabled && !!client && (\n selector.kind === 'program' || (selector.kind === 'course' && !!courseKey)\n ),\n queryFn: (): Promise<ProductDetail | null> => {\n if (selector.kind === 'course') return client!.getCourseDetail(courseKey!);\n if (selector.kind === 'program') return client!.getProgramDetail(selector.programUid);\n return Promise.resolve(null);\n },\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/** Fetches a placed order for the confirmation page. Polls while the order\n * is not yet placed (Stripe confirms to the backend asynchronously). */\nexport function usePlacedOrderQuery(\n client: AlacarteCheckoutClient | null,\n biId: string | null,\n enabled: boolean,\n) {\n return useQuery({\n queryKey: alacarteKeys.placedOrder(biId ?? 'none'),\n enabled: enabled && !!client && !!biId,\n queryFn: (): Promise<PlacedOrderResult> => client!.getPlacedOrder(biId!),\n staleTime: 0,\n refetchOnWindowFocus: false,\n retry: 1,\n refetchInterval: (query) => {\n const result = query.state.data;\n // Keep polling while payment is still settling on the backend.\n if (!result) return 2000;\n if (result.ok && result.order) return false;\n if (result.error === 'NOT_PLACED' || result.error === 'ORDER_READ_FAILED') return 2000;\n return false;\n },\n });\n}\n\n/** Fetches the order's placement/creation timestamps — used for the\n * confirmation page's purchase date and refund-window deadline. */\nexport function useOrderDatesQuery(\n client: AlacarteCheckoutClient | null,\n biId: string | null,\n enabled: boolean,\n) {\n return useQuery({\n queryKey: alacarteKeys.orderDates(biId ?? 'none'),\n enabled: enabled && !!client && !!biId,\n queryFn: (): Promise<OrderDatesResult> => client!.getOrderDates(biId!),\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useAlacarteQueries.js","sourceRoot":"","sources":["../../../src/data/hooks/useAlacarteQueries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * data/hooks/useAlacarteQueries.ts — read/write-side TanStack Query hooks for\n * the à la carte checkout flow. Mirrors useCheckoutQueries.ts's conventions.\n */\nimport { useEffect, useRef, useState } from 'react';\nimport { useQuery, useMutation } from '@tanstack/react-query';\nimport { alacarteKeys } from './keys';\nimport {\n PLACED_ORDER_POLL_TIMEOUT_MS,\n orderDatesRefetchInterval,\n placedOrderRefetchInterval,\n} from './placedOrderPoll';\nimport type {\n AlacarteCheckoutClient, AlacarteCheckoutResult, OwnedProductResult, ProductSelector, ProductDetail,\n PlacedOrderResult, OrderDatesResult,\n} from '../../types/alacarte';\n\n/** 30s window starting when a post-payment poll becomes active. */\nfunction usePollWindow(active: boolean): { elapsedMs: () => number, timedOut: boolean } {\n const startedAtRef = useRef<number | null>(null);\n const [timedOut, setTimedOut] = useState(false);\n\n useEffect(() => {\n if (!active) {\n startedAtRef.current = null;\n setTimedOut(false);\n return undefined;\n }\n startedAtRef.current = Date.now();\n setTimedOut(false);\n const timer = window.setTimeout(() => setTimedOut(true), PLACED_ORDER_POLL_TIMEOUT_MS);\n return () => window.clearTimeout(timer);\n }, [active]);\n\n const elapsedMs = () => (startedAtRef.current == null ? 0 : Date.now() - startedAtRef.current);\n return { elapsedMs, timedOut };\n}\n\n/** Checks ownership of the product identified by the query params. */\nexport function useOwnedProductQuery(\n client: AlacarteCheckoutClient | null,\n enabled: boolean,\n productId: string | null,\n) {\n return useQuery({\n queryKey: alacarteKeys.ownedProduct(productId ?? 'none'),\n enabled: enabled && !!client && !!productId,\n queryFn: (): Promise<OwnedProductResult> => client!.getOwnedProduct(productId!),\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/** Reads the learner's existing open checkout (used when there's no product selector). */\nexport function useAlacarteCheckoutQuery(client: AlacarteCheckoutClient | null, enabled: boolean) {\n return useQuery({\n queryKey: alacarteKeys.checkout(),\n enabled: enabled && !!client,\n queryFn: (): Promise<AlacarteCheckoutResult> => client!.getCheckout(),\n // Keep the open checkout stable while the tab is backgrounded — a focus\n // refetch would remount payment UI / re-run select for no user action.\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/** Selects a course or program into the learner's checkout — fired once ownership is confirmed not-full.\n * `viewedPrice` is `tOwnedProducts.price.amountDue` (USD, before any coupon) for ALC-159. */\nexport function useSelectProductMutation(client: AlacarteCheckoutClient | null) {\n return useMutation({\n mutationFn: (args: {\n selector: ProductSelector,\n viewedPrice?: number,\n }): Promise<AlacarteCheckoutResult> => {\n if (!client) return Promise.reject(new Error('Alacarte client not ready.'));\n const { selector, viewedPrice } = args;\n if (selector.kind === 'course') {\n return client.selectCourse(selector.courseUid, selector.runKey, viewedPrice);\n }\n if (selector.kind === 'program') {\n return client.selectProgram(selector.programUid, viewedPrice);\n }\n return Promise.reject(new Error('selectMutation called with no product selector.'));\n },\n });\n}\n\n/** Updates the learner's checkout — used here to apply ?discount_code= right\n * after the checkout is created. */\nexport function useUpdateCheckoutMutation(client: AlacarteCheckoutClient | null) {\n return useMutation({\n mutationFn: (args: {\n biId?: string, couponCode?: string, termsAccepted?: boolean, acceptPrice?: boolean,\n }): Promise<AlacarteCheckoutResult> => {\n if (!client) return Promise.reject(new Error('Alacarte client not ready.'));\n return client.updateCheckout(args);\n },\n });\n}\n\n/** Fetches display details for the selected product.\n * A course is looked up in Viax by elements[0].course from the\n * subscription course-run API (not the course-run key or the URL param).\n * A program uses filterTwouProgram by tProgramUuid. */\nexport function useProductDetailQuery(\n client: AlacarteCheckoutClient | null,\n enabled: boolean,\n selector: ProductSelector,\n courseKey: string | null = null,\n) {\n const productKey = selector.kind === 'course'\n ? (courseKey ?? 'none')\n : selector.kind === 'program' ? selector.programUid : 'none';\n\n return useQuery({\n queryKey: alacarteKeys.productDetail(`${selector.kind}:${productKey}`),\n enabled: enabled && !!client && (\n selector.kind === 'program' || (selector.kind === 'course' && !!courseKey)\n ),\n queryFn: (): Promise<ProductDetail | null> => {\n if (selector.kind === 'course') return client!.getCourseDetail(courseKey!);\n if (selector.kind === 'program') return client!.getProgramDetail(selector.programUid);\n return Promise.resolve(null);\n },\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n retry: 1,\n });\n}\n\n/**\n * Fetches a placed order for the confirmation page.\n * Polls for 30s while the payment is still confirming (PAYMENT_PENDING,\n * including 3DS). Stops on a placed order, or immediately on NOT_PLACED\n * (payment failed — caller returns the learner to checkout).\n */\nexport function usePlacedOrderQuery(\n client: AlacarteCheckoutClient | null,\n biId: string | null,\n enabled: boolean,\n) {\n const active = enabled && !!client && !!biId;\n const poll = usePollWindow(active);\n const query = useQuery({\n queryKey: alacarteKeys.placedOrder(biId ?? 'none'),\n enabled: active,\n queryFn: (): Promise<PlacedOrderResult> => client!.getPlacedOrder(biId!),\n staleTime: 0,\n refetchOnWindowFocus: false,\n refetchIntervalInBackground: true,\n retry: 1,\n refetchInterval: (q) => placedOrderRefetchInterval(q.state.data, poll.elapsedMs()),\n });\n return { ...query, pollTimedOut: poll.timedOut };\n}\n\n/** Fetches the order's placement/creation timestamps — used for the\n * confirmation page's purchase date and refund-window deadline.\n * Polls until tPlacedAt is set (it stays null while payment is pending). */\nexport function useOrderDatesQuery(\n client: AlacarteCheckoutClient | null,\n biId: string | null,\n enabled: boolean,\n) {\n const active = enabled && !!client && !!biId;\n const poll = usePollWindow(active);\n return useQuery({\n queryKey: alacarteKeys.orderDates(biId ?? 'none'),\n enabled: active,\n queryFn: (): Promise<OrderDatesResult> => client!.getOrderDates(biId!),\n staleTime: 0,\n refetchOnWindowFocus: false,\n refetchIntervalInBackground: true,\n retry: 1,\n refetchInterval: (q) => orderDatesRefetchInterval(q.state.data, poll.elapsedMs()),\n });\n}\n"]}
|
package/dist/types/alacarte.d.ts
CHANGED
|
@@ -107,7 +107,7 @@ export interface AlacarteCheckout {
|
|
|
107
107
|
paymentDecline?: AlacartePaymentDecline | null;
|
|
108
108
|
line: AlacarteLine | null;
|
|
109
109
|
}
|
|
110
|
-
export type AlacarteErrorCode = 'NOT_AUTHENTICATED' | 'RUN_KEY_REQUIRED' | 'CONFLICTING_SELECTION' | 'NOTHING_SELECTED' | 'COURSE_NOT_FOUND' | 'PROGRAM_NOT_FOUND' | 'CHECKOUT_NOT_FOUND' | 'CHECKOUT_NOT_YOURS' | 'PRODUCT_NOT_FOUND' | 'ALREADY_OWNED' | 'NOT_PURCHASABLE' | 'PRICING_UNAVAILABLE' | 'COUPON_INVALID' | 'COUPON_NOT_ACTIVE' | 'COUPON_EXPIRED' | 'COUPON_CAP_REACHED' | 'COUPON_PRODUCT_NOT_ELIGIBLE' | 'COUPON_PARTY_NOT_ELIGIBLE' | 'COUPON_PER_CUSTOMER_LIMIT' | 'COUPON_REJECTED' | 'ORDER_ID_REQUIRED' | 'ORDER_NOT_FOUND' | 'NOT_PLACED' | 'ORDER_READ_FAILED';
|
|
110
|
+
export type AlacarteErrorCode = 'NOT_AUTHENTICATED' | 'RUN_KEY_REQUIRED' | 'CONFLICTING_SELECTION' | 'NOTHING_SELECTED' | 'COURSE_NOT_FOUND' | 'PROGRAM_NOT_FOUND' | 'CHECKOUT_NOT_FOUND' | 'CHECKOUT_NOT_YOURS' | 'PRODUCT_NOT_FOUND' | 'ALREADY_OWNED' | 'NOT_PURCHASABLE' | 'PRICING_UNAVAILABLE' | 'COUPON_INVALID' | 'COUPON_NOT_ACTIVE' | 'COUPON_EXPIRED' | 'COUPON_CAP_REACHED' | 'COUPON_PRODUCT_NOT_ELIGIBLE' | 'COUPON_PARTY_NOT_ELIGIBLE' | 'COUPON_PER_CUSTOMER_LIMIT' | 'COUPON_REJECTED' | 'ORDER_ID_REQUIRED' | 'ORDER_NOT_FOUND' | 'NOT_PLACED' | 'ORDER_READ_FAILED' | 'PAYMENT_PENDING';
|
|
111
111
|
export interface AlacarteReplaced {
|
|
112
112
|
courseName?: string;
|
|
113
113
|
courseUid?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alacarte.js","sourceRoot":"","sources":["../../src/types/alacarte.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * types/alacarte.ts — domain types for the à la carte checkout flow\n * (tCheckout / tCheckoutSelect / tCheckoutUpdate / tOwnedProducts), a separate\n * bounded context from the subscription checkout modeled in ../types.ts.\n * See alacarte-checkout-api.md and alacarte-owned-products-api.md at the repo\n * root for the full contract.\n */\nimport type { GqlRunner, CourseDetail } from '../types';\n\nexport type { GqlRunner };\n\n// --- Checkout status -----------------------------------------------------------\nexport type AlacarteStatus =\n | 'CART_CREATED'\n | 'CART_SANCTION_PASSED'\n | 'CART_PAYMENT_FAILED';\n\n// --- Money -----------------------------------------------------------------------\nexport interface AlacarteMoney {\n amount: number,\n currency: string,\n}\n\n// --- Checkout line (discriminated union on `kind`) --------------------------------\n/** Where the line discount came from — coupon code vs automatic offer (bundle / first-order). */\nexport type AlacarteDiscountSource = 'coupon' | 'automatic';\n\nexport interface AlacarteCourseLine {\n biiId: string,\n kind: 'course',\n courseName: string,\n courseUid: string,\n courseRunKey: string,\n /** The seat being bought on that run, e.g. \"verified\". */\n seatType: string | null,\n price: AlacarteMoney | null,\n /** Taken off by an applied coupon or automatic offer; absent when neither applies. */\n discount: AlacarteMoney | null,\n /** `\"coupon\"` or `\"automatic\"`; absent with no discount. */\n discountSource?: AlacarteDiscountSource | null,\n /** Discount name for the order summary, e.g. \"First order discount\". */\n discountName?: string | null,\n /** price less discount. */\n amountDue: AlacarteMoney | null,\n billingAddressSet: boolean,\n}\n\n/** Owned member course on a program checkout line (simpler than OwnedCourseRef). */\nexport interface AlacarteProgramOwnedCourse {\n courseId: string,\n name: string,\n via: 'order' | 'program' | 'enrollment',\n}\n\nexport interface AlacarteProgramLine {\n biiId: string,\n kind: 'program',\n programId: string,\n programName: string,\n price: AlacarteMoney | null,\n /** Taken off for member courses the learner already owns; absent when they own none. */\n deduction: AlacarteMoney | null,\n /**\n * Member courses the learner already owns — explains the deduction\n * (\"Already yours: …\"). Empty/absent when they own none.\n */\n ownedCourses?: AlacarteProgramOwnedCourse[] | null,\n /** Taken off by an applied coupon or automatic offer; absent when neither applies. */\n discount: AlacarteMoney | null,\n /** `\"coupon\"` or `\"automatic\"`; absent with no discount. */\n discountSource?: AlacarteDiscountSource | null,\n /** Discount name for the order summary, e.g. \"Bundle discount\". */\n discountName?: string | null,\n /** price less deduction less discount. */\n amountDue: AlacarteMoney | null,\n billingAddressSet: boolean,\n}\n\nexport type AlacarteLine = AlacarteCourseLine | AlacarteProgramLine;\n\n/** Present when the learner switches product while a coupon is on the cart. */\nexport type AlacarteCouponRecheckOutcome = 'kept' | 'rejected' | 'unverified';\n\nexport interface AlacarteCouponRecheck {\n outcome: AlacarteCouponRecheckOutcome,\n /** Coupon error code when outcome is `rejected`. */\n reason?: AlacarteErrorCode | string | null,\n}\n\n/** Last card-decline details; cleared when the next payment attempt starts. */\nexport interface AlacartePaymentDecline {\n code?: string | null,\n /** Ready to show the learner. */\n message: string,\n}\n\n/** Why a selection was refused — see section 5 of alacarte-checkout-api.md. */\nexport interface AlacarteBlocked {\n reason: 'ALREADY_OWNED' | 'NOT_PURCHASABLE',\n productId: string,\n kind: 'course' | 'program',\n /** ALREADY_OWNED only — where to send the learner (the learner dashboard). */\n route?: string,\n /** ALREADY_OWNED only. */\n ownedCourses?: OwnedCourseRef[],\n /** NOT_PURCHASABLE only — the catalog's reason, e.g. \"program_not_one_click_eligible\". */\n detail?: string,\n}\n\n/** Present when the price the learner saw (viewedPrice) differs from what checkout resolves now. */\nexport interface AlacartePriceChanged {\n viewed: AlacarteMoney,\n current: AlacarteMoney,\n}\n\n// --- Checkout envelope -------------------------------------------------------------\nexport interface AlacarteCheckout {\n biId: string,\n uid: string,\n status: AlacarteStatus,\n // Not always present in real responses (absent, not null, when no coupon was entered).\n couponCode?: string | null,\n termsAcceptedAt: string | null,\n acceptedAmount?: AlacarteMoney | null,\n priceAccepted?: boolean,\n /** True when this is the learner's first à-la-carte order (first-order offer applies). */\n firstOrder?: boolean,\n /** Present after the card was declined on the last attempt. */\n paymentDecline?: AlacartePaymentDecline | null,\n line: AlacarteLine | null,\n}\n\nexport type AlacarteErrorCode =\n | 'NOT_AUTHENTICATED'\n | 'RUN_KEY_REQUIRED'\n | 'CONFLICTING_SELECTION'\n | 'NOTHING_SELECTED'\n | 'COURSE_NOT_FOUND'\n | 'PROGRAM_NOT_FOUND'\n | 'CHECKOUT_NOT_FOUND'\n | 'CHECKOUT_NOT_YOURS'\n | 'PRODUCT_NOT_FOUND'\n // Blocked selection — details in the `blocked` envelope field.\n | 'ALREADY_OWNED'\n | 'NOT_PURCHASABLE'\n // No price can be resolved for the product right now.\n | 'PRICING_UNAVAILABLE'\n // Coupon errors — returned from tCheckoutUpdate, checkout left unchanged.\n | 'COUPON_INVALID'\n | 'COUPON_NOT_ACTIVE'\n | 'COUPON_EXPIRED'\n | 'COUPON_CAP_REACHED'\n | 'COUPON_PRODUCT_NOT_ELIGIBLE'\n | 'COUPON_PARTY_NOT_ELIGIBLE'\n | 'COUPON_PER_CUSTOMER_LIMIT'\n | 'COUPON_REJECTED'\n // tPlacedOrder errors.\n | 'ORDER_ID_REQUIRED'\n | 'ORDER_NOT_FOUND'\n | 'NOT_PLACED'\n | 'ORDER_READ_FAILED';\n\nexport interface AlacarteReplaced {\n courseName?: string,\n courseUid?: string,\n courseRunKey?: string,\n programId?: string,\n programName?: string,\n}\n\n/** Shared response envelope for tCheckout / tCheckoutSelect / tCheckoutUpdate. */\nexport interface AlacarteCheckoutResult {\n ok: boolean,\n checkout: AlacarteCheckout | null,\n replaced?: AlacarteReplaced,\n priceChanged?: AlacartePriceChanged,\n blocked?: AlacarteBlocked,\n /** Present when select switches product while a coupon was applied. */\n couponRecheck?: AlacarteCouponRecheck,\n error?: AlacarteErrorCode | string,\n}\n\n// --- Owned products ------------------------------------------------------------------\nexport type OwnedProductStatus = 'full' | 'partial' | 'none';\n\nexport interface OwnedCourseRef {\n courseId: string,\n via: 'order' | 'program' | 'enrollment',\n orderBiId?: string,\n enrollmentBiId?: string,\n programId?: string,\n name: string,\n slug: string,\n}\n\nexport interface OwnedProductPrice {\n full: number,\n deduction: number,\n amountDue: number,\n currency: string,\n}\n\nexport interface OwnedProduct {\n productId: string,\n kind: 'course' | 'program',\n name: string,\n slug: string,\n status: OwnedProductStatus,\n memberCount?: number,\n ownedCourses: OwnedCourseRef[],\n /** Pricing breakdown — available when the backend returns it (e.g. for\n * already-owned products where the checkout line is not created). */\n price?: OwnedProductPrice | null,\n}\n\nexport interface OwnedProductResult {\n ok: boolean,\n product: OwnedProduct | null,\n error?: AlacarteErrorCode | string,\n}\n\nexport interface OwnedProgramRef {\n programId: string,\n orderBiId: string,\n}\n\nexport interface OwnedProductsSummary {\n courses: OwnedCourseRef[],\n programs: OwnedProgramRef[],\n}\n\nexport interface AllOwnedProductsResult {\n ok: boolean,\n owned: OwnedProductsSummary | null,\n error?: AlacarteErrorCode | string,\n}\n\n// --- Program details (filterTwouProgram, by tProgramUuid) -----------------------------\nexport interface ProgramCourseRef {\n id: string,\n uid: string | null,\n maName: string | null,\n maDescription: string | null,\n tShortDescription: string | null,\n imageUrl: string | null,\n tBrandText: string | null,\n tOrgLogoUrl: string | null,\n tUrlCourse: string | null,\n tMarketingSlug: string | null,\n tStatus: { code: string } | null,\n}\n\nexport interface ProgramDetail {\n uid: string,\n tEffortLow: number | null,\n tEnrollmentCount: number | null,\n tMarketingSlug: string | null,\n tSkills: string[] | null,\n tFaq: string | null,\n tLevelType: string | null,\n tCourses: ProgramCourseRef[],\n}\n\n/** Course or program detail, depending on which was selected. */\nexport type ProductDetail = CourseDetail | ProgramDetail;\n\n// --- Banner details (PDP banner before checkout) ---------------------------------\n\nexport interface BannerCourseInstructor {\n tGivenName: string | null,\n tFamilyName: string | null,\n tOrganization: string | null,\n tInstructorUuid: string | null,\n tSortValue: number | null,\n}\n\nexport interface BannerCourseDetail {\n uid: string,\n id: string | null,\n maName: string,\n tEnrollmentCount: number | null,\n tAlacartePurchasable: boolean | null,\n tCourseKey: string | null,\n tShortDescription: string | null,\n maDescription: string | null,\n tBrandText: string | null,\n tOrgLogoUrl: string | null,\n imageUrl: string | null,\n tUrlCourse: string | null,\n tMarketingSlug: string | null,\n tLevelType: 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 tLanguage: string | null,\n tExpectedLearningItems: string | null,\n tSkills: string[] | null,\n tPrerequisites: string | null,\n tSyllabus: string | null,\n tFaq: string | null,\n tTestimonials: string | null,\n tTranscriptLanguages: string[] | null,\n tInstructors: BannerCourseInstructor[],\n}\n\nexport interface BannerProgramCourse {\n id: string,\n uid: string | null,\n maName: string | null,\n tShortDescription: string | null,\n maDescription: string | null,\n tBrandText: string | null,\n tOrgLogoUrl: string | null,\n tCourseKey: string | null,\n imageUrl: string | null,\n tUrlCourse: string | null,\n tMarketingSlug: string | null,\n tStatus: { code: string } | null,\n}\n\nexport interface BannerProgramDetail {\n uid: string,\n tEffortLow: number | null,\n tEnrollmentCount: number | null,\n tMarketingSlug: string | null,\n tAlacartePurchasable: boolean | null,\n tSkills: string[] | null,\n tFaq: string | null,\n tLevelType: string | null,\n imageUrl: string | null,\n tBannerImageUrl: string | null,\n tCourses: BannerProgramCourse[],\n}\n\nexport type BannerData =\n | { kind: 'course', data: BannerCourseDetail }\n | { kind: 'program', data: BannerProgramDetail };\n\n// --- Placed order (tPlacedOrder, confirmation page) -------------------------------------\nexport interface PlacedOrderAmounts {\n price: number,\n deduction: number,\n discount: number,\n paid: number,\n currency: string,\n}\n\nexport interface PlacedOrderLine {\n lineId: string,\n kind: 'course' | 'program',\n productId?: string,\n productUid?: string,\n name: string,\n /** Present on course lines and on each course line of a program order. */\n courseRunKey?: string | null,\n seatType?: string | null,\n /** Set on every line of a program order (one line per course bought). */\n programId?: string,\n bundleRef?: string,\n discountRuleId?: string,\n amounts: PlacedOrderAmounts,\n}\n\nexport interface PlacedOrder {\n orderId: string,\n orderNumber: string,\n status: string,\n couponCode: string | null,\n paymentMethod: { brand: string, last4: string } | null,\n lines: PlacedOrderLine[],\n totals: PlacedOrderAmounts,\n}\n\nexport interface PlacedOrderResult {\n ok: boolean,\n order: PlacedOrder | null,\n error?: AlacarteErrorCode | string,\n}\n\n/** getTwouOrder(biId) — order placement/creation timestamps, ISO strings. */\nexport interface OrderDates {\n tPlacedAt: string | null,\n biCreatedAt: string | null,\n}\n\nexport interface OrderDatesResult {\n ok: boolean,\n dates: OrderDates | null,\n}\n\n/** Billing for tCheckoutPlaceOrder. Only `country` (ISO-2) is required;\n * name/line/city/etc. are optional when the UI doesn't collect a full address. */\nexport interface AlacarteBillingAddress {\n country: string,\n firstName?: string,\n lastName?: string,\n line1?: string,\n line2?: string,\n city?: string,\n state?: string,\n postcode?: string,\n}\n\n/** Stripe payment details returned by a successful tCheckoutPlaceOrder. */\nexport interface AlacartePlaceOrderPayment {\n clientSecret: string,\n paymentIntentId: string,\n /** Amount in cents. */\n amount: number,\n currency: string,\n}\n\nexport type AlacartePlaceOrderErrorCode =\n | 'TERMS_NOT_ACCEPTED'\n | 'PRICE_NOT_ACCEPTED'\n | 'BILLING_ADDRESS_REQUIRED'\n | 'SANCTIONS_BLOCKED'\n | 'COUPON_UNAVAILABLE'\n | 'PAYMENT_NOT_STARTED'\n | 'CHECKOUT_NOT_FOUND'\n | 'NOT_AUTHENTICATED';\n\nexport interface AlacartePlaceOrderApiResult {\n ok: boolean,\n payment: AlacartePlaceOrderPayment | null,\n /** Present on some failures (e.g. PRICE_NOT_ACCEPTED, COUPON_UNAVAILABLE). */\n checkout?: AlacarteCheckout | null,\n error?: AlacartePlaceOrderErrorCode | string,\n}\n\n// --- Product selector (derived from query params) --------------------------------------\nexport type ProductSelector =\n | { kind: 'course', courseUid: string, runKey: string }\n | { kind: 'program', programUid: string }\n | { kind: 'invalid' }\n | { kind: 'none' };\n\n// --- Client surface --------------------------------------------------------------------\nexport interface AlacarteCheckoutClient {\n gql: GqlRunner,\n setToken: (token: string) => void,\n getCheckout: () => Promise<AlacarteCheckoutResult>,\n /** viewedPrice: the amount the learner saw on the page before checkout (USD, before any coupon). */\n selectCourse: (courseUid: string, runKey: string, viewedPrice?: number) => Promise<AlacarteCheckoutResult>,\n selectProgram: (programUid: string, viewedPrice?: number) => Promise<AlacarteCheckoutResult>,\n updateCheckout: (args: {\n biId?: string, couponCode?: string, termsAccepted?: boolean, acceptPrice?: boolean,\n }) => Promise<AlacarteCheckoutResult>,\n getOwnedProduct: (productId: string) => Promise<OwnedProductResult>,\n getAllOwnedProducts: () => Promise<AllOwnedProductsResult>,\n getProgramDetail: (programUid: string) => Promise<ProgramDetail | null>,\n getCourseDetail: (courseKey: string) => Promise<CourseDetail | null>,\n getPlacedOrder: (biId: string) => Promise<PlacedOrderResult>,\n getOrderDates: (biId: string) => Promise<OrderDatesResult>,\n /** One-call place order — returns Stripe client secret (section 5a). */\n placeCheckoutOrder: (\n biId: string,\n billing?: AlacarteBillingAddress,\n ) => Promise<AlacartePlaceOrderApiResult>,\n}\n"]}
|
|
1
|
+
{"version":3,"file":"alacarte.js","sourceRoot":"","sources":["../../src/types/alacarte.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * types/alacarte.ts — domain types for the à la carte checkout flow\n * (tCheckout / tCheckoutSelect / tCheckoutUpdate / tOwnedProducts), a separate\n * bounded context from the subscription checkout modeled in ../types.ts.\n * See alacarte-checkout-api.md and alacarte-owned-products-api.md at the repo\n * root for the full contract.\n */\nimport type { GqlRunner, CourseDetail } from '../types';\n\nexport type { GqlRunner };\n\n// --- Checkout status -----------------------------------------------------------\nexport type AlacarteStatus =\n | 'CART_CREATED'\n | 'CART_SANCTION_PASSED'\n | 'CART_PAYMENT_FAILED';\n\n// --- Money -----------------------------------------------------------------------\nexport interface AlacarteMoney {\n amount: number,\n currency: string,\n}\n\n// --- Checkout line (discriminated union on `kind`) --------------------------------\n/** Where the line discount came from — coupon code vs automatic offer (bundle / first-order). */\nexport type AlacarteDiscountSource = 'coupon' | 'automatic';\n\nexport interface AlacarteCourseLine {\n biiId: string,\n kind: 'course',\n courseName: string,\n courseUid: string,\n courseRunKey: string,\n /** The seat being bought on that run, e.g. \"verified\". */\n seatType: string | null,\n price: AlacarteMoney | null,\n /** Taken off by an applied coupon or automatic offer; absent when neither applies. */\n discount: AlacarteMoney | null,\n /** `\"coupon\"` or `\"automatic\"`; absent with no discount. */\n discountSource?: AlacarteDiscountSource | null,\n /** Discount name for the order summary, e.g. \"First order discount\". */\n discountName?: string | null,\n /** price less discount. */\n amountDue: AlacarteMoney | null,\n billingAddressSet: boolean,\n}\n\n/** Owned member course on a program checkout line (simpler than OwnedCourseRef). */\nexport interface AlacarteProgramOwnedCourse {\n courseId: string,\n name: string,\n via: 'order' | 'program' | 'enrollment',\n}\n\nexport interface AlacarteProgramLine {\n biiId: string,\n kind: 'program',\n programId: string,\n programName: string,\n price: AlacarteMoney | null,\n /** Taken off for member courses the learner already owns; absent when they own none. */\n deduction: AlacarteMoney | null,\n /**\n * Member courses the learner already owns — explains the deduction\n * (\"Already yours: …\"). Empty/absent when they own none.\n */\n ownedCourses?: AlacarteProgramOwnedCourse[] | null,\n /** Taken off by an applied coupon or automatic offer; absent when neither applies. */\n discount: AlacarteMoney | null,\n /** `\"coupon\"` or `\"automatic\"`; absent with no discount. */\n discountSource?: AlacarteDiscountSource | null,\n /** Discount name for the order summary, e.g. \"Bundle discount\". */\n discountName?: string | null,\n /** price less deduction less discount. */\n amountDue: AlacarteMoney | null,\n billingAddressSet: boolean,\n}\n\nexport type AlacarteLine = AlacarteCourseLine | AlacarteProgramLine;\n\n/** Present when the learner switches product while a coupon is on the cart. */\nexport type AlacarteCouponRecheckOutcome = 'kept' | 'rejected' | 'unverified';\n\nexport interface AlacarteCouponRecheck {\n outcome: AlacarteCouponRecheckOutcome,\n /** Coupon error code when outcome is `rejected`. */\n reason?: AlacarteErrorCode | string | null,\n}\n\n/** Last card-decline details; cleared when the next payment attempt starts. */\nexport interface AlacartePaymentDecline {\n code?: string | null,\n /** Ready to show the learner. */\n message: string,\n}\n\n/** Why a selection was refused — see section 5 of alacarte-checkout-api.md. */\nexport interface AlacarteBlocked {\n reason: 'ALREADY_OWNED' | 'NOT_PURCHASABLE',\n productId: string,\n kind: 'course' | 'program',\n /** ALREADY_OWNED only — where to send the learner (the learner dashboard). */\n route?: string,\n /** ALREADY_OWNED only. */\n ownedCourses?: OwnedCourseRef[],\n /** NOT_PURCHASABLE only — the catalog's reason, e.g. \"program_not_one_click_eligible\". */\n detail?: string,\n}\n\n/** Present when the price the learner saw (viewedPrice) differs from what checkout resolves now. */\nexport interface AlacartePriceChanged {\n viewed: AlacarteMoney,\n current: AlacarteMoney,\n}\n\n// --- Checkout envelope -------------------------------------------------------------\nexport interface AlacarteCheckout {\n biId: string,\n uid: string,\n status: AlacarteStatus,\n // Not always present in real responses (absent, not null, when no coupon was entered).\n couponCode?: string | null,\n termsAcceptedAt: string | null,\n acceptedAmount?: AlacarteMoney | null,\n priceAccepted?: boolean,\n /** True when this is the learner's first à-la-carte order (first-order offer applies). */\n firstOrder?: boolean,\n /** Present after the card was declined on the last attempt. */\n paymentDecline?: AlacartePaymentDecline | null,\n line: AlacarteLine | null,\n}\n\nexport type AlacarteErrorCode =\n | 'NOT_AUTHENTICATED'\n | 'RUN_KEY_REQUIRED'\n | 'CONFLICTING_SELECTION'\n | 'NOTHING_SELECTED'\n | 'COURSE_NOT_FOUND'\n | 'PROGRAM_NOT_FOUND'\n | 'CHECKOUT_NOT_FOUND'\n | 'CHECKOUT_NOT_YOURS'\n | 'PRODUCT_NOT_FOUND'\n // Blocked selection — details in the `blocked` envelope field.\n | 'ALREADY_OWNED'\n | 'NOT_PURCHASABLE'\n // No price can be resolved for the product right now.\n | 'PRICING_UNAVAILABLE'\n // Coupon errors — returned from tCheckoutUpdate, checkout left unchanged.\n | 'COUPON_INVALID'\n | 'COUPON_NOT_ACTIVE'\n | 'COUPON_EXPIRED'\n | 'COUPON_CAP_REACHED'\n | 'COUPON_PRODUCT_NOT_ELIGIBLE'\n | 'COUPON_PARTY_NOT_ELIGIBLE'\n | 'COUPON_PER_CUSTOMER_LIMIT'\n | 'COUPON_REJECTED'\n // tPlacedOrder errors.\n | 'ORDER_ID_REQUIRED'\n | 'ORDER_NOT_FOUND'\n | 'NOT_PLACED'\n | 'ORDER_READ_FAILED'\n // Payment is still being confirmed, including a 3D Secure check. Poll; not a failure yet.\n | 'PAYMENT_PENDING';\n\nexport interface AlacarteReplaced {\n courseName?: string,\n courseUid?: string,\n courseRunKey?: string,\n programId?: string,\n programName?: string,\n}\n\n/** Shared response envelope for tCheckout / tCheckoutSelect / tCheckoutUpdate. */\nexport interface AlacarteCheckoutResult {\n ok: boolean,\n checkout: AlacarteCheckout | null,\n replaced?: AlacarteReplaced,\n priceChanged?: AlacartePriceChanged,\n blocked?: AlacarteBlocked,\n /** Present when select switches product while a coupon was applied. */\n couponRecheck?: AlacarteCouponRecheck,\n error?: AlacarteErrorCode | string,\n}\n\n// --- Owned products ------------------------------------------------------------------\nexport type OwnedProductStatus = 'full' | 'partial' | 'none';\n\nexport interface OwnedCourseRef {\n courseId: string,\n via: 'order' | 'program' | 'enrollment',\n orderBiId?: string,\n enrollmentBiId?: string,\n programId?: string,\n name: string,\n slug: string,\n}\n\nexport interface OwnedProductPrice {\n full: number,\n deduction: number,\n amountDue: number,\n currency: string,\n}\n\nexport interface OwnedProduct {\n productId: string,\n kind: 'course' | 'program',\n name: string,\n slug: string,\n status: OwnedProductStatus,\n memberCount?: number,\n ownedCourses: OwnedCourseRef[],\n /** Pricing breakdown — available when the backend returns it (e.g. for\n * already-owned products where the checkout line is not created). */\n price?: OwnedProductPrice | null,\n}\n\nexport interface OwnedProductResult {\n ok: boolean,\n product: OwnedProduct | null,\n error?: AlacarteErrorCode | string,\n}\n\nexport interface OwnedProgramRef {\n programId: string,\n orderBiId: string,\n}\n\nexport interface OwnedProductsSummary {\n courses: OwnedCourseRef[],\n programs: OwnedProgramRef[],\n}\n\nexport interface AllOwnedProductsResult {\n ok: boolean,\n owned: OwnedProductsSummary | null,\n error?: AlacarteErrorCode | string,\n}\n\n// --- Program details (filterTwouProgram, by tProgramUuid) -----------------------------\nexport interface ProgramCourseRef {\n id: string,\n uid: string | null,\n maName: string | null,\n maDescription: string | null,\n tShortDescription: string | null,\n imageUrl: string | null,\n tBrandText: string | null,\n tOrgLogoUrl: string | null,\n tUrlCourse: string | null,\n tMarketingSlug: string | null,\n tStatus: { code: string } | null,\n}\n\nexport interface ProgramDetail {\n uid: string,\n tEffortLow: number | null,\n tEnrollmentCount: number | null,\n tMarketingSlug: string | null,\n tSkills: string[] | null,\n tFaq: string | null,\n tLevelType: string | null,\n tCourses: ProgramCourseRef[],\n}\n\n/** Course or program detail, depending on which was selected. */\nexport type ProductDetail = CourseDetail | ProgramDetail;\n\n// --- Banner details (PDP banner before checkout) ---------------------------------\n\nexport interface BannerCourseInstructor {\n tGivenName: string | null,\n tFamilyName: string | null,\n tOrganization: string | null,\n tInstructorUuid: string | null,\n tSortValue: number | null,\n}\n\nexport interface BannerCourseDetail {\n uid: string,\n id: string | null,\n maName: string,\n tEnrollmentCount: number | null,\n tAlacartePurchasable: boolean | null,\n tCourseKey: string | null,\n tShortDescription: string | null,\n maDescription: string | null,\n tBrandText: string | null,\n tOrgLogoUrl: string | null,\n imageUrl: string | null,\n tUrlCourse: string | null,\n tMarketingSlug: string | null,\n tLevelType: 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 tLanguage: string | null,\n tExpectedLearningItems: string | null,\n tSkills: string[] | null,\n tPrerequisites: string | null,\n tSyllabus: string | null,\n tFaq: string | null,\n tTestimonials: string | null,\n tTranscriptLanguages: string[] | null,\n tInstructors: BannerCourseInstructor[],\n}\n\nexport interface BannerProgramCourse {\n id: string,\n uid: string | null,\n maName: string | null,\n tShortDescription: string | null,\n maDescription: string | null,\n tBrandText: string | null,\n tOrgLogoUrl: string | null,\n tCourseKey: string | null,\n imageUrl: string | null,\n tUrlCourse: string | null,\n tMarketingSlug: string | null,\n tStatus: { code: string } | null,\n}\n\nexport interface BannerProgramDetail {\n uid: string,\n tEffortLow: number | null,\n tEnrollmentCount: number | null,\n tMarketingSlug: string | null,\n tAlacartePurchasable: boolean | null,\n tSkills: string[] | null,\n tFaq: string | null,\n tLevelType: string | null,\n imageUrl: string | null,\n tBannerImageUrl: string | null,\n tCourses: BannerProgramCourse[],\n}\n\nexport type BannerData =\n | { kind: 'course', data: BannerCourseDetail }\n | { kind: 'program', data: BannerProgramDetail };\n\n// --- Placed order (tPlacedOrder, confirmation page) -------------------------------------\nexport interface PlacedOrderAmounts {\n price: number,\n deduction: number,\n discount: number,\n paid: number,\n currency: string,\n}\n\nexport interface PlacedOrderLine {\n lineId: string,\n kind: 'course' | 'program',\n productId?: string,\n productUid?: string,\n name: string,\n /** Present on course lines and on each course line of a program order. */\n courseRunKey?: string | null,\n seatType?: string | null,\n /** Set on every line of a program order (one line per course bought). */\n programId?: string,\n bundleRef?: string,\n discountRuleId?: string,\n amounts: PlacedOrderAmounts,\n}\n\nexport interface PlacedOrder {\n orderId: string,\n orderNumber: string,\n status: string,\n couponCode: string | null,\n paymentMethod: { brand: string, last4: string } | null,\n lines: PlacedOrderLine[],\n totals: PlacedOrderAmounts,\n}\n\nexport interface PlacedOrderResult {\n ok: boolean,\n order: PlacedOrder | null,\n error?: AlacarteErrorCode | string,\n}\n\n/** getTwouOrder(biId) — order placement/creation timestamps, ISO strings. */\nexport interface OrderDates {\n tPlacedAt: string | null,\n biCreatedAt: string | null,\n}\n\nexport interface OrderDatesResult {\n ok: boolean,\n dates: OrderDates | null,\n}\n\n/** Billing for tCheckoutPlaceOrder. Only `country` (ISO-2) is required;\n * name/line/city/etc. are optional when the UI doesn't collect a full address. */\nexport interface AlacarteBillingAddress {\n country: string,\n firstName?: string,\n lastName?: string,\n line1?: string,\n line2?: string,\n city?: string,\n state?: string,\n postcode?: string,\n}\n\n/** Stripe payment details returned by a successful tCheckoutPlaceOrder. */\nexport interface AlacartePlaceOrderPayment {\n clientSecret: string,\n paymentIntentId: string,\n /** Amount in cents. */\n amount: number,\n currency: string,\n}\n\nexport type AlacartePlaceOrderErrorCode =\n | 'TERMS_NOT_ACCEPTED'\n | 'PRICE_NOT_ACCEPTED'\n | 'BILLING_ADDRESS_REQUIRED'\n | 'SANCTIONS_BLOCKED'\n | 'COUPON_UNAVAILABLE'\n | 'PAYMENT_NOT_STARTED'\n | 'CHECKOUT_NOT_FOUND'\n | 'NOT_AUTHENTICATED';\n\nexport interface AlacartePlaceOrderApiResult {\n ok: boolean,\n payment: AlacartePlaceOrderPayment | null,\n /** Present on some failures (e.g. PRICE_NOT_ACCEPTED, COUPON_UNAVAILABLE). */\n checkout?: AlacarteCheckout | null,\n error?: AlacartePlaceOrderErrorCode | string,\n}\n\n// --- Product selector (derived from query params) --------------------------------------\nexport type ProductSelector =\n | { kind: 'course', courseUid: string, runKey: string }\n | { kind: 'program', programUid: string }\n | { kind: 'invalid' }\n | { kind: 'none' };\n\n// --- Client surface --------------------------------------------------------------------\nexport interface AlacarteCheckoutClient {\n gql: GqlRunner,\n setToken: (token: string) => void,\n getCheckout: () => Promise<AlacarteCheckoutResult>,\n /** viewedPrice: the amount the learner saw on the page before checkout (USD, before any coupon). */\n selectCourse: (courseUid: string, runKey: string, viewedPrice?: number) => Promise<AlacarteCheckoutResult>,\n selectProgram: (programUid: string, viewedPrice?: number) => Promise<AlacarteCheckoutResult>,\n updateCheckout: (args: {\n biId?: string, couponCode?: string, termsAccepted?: boolean, acceptPrice?: boolean,\n }) => Promise<AlacarteCheckoutResult>,\n getOwnedProduct: (productId: string) => Promise<OwnedProductResult>,\n getAllOwnedProducts: () => Promise<AllOwnedProductsResult>,\n getProgramDetail: (programUid: string) => Promise<ProgramDetail | null>,\n getCourseDetail: (courseKey: string) => Promise<CourseDetail | null>,\n getPlacedOrder: (biId: string) => Promise<PlacedOrderResult>,\n getOrderDates: (biId: string) => Promise<OrderDatesResult>,\n /** One-call place order — returns Stripe client secret (section 5a). */\n placeCheckoutOrder: (\n biId: string,\n billing?: AlacarteBillingAddress,\n ) => Promise<AlacartePlaceOrderApiResult>,\n}\n"]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 1. tCheckoutUpdate(termsAccepted, acceptPrice?)
|
|
6
6
|
* 2. tCheckoutPlaceOrder(biId, billing address) → Stripe client secret
|
|
7
7
|
* 3. stripe.confirmCardPayment(clientSecret, { payment_method: { card, billing_details } })
|
|
8
|
-
* 4. caller polls tPlacedOrder(biId)
|
|
8
|
+
* 4. caller polls tPlacedOrder(biId) for up to 30s while PAYMENT_PENDING
|
|
9
9
|
*
|
|
10
10
|
* Do not call platform BI transitions (Sanction_Check / CART_PAYMENT_PENDING)
|
|
11
11
|
* directly — tCheckoutPlaceOrder handles sanctions + payment start.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alacartePayment.js","sourceRoot":"","sources":["../../src/utils/alacartePayment.ts"],"names":[],"mappings":";;;;;;;;;AAgBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AASvD;;;GAGG;AACH,MAAM,UAAgB,sBAAsB,CAC1C,cAAgC,EAChC,QAA0B;;;QAE1B,MAAM,MAAM,GAAG,MAAM,cAAc,iBACjC,IAAI,EAAE,QAAQ,CAAC,IAAI,EACnB,aAAa,EAAE,IAAI,IAChB,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EACxD,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,MAAA,MAAM,CAAC,KAAK,mCAAI,yCAAyC,CAAC,CAAC;QAC7E,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QAED,OAAO,MAAM,CAAC,QAAQ,CAAC;IACzB,CAAC;CAAA;AAOD;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAQxC;IACC,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;QAC7C,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,QAAQ,EAAE,OAAO,CAAC,GAAG;KACtB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAgB,kBAAkB,CAAC,IAgBxC;;;QACC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW;YAC/B,CAAC,CAAC,IAAI,CAAC,QAAQ;YACf,CAAC,CAAC,MAAM,sBAAsB,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAErE,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC9C,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAE1E,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAA,MAAA,MAAM,CAAC,OAAO,0CAAE,YAAY,CAAA,EAAE,CAAC;YAChD,MAAM,iBAAiB,CACrB,MAAA,MAAM,CAAC,KAAK,mCAAI,qBAAqB,EACrC,MAAA,MAAM,CAAC,QAAQ,mCAAI,IAAI,CACxB,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAE3D,OAAO;YACL,QAAQ,EAAE,MAAA,MAAM,CAAC,QAAQ,mCAAI,QAAQ;YACrC,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC;IACJ,CAAC;CAAA;AAED,6DAA6D;AAC7D,MAAM,UAAgB,yBAAyB,CAAC,IAG/C;;QACC,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAClF,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC;CAAA","sourcesContent":["/**\n * Prepares an à la carte checkout and places the order via tCheckoutPlaceOrder.\n *\n * Sequence:\n * 1. tCheckoutUpdate(termsAccepted, acceptPrice?)\n * 2. tCheckoutPlaceOrder(biId, billing address) → Stripe client secret\n * 3. stripe.confirmCardPayment(clientSecret, { payment_method: { card, billing_details } })\n * 4. caller polls tPlacedOrder(biId)
|
|
1
|
+
{"version":3,"file":"alacartePayment.js","sourceRoot":"","sources":["../../src/utils/alacartePayment.ts"],"names":[],"mappings":";;;;;;;;;AAgBA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AASvD;;;GAGG;AACH,MAAM,UAAgB,sBAAsB,CAC1C,cAAgC,EAChC,QAA0B;;;QAE1B,MAAM,MAAM,GAAG,MAAM,cAAc,iBACjC,IAAI,EAAE,QAAQ,CAAC,IAAI,EACnB,aAAa,EAAE,IAAI,IAChB,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EACxD,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,MAAA,MAAM,CAAC,KAAK,mCAAI,yCAAyC,CAAC,CAAC;QAC7E,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QAED,OAAO,MAAM,CAAC,QAAQ,CAAC;IACzB,CAAC;CAAA;AAOD;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAQxC;IACC,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;QAC7C,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,QAAQ,EAAE,OAAO,CAAC,GAAG;KACtB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAgB,kBAAkB,CAAC,IAgBxC;;;QACC,MAAM,QAAQ,GAAG,IAAI,CAAC,WAAW;YAC/B,CAAC,CAAC,IAAI,CAAC,QAAQ;YACf,CAAC,CAAC,MAAM,sBAAsB,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAErE,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC9C,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAE1E,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAA,MAAA,MAAM,CAAC,OAAO,0CAAE,YAAY,CAAA,EAAE,CAAC;YAChD,MAAM,iBAAiB,CACrB,MAAA,MAAM,CAAC,KAAK,mCAAI,qBAAqB,EACrC,MAAA,MAAM,CAAC,QAAQ,mCAAI,IAAI,CACxB,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAE3D,OAAO;YACL,QAAQ,EAAE,MAAA,MAAM,CAAC,QAAQ,mCAAI,QAAQ;YACrC,OAAO,EAAE,MAAM,CAAC,OAAO;SACxB,CAAC;IACJ,CAAC;CAAA;AAED,6DAA6D;AAC7D,MAAM,UAAgB,yBAAyB,CAAC,IAG/C;;QACC,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAClF,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC;CAAA","sourcesContent":["/**\n * Prepares an à la carte checkout and places the order via tCheckoutPlaceOrder.\n *\n * Sequence:\n * 1. tCheckoutUpdate(termsAccepted, acceptPrice?)\n * 2. tCheckoutPlaceOrder(biId, billing address) → Stripe client secret\n * 3. stripe.confirmCardPayment(clientSecret, { payment_method: { card, billing_details } })\n * 4. caller polls tPlacedOrder(biId) for up to 30s while PAYMENT_PENDING\n *\n * Do not call platform BI transitions (Sanction_Check / CART_PAYMENT_PENDING)\n * directly — tCheckoutPlaceOrder handles sanctions + payment start.\n */\nimport type {\n AlacarteBillingAddress, AlacarteCheckout, AlacarteCheckoutResult,\n AlacartePlaceOrderApiResult, AlacartePlaceOrderPayment,\n} from '../types/alacarte';\nimport { toPlaceOrderError } from './placeOrderErrors';\n\ntype UpdateCheckoutFn = (args: {\n biId?: string,\n couponCode?: string,\n termsAccepted?: boolean,\n acceptPrice?: boolean,\n}) => Promise<AlacarteCheckoutResult>;\n\n/**\n * Calls tCheckoutUpdate with termsAccepted (and acceptPrice when the cart\n * still needs price acceptance). Returns the updated checkout.\n */\nexport async function prepareAlacartePayment(\n updateCheckout: UpdateCheckoutFn,\n checkout: AlacarteCheckout,\n): Promise<AlacarteCheckout> {\n const result = await updateCheckout({\n biId: checkout.biId,\n termsAccepted: true,\n ...(checkout.priceAccepted ? {} : { acceptPrice: true }),\n });\n\n if (!result.ok || !result.checkout) {\n throw new Error(result.error ?? 'Could not prepare checkout for payment.');\n }\n\n if (!result.checkout.priceAccepted) {\n throw new Error('Price must be accepted before payment.');\n }\n\n return result.checkout;\n}\n\nexport interface AlacartePlaceOrderResult {\n checkout: AlacarteCheckout,\n payment: AlacartePlaceOrderPayment,\n}\n\n/**\n * Maps form BillingInfo onto tCheckoutPlaceOrder billing.\n * Only country is required by the API; other fields are included when present.\n */\nexport function toAlacarteBillingAddress(billing: {\n country: string,\n firstName?: string,\n lastName?: string,\n line1?: string,\n city?: string,\n state?: string,\n zip?: string,\n}): AlacarteBillingAddress {\n return {\n country: billing.country.trim().toUpperCase(),\n firstName: billing.firstName,\n lastName: billing.lastName,\n line1: billing.line1,\n city: billing.city,\n state: billing.state,\n postcode: billing.zip,\n };\n}\n\n/**\n * Full place-order sequence:\n * prep → tCheckoutPlaceOrder → Stripe confirmCardPayment\n *\n * Caller then sets ?order_bi_id= and polls tPlacedOrder.\n */\nexport async function placeAlacarteOrder(args: {\n updateCheckout: UpdateCheckoutFn,\n placeCheckoutOrder: (\n biId: string,\n billing?: AlacarteBillingAddress,\n ) => Promise<AlacartePlaceOrderApiResult>,\n checkout: AlacarteCheckout,\n billing: AlacarteBillingAddress,\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n /**\n * Set when retrying after Stripe.js rejected the card client-side\n * (CardValidationError). tCheckoutPlaceOrder already ran and locked the\n * checkout into CART_PAYMENT_PENDING, so re-running tCheckoutUpdate here\n * would fail with CHECKOUT_NOT_OPEN. Skip straight to tCheckoutPlaceOrder.\n */\n skipPrepare?: boolean,\n}): Promise<AlacartePlaceOrderResult> {\n const checkout = args.skipPrepare\n ? args.checkout\n : await prepareAlacartePayment(args.updateCheckout, args.checkout);\n\n if (!checkout.priceAccepted) {\n throw new Error('Price must be accepted before payment.');\n }\n\n if (!args.billing.country) {\n throw new Error('Please select a country.');\n }\n\n const placed = await args.placeCheckoutOrder(checkout.biId, args.billing);\n\n if (!placed.ok || !placed.payment?.clientSecret) {\n throw toPlaceOrderError(\n placed.error ?? 'PAYMENT_NOT_STARTED',\n placed.checkout ?? null,\n );\n }\n\n await args.confirmCardPayment(placed.payment.clientSecret);\n\n return {\n checkout: placed.checkout ?? checkout,\n payment: placed.payment,\n };\n}\n\n/** Prep only — used by tests that assert the update step. */\nexport async function prepareAlacartePlaceOrder(args: {\n updateCheckout: UpdateCheckoutFn,\n checkout: AlacarteCheckout,\n}): Promise<{ checkout: AlacarteCheckout }> {\n const checkout = await prepareAlacartePayment(args.updateCheckout, args.checkout);\n return { checkout };\n}\n"]}
|