@edx/frontend-app-alacarte-checkout 1.0.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/LICENSE +661 -0
- package/README.md +2 -0
- package/README.rst +235 -0
- package/dist/Main.d.ts +3 -0
- package/dist/Main.js +15 -0
- package/dist/Main.js.map +1 -0
- package/dist/app.d.ts +3 -0
- package/dist/app.js +12 -0
- package/dist/app.js.map +1 -0
- package/dist/checkoutPage/CheckoutPage.d.ts +3 -0
- package/dist/checkoutPage/CheckoutPage.js +26 -0
- package/dist/checkoutPage/CheckoutPage.js.map +1 -0
- package/dist/components/BrandMarks/BrandMarks.d.ts +7 -0
- package/dist/components/BrandMarks/BrandMarks.js +9 -0
- package/dist/components/BrandMarks/BrandMarks.js.map +1 -0
- package/dist/components/CartSummary/CartSummary.d.ts +26 -0
- package/dist/components/CartSummary/CartSummary.js +41 -0
- package/dist/components/CartSummary/CartSummary.js.map +1 -0
- package/dist/components/CartSummary/Messages.d.ts +103 -0
- package/dist/components/CartSummary/Messages.js +106 -0
- package/dist/components/CartSummary/Messages.js.map +1 -0
- package/dist/components/Checkout/Checkout.d.ts +9 -0
- package/dist/components/Checkout/Checkout.js +82 -0
- package/dist/components/Checkout/Checkout.js.map +1 -0
- package/dist/components/Checkout/Messages.d.ts +8 -0
- package/dist/components/Checkout/Messages.js +10 -0
- package/dist/components/Checkout/Messages.js.map +1 -0
- package/dist/components/CheckoutGate/CheckoutGate.d.ts +9 -0
- package/dist/components/CheckoutGate/CheckoutGate.js +40 -0
- package/dist/components/CheckoutGate/CheckoutGate.js.map +1 -0
- package/dist/components/CheckoutGate/Messages.d.ts +8 -0
- package/dist/components/CheckoutGate/Messages.js +10 -0
- package/dist/components/CheckoutGate/Messages.js.map +1 -0
- package/dist/components/CountryUnavailableModal/CountryUnavailableModal.d.ts +9 -0
- package/dist/components/CountryUnavailableModal/CountryUnavailableModal.js +10 -0
- package/dist/components/CountryUnavailableModal/CountryUnavailableModal.js.map +1 -0
- package/dist/components/CountryUnavailableModal/Messages.d.ts +18 -0
- package/dist/components/CountryUnavailableModal/Messages.js +20 -0
- package/dist/components/CountryUnavailableModal/Messages.js.map +1 -0
- package/dist/components/CourseEnrollmentCard/CourseEnrollmentCard.d.ts +9 -0
- package/dist/components/CourseEnrollmentCard/CourseEnrollmentCard.js +31 -0
- package/dist/components/CourseEnrollmentCard/CourseEnrollmentCard.js.map +1 -0
- package/dist/components/CourseEnrollmentCard/Messages.d.ts +33 -0
- package/dist/components/CourseEnrollmentCard/Messages.js +35 -0
- package/dist/components/CourseEnrollmentCard/Messages.js.map +1 -0
- package/dist/components/EdxUnlimitedBanner/EdxUnlimitedBanner.d.ts +13 -0
- package/dist/components/EdxUnlimitedBanner/EdxUnlimitedBanner.js +11 -0
- package/dist/components/EdxUnlimitedBanner/EdxUnlimitedBanner.js.map +1 -0
- package/dist/components/EnrollmentSummary/EnrollmentSummary.d.ts +22 -0
- package/dist/components/EnrollmentSummary/EnrollmentSummary.js +33 -0
- package/dist/components/EnrollmentSummary/EnrollmentSummary.js.map +1 -0
- package/dist/components/EnrollmentSummary/Messages.d.ts +48 -0
- package/dist/components/EnrollmentSummary/Messages.js +50 -0
- package/dist/components/EnrollmentSummary/Messages.js.map +1 -0
- package/dist/components/PaymentFields/PaymentFields.d.ts +9 -0
- package/dist/components/PaymentFields/PaymentFields.js +7 -0
- package/dist/components/PaymentFields/PaymentFields.js.map +1 -0
- package/dist/components/PaymentForm/Messages.d.ts +118 -0
- package/dist/components/PaymentForm/Messages.js +125 -0
- package/dist/components/PaymentForm/Messages.js.map +1 -0
- package/dist/components/PaymentForm/PaymentForm.d.ts +28 -0
- package/dist/components/PaymentForm/PaymentForm.js +121 -0
- package/dist/components/PaymentForm/PaymentForm.js.map +1 -0
- package/dist/components/StatusScreens/Messages.d.ts +108 -0
- package/dist/components/StatusScreens/Messages.js +114 -0
- package/dist/components/StatusScreens/Messages.js.map +1 -0
- package/dist/components/StatusScreens/StatusScreens.d.ts +24 -0
- package/dist/components/StatusScreens/StatusScreens.js +32 -0
- package/dist/components/StatusScreens/StatusScreens.js.map +1 -0
- package/dist/constants.d.ts +59 -0
- package/dist/constants.js +282 -0
- package/dist/constants.js.map +1 -0
- package/dist/data/hooks/keys.d.ts +14 -0
- package/dist/data/hooks/keys.js +15 -0
- package/dist/data/hooks/keys.js.map +1 -0
- package/dist/data/hooks/useCheckout.d.ts +61 -0
- package/dist/data/hooks/useCheckout.js +343 -0
- package/dist/data/hooks/useCheckout.js.map +1 -0
- package/dist/data/hooks/useCheckoutGate.d.ts +16 -0
- package/dist/data/hooks/useCheckoutGate.js +61 -0
- package/dist/data/hooks/useCheckoutGate.js.map +1 -0
- package/dist/data/hooks/useCheckoutMutations.d.ts +22 -0
- package/dist/data/hooks/useCheckoutMutations.js +166 -0
- package/dist/data/hooks/useCheckoutMutations.js.map +1 -0
- package/dist/data/hooks/useCheckoutQueries.d.ts +32 -0
- package/dist/data/hooks/useCheckoutQueries.js +107 -0
- package/dist/data/hooks/useCheckoutQueries.js.map +1 -0
- package/dist/data/services/subs/urls.d.ts +2 -0
- package/dist/data/services/subs/urls.js +4 -0
- package/dist/data/services/subs/urls.js.map +1 -0
- package/dist/i18n/index.d.ts +1 -0
- package/dist/i18n/index.js +2 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/messages.d.ts +8 -0
- package/dist/messages.js +10 -0
- package/dist/messages.js.map +1 -0
- package/dist/routes.d.ts +18 -0
- package/dist/routes.js +47 -0
- package/dist/routes.js.map +1 -0
- package/dist/slots/ExampleSlot/ExampleSlot.d.ts +3 -0
- package/dist/slots/ExampleSlot/ExampleSlot.js +6 -0
- package/dist/slots/ExampleSlot/ExampleSlot.js.map +1 -0
- package/dist/slots.d.ts +3 -0
- package/dist/slots.js +3 -0
- package/dist/slots.js.map +1 -0
- package/dist/style.scss +14 -0
- package/dist/styles/components/brandMarks.scss +41 -0
- package/dist/styles/components/cartSummary.scss +29 -0
- package/dist/styles/components/checkout.scss +29 -0
- package/dist/styles/components/checkoutStepper.scss +9 -0
- package/dist/styles/components/courseEnrollmentCard.scss +38 -0
- package/dist/styles/components/edxUnlimitedBanner.scss +138 -0
- package/dist/styles/components/enrollmentSummary.scss +19 -0
- package/dist/styles/components/paymentFields.scss +15 -0
- package/dist/styles/components/paymentForm.scss +139 -0
- package/dist/styles/components/statusScreens.scss +39 -0
- package/dist/styles/components/successScreen.scss +0 -0
- package/dist/types.d.ts +232 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/auth.d.ts +7 -0
- package/dist/utils/auth.js +28 -0
- package/dist/utils/auth.js.map +1 -0
- package/dist/utils/billingDetails.d.ts +30 -0
- package/dist/utils/billingDetails.js +48 -0
- package/dist/utils/billingDetails.js.map +1 -0
- package/dist/utils/bootstrapApi.d.ts +20 -0
- package/dist/utils/bootstrapApi.js +142 -0
- package/dist/utils/bootstrapApi.js.map +1 -0
- package/dist/utils/cartApi.d.ts +16 -0
- package/dist/utils/cartApi.js +133 -0
- package/dist/utils/cartApi.js.map +1 -0
- package/dist/utils/checkout.d.ts +6 -0
- package/dist/utils/checkout.js +28 -0
- package/dist/utils/checkout.js.map +1 -0
- package/dist/utils/checkoutErrors.d.ts +1 -0
- package/dist/utils/checkoutErrors.js +11 -0
- package/dist/utils/checkoutErrors.js.map +1 -0
- package/dist/utils/fetchActiveSubscriptions.d.ts +4 -0
- package/dist/utils/fetchActiveSubscriptions.js +36 -0
- package/dist/utils/fetchActiveSubscriptions.js.map +1 -0
- package/dist/utils/fetchBillingPlans.d.ts +2 -0
- package/dist/utils/fetchBillingPlans.js +48 -0
- package/dist/utils/fetchBillingPlans.js.map +1 -0
- package/dist/utils/graphqlClient.d.ts +16 -0
- package/dist/utils/graphqlClient.js +39 -0
- package/dist/utils/graphqlClient.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.js +17 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/planQuery.d.ts +16 -0
- package/dist/utils/planQuery.js +57 -0
- package/dist/utils/planQuery.js.map +1 -0
- package/dist/utils/provision.d.ts +48 -0
- package/dist/utils/provision.js +161 -0
- package/dist/utils/provision.js.map +1 -0
- package/dist/utils/queries.d.ts +35 -0
- package/dist/utils/queries.js +168 -0
- package/dist/utils/queries.js.map +1 -0
- package/dist/utils/stripe.d.ts +23 -0
- package/dist/utils/stripe.js +86 -0
- package/dist/utils/stripe.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* query/useCheckoutMutations.ts — write-side TanStack mutations.
|
|
12
|
+
*
|
|
13
|
+
* The placeOrder mutation drives the checkout sequence on button click.
|
|
14
|
+
* The viax token and customerUid are established on page load via
|
|
15
|
+
* GET /edx-session and passed in through sessionRef.
|
|
16
|
+
*
|
|
17
|
+
* Card:
|
|
18
|
+
* 1. getSalableUid + getBillingPlans (parallel)
|
|
19
|
+
* 2. createTwouCart
|
|
20
|
+
* 3. stripe.createPaymentMethod → pm_xxx
|
|
21
|
+
* 4. updateTwouCartItem → billing address + pm_xxx
|
|
22
|
+
* 5. updateTwouCart → consent fields (SUBS-228)
|
|
23
|
+
* 6. startBusinessProcessableTransitionV2('Sanction_Check')
|
|
24
|
+
*
|
|
25
|
+
* PayPal (mirrors the card sequence, skipping card tokenization):
|
|
26
|
+
* 1. getSalableUid + getBillingPlans (parallel)
|
|
27
|
+
* 2. createTwouCart
|
|
28
|
+
* 3. updateTwouCartItem → billing address + TwouPaypalPayment
|
|
29
|
+
* 4. updateTwouCart → consent fields + return URL (SUBS-228)
|
|
30
|
+
* 5. startBusinessProcessableTransitionV2('Sanction_Check')
|
|
31
|
+
* The backend then drives bpStatus to STRIPE_CONFIRMATION_REQUIRED and
|
|
32
|
+
* populates tPaypalApprovalUrl, which useCheckout redirects to as the
|
|
33
|
+
* hosted confirmation step for PayPal.
|
|
34
|
+
*/
|
|
35
|
+
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
|
36
|
+
import { STATUS } from '../../constants';
|
|
37
|
+
import { checkoutKeys } from './keys';
|
|
38
|
+
import { getPlanPrice } from '../../utils/billingDetails';
|
|
39
|
+
import { buildStripeReturnUrl } from '../../utils/stripe';
|
|
40
|
+
class CardValidationError extends Error {
|
|
41
|
+
}
|
|
42
|
+
function toCartAddress(b) {
|
|
43
|
+
return {
|
|
44
|
+
tFirstName: b.firstName,
|
|
45
|
+
tLastName: b.lastName,
|
|
46
|
+
upaLine1: b.line1,
|
|
47
|
+
upaCity: b.city,
|
|
48
|
+
upaPostcode: b.zip,
|
|
49
|
+
gadStateOrProvince: b.state,
|
|
50
|
+
gadCountry: { iso2Code: b.country },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function toStripeAddress(b) {
|
|
54
|
+
return {
|
|
55
|
+
line1: b.line1 || undefined,
|
|
56
|
+
city: b.city || undefined,
|
|
57
|
+
state: b.state || undefined,
|
|
58
|
+
postal_code: b.zip || undefined,
|
|
59
|
+
country: b.country || undefined,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function useCheckoutMutations(ctx) {
|
|
63
|
+
const queryClient = useQueryClient();
|
|
64
|
+
const optimisticConfirm = (cartBiId) => {
|
|
65
|
+
queryClient.setQueryData(checkoutKeys.status(cartBiId), (old) => {
|
|
66
|
+
var _a, _b, _c, _d;
|
|
67
|
+
console.log('old optimisticConfirm', old);
|
|
68
|
+
return {
|
|
69
|
+
status: STATUS.CONFIRM,
|
|
70
|
+
stripeSubscriptionId: (_a = old === null || old === void 0 ? void 0 : old.stripeSubscriptionId) !== null && _a !== void 0 ? _a : null,
|
|
71
|
+
stripeConfirmationRedirectUrl: (_b = old === null || old === void 0 ? void 0 : old.stripeConfirmationRedirectUrl) !== null && _b !== void 0 ? _b : null,
|
|
72
|
+
paypalApprovalUrl: (_c = old === null || old === void 0 ? void 0 : old.paypalApprovalUrl) !== null && _c !== void 0 ? _c : null,
|
|
73
|
+
lastComment: (_d = old === null || old === void 0 ? void 0 : old.lastComment) !== null && _d !== void 0 ? _d : '',
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
queryClient.invalidateQueries({ queryKey: checkoutKeys.status(cartBiId) });
|
|
77
|
+
};
|
|
78
|
+
const placeOrder = useMutation({
|
|
79
|
+
mutationFn: (_a) => __awaiter(this, [_a], void 0, function* ({ consent, paymentMethod }) {
|
|
80
|
+
var _b, _c;
|
|
81
|
+
const { client, billingRef } = ctx;
|
|
82
|
+
if (!client)
|
|
83
|
+
throw new Error('Checkout is not ready yet.');
|
|
84
|
+
// Google Pay is not currently supported.
|
|
85
|
+
// if (paymentMethod === 'googlepay') {
|
|
86
|
+
// throw new Error('Google Pay is not supported yet. Please choose Card or PayPal.');
|
|
87
|
+
// }
|
|
88
|
+
const billing = billingRef.current;
|
|
89
|
+
if (!billing)
|
|
90
|
+
throw new Error('Billing information is not available. Please refresh and try again.');
|
|
91
|
+
const session = ctx.sessionRef.current;
|
|
92
|
+
if (!session) {
|
|
93
|
+
throw new Error('Checkout session is not ready yet. Please wait a moment and try again.');
|
|
94
|
+
}
|
|
95
|
+
const { customerUid } = session;
|
|
96
|
+
// ── Step 1: Fetch salable(product) uid + billing plans in parallel ─────────────
|
|
97
|
+
const [productInfo, plansResult] = yield Promise.all([
|
|
98
|
+
client.getSalableUid('TWOU-SUB-PLAN-001'),
|
|
99
|
+
client.getBillingPlans(),
|
|
100
|
+
]);
|
|
101
|
+
const salableUid = productInfo.uid;
|
|
102
|
+
const selectedCycle = (_b = ctx.selectedCycleRef.current) !== null && _b !== void 0 ? _b : 'monthly';
|
|
103
|
+
const selectedPlan = getPlanPrice(plansResult.plans, selectedCycle);
|
|
104
|
+
if (!selectedPlan) {
|
|
105
|
+
throw new Error(`No billing plan found for ${selectedCycle} billing.`);
|
|
106
|
+
}
|
|
107
|
+
const billingPlanUid = selectedPlan.uid;
|
|
108
|
+
// ── Step 2: Create the cart ───────────────────────────────────────────
|
|
109
|
+
const createdCart = yield client.createCart({ customerUid, salableUid, billingPlanUid });
|
|
110
|
+
ctx.onCartCreated(createdCart);
|
|
111
|
+
// ── Step 3: Attach billing address + payment method to cart item ──────
|
|
112
|
+
const { cartItemUid } = yield client.getCartItem(createdCart.biId);
|
|
113
|
+
if (paymentMethod === 'paypal') {
|
|
114
|
+
// No card tokenization — PayPal issues a hosted approval redirect instead.
|
|
115
|
+
yield client.updateCartItem({
|
|
116
|
+
cartItemUid,
|
|
117
|
+
billingAddress: toCartAddress(billing),
|
|
118
|
+
paypalEmail: (_c = session.email) !== null && _c !== void 0 ? _c : null,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
const stripe = ctx.stripeRef.current;
|
|
123
|
+
const card = ctx.cardNumberElRef.current;
|
|
124
|
+
if (!stripe || !card)
|
|
125
|
+
throw new Error('Checkout is not ready yet.');
|
|
126
|
+
// Tokenize the card (browser → Stripe).
|
|
127
|
+
const { paymentMethod: stripePaymentMethod, error } = yield stripe.createPaymentMethod({
|
|
128
|
+
type: 'card',
|
|
129
|
+
card,
|
|
130
|
+
billing_details: {
|
|
131
|
+
name: `${billing.firstName} ${billing.lastName}`.trim(),
|
|
132
|
+
address: toStripeAddress(billing),
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
if (error || !stripePaymentMethod) {
|
|
136
|
+
throw new CardValidationError((error === null || error === void 0 ? void 0 : error.message) || 'There was a problem with your card.');
|
|
137
|
+
}
|
|
138
|
+
yield client.updateCartItem({
|
|
139
|
+
cartItemUid,
|
|
140
|
+
billingAddress: toCartAddress(billing),
|
|
141
|
+
paymentMethodId: stripePaymentMethod.id,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
// ── Step 4: Persist consent + hosted-payment return URL on the TwouCart BI ────
|
|
145
|
+
yield client.updateCartConsent({
|
|
146
|
+
cartUid: createdCart.uid,
|
|
147
|
+
consent,
|
|
148
|
+
returnUrl: buildStripeReturnUrl(createdCart.biId),
|
|
149
|
+
});
|
|
150
|
+
// ── Step 5: Fire the backend workflow ─────────────────────────────────
|
|
151
|
+
yield client.placeOrder(createdCart.uid);
|
|
152
|
+
return createdCart;
|
|
153
|
+
}),
|
|
154
|
+
onSuccess: (cart) => {
|
|
155
|
+
optimisticConfirm(cart.biId);
|
|
156
|
+
},
|
|
157
|
+
onError: (err) => {
|
|
158
|
+
if (err instanceof CardValidationError)
|
|
159
|
+
ctx.setCardError(err.message);
|
|
160
|
+
else
|
|
161
|
+
ctx.setError(err.message);
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
return { placeOrder };
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=useCheckoutMutations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCheckoutMutations.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutMutations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * query/useCheckoutMutations.ts — write-side TanStack mutations.\n *\n * The placeOrder mutation drives the checkout sequence on button click.\n * The viax token and customerUid are established on page load via\n * GET /edx-session and passed in through sessionRef.\n *\n * Card:\n * 1. getSalableUid + getBillingPlans (parallel)\n * 2. createTwouCart\n * 3. stripe.createPaymentMethod → pm_xxx\n * 4. updateTwouCartItem → billing address + pm_xxx\n * 5. updateTwouCart → consent fields (SUBS-228)\n * 6. startBusinessProcessableTransitionV2('Sanction_Check')\n *\n * PayPal (mirrors the card sequence, skipping card tokenization):\n * 1. getSalableUid + getBillingPlans (parallel)\n * 2. createTwouCart\n * 3. updateTwouCartItem → billing address + TwouPaypalPayment\n * 4. updateTwouCart → consent fields + return URL (SUBS-228)\n * 5. startBusinessProcessableTransitionV2('Sanction_Check')\n * The backend then drives bpStatus to STRIPE_CONFIRMATION_REQUIRED and\n * populates tPaypalApprovalUrl, which useCheckout redirects to as the\n * hosted confirmation step for PayPal.\n */\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport type { RefObject } from 'react';\nimport { STATUS } from '../../constants';\nimport { checkoutKeys } from './keys';\nimport type { EdxSessionResult } from '../../utils/provision';\nimport { getPlanPrice } from '../../utils/billingDetails';\nimport { buildStripeReturnUrl } from '../../utils/stripe';\nimport type {\n CheckoutClient, StripeLike, StripeElementLike, Cart, BillingInfo,\n CartBillingAddress, CartStatusResult, CheckoutConsent, BillingCycle, SupportedPaymentMethod,\n} from '../../types';\n\nclass CardValidationError extends Error {}\n\nfunction toCartAddress(b: BillingInfo): CartBillingAddress {\n return {\n tFirstName: b.firstName,\n tLastName: b.lastName,\n upaLine1: b.line1,\n upaCity: b.city,\n upaPostcode: b.zip,\n gadStateOrProvince: b.state,\n gadCountry: { iso2Code: b.country },\n };\n}\n\nfunction toStripeAddress(b: BillingInfo): Record<string, string | undefined> {\n return {\n line1: b.line1 || undefined,\n city: b.city || undefined,\n state: b.state || undefined,\n postal_code: b.zip || undefined,\n country: b.country || undefined,\n };\n}\n\nexport interface CheckoutMutationsCtx {\n client: CheckoutClient | null,\n sessionRef: RefObject<EdxSessionResult | null>,\n stripeRef: RefObject<StripeLike | null>,\n cardNumberElRef: RefObject<StripeElementLike | null>,\n billingRef: RefObject<BillingInfo | null>,\n /** Selected monthly/annual cycle at Place order time. */\n selectedCycleRef: RefObject<BillingCycle>,\n setError: (msg: string) => void,\n setCardError: (msg: string) => void,\n onCartCreated: (cart: Cart) => void,\n}\n\nexport interface PlaceOrderArgs {\n consent: CheckoutConsent,\n paymentMethod: SupportedPaymentMethod,\n}\n\nexport function useCheckoutMutations(ctx: CheckoutMutationsCtx) {\n const queryClient = useQueryClient();\n\n const optimisticConfirm = (cartBiId: string) => {\n queryClient.setQueryData<CartStatusResult>(checkoutKeys.status(cartBiId), (old) => {\n console.log('old optimisticConfirm', old);\n return {\n status: STATUS.CONFIRM,\n stripeSubscriptionId: old?.stripeSubscriptionId ?? null,\n stripeConfirmationRedirectUrl: old?.stripeConfirmationRedirectUrl ?? null,\n paypalApprovalUrl: old?.paypalApprovalUrl ?? null,\n lastComment: old?.lastComment ?? '',\n };\n });\n queryClient.invalidateQueries({ queryKey: checkoutKeys.status(cartBiId) });\n };\n\n const placeOrder = useMutation<Cart, Error, PlaceOrderArgs>({\n mutationFn: async ({ consent, paymentMethod }) => {\n const { client, billingRef } = ctx;\n if (!client) throw new Error('Checkout is not ready yet.');\n\n // Google Pay is not currently supported.\n // if (paymentMethod === 'googlepay') {\n // throw new Error('Google Pay is not supported yet. Please choose Card or PayPal.');\n // }\n\n const billing = billingRef.current;\n if (!billing) throw new Error('Billing information is not available. Please refresh and try again.');\n\n const session = ctx.sessionRef.current;\n if (!session) {\n throw new Error('Checkout session is not ready yet. Please wait a moment and try again.');\n }\n const { customerUid } = session;\n\n // ── Step 1: Fetch salable(product) uid + billing plans in parallel ─────────────\n const [productInfo, plansResult] = await Promise.all([\n client.getSalableUid('TWOU-SUB-PLAN-001'),\n client.getBillingPlans(),\n ]);\n const salableUid = productInfo.uid;\n const selectedCycle = ctx.selectedCycleRef.current ?? 'monthly';\n const selectedPlan = getPlanPrice(plansResult.plans, selectedCycle);\n if (!selectedPlan) {\n throw new Error(`No billing plan found for ${selectedCycle} billing.`);\n }\n const billingPlanUid = selectedPlan.uid;\n\n // ── Step 2: Create the cart ───────────────────────────────────────────\n const createdCart = await client.createCart({ customerUid, salableUid, billingPlanUid });\n ctx.onCartCreated(createdCart);\n\n // ── Step 3: Attach billing address + payment method to cart item ──────\n const { cartItemUid } = await client.getCartItem(createdCart.biId);\n\n if (paymentMethod === 'paypal') {\n // No card tokenization — PayPal issues a hosted approval redirect instead.\n await client.updateCartItem({\n cartItemUid,\n billingAddress: toCartAddress(billing),\n paypalEmail: session.email ?? null,\n });\n } else {\n const stripe = ctx.stripeRef.current;\n const card = ctx.cardNumberElRef.current;\n if (!stripe || !card) throw new Error('Checkout is not ready yet.');\n\n // Tokenize the card (browser → Stripe).\n const { paymentMethod: stripePaymentMethod, error } = await stripe.createPaymentMethod({\n type: 'card',\n card,\n billing_details: {\n name: `${billing.firstName} ${billing.lastName}`.trim(),\n address: toStripeAddress(billing),\n },\n });\n if (error || !stripePaymentMethod) {\n throw new CardValidationError(error?.message || 'There was a problem with your card.');\n }\n\n await client.updateCartItem({\n cartItemUid,\n billingAddress: toCartAddress(billing),\n paymentMethodId: stripePaymentMethod.id,\n });\n }\n\n // ── Step 4: Persist consent + hosted-payment return URL on the TwouCart BI ────\n await client.updateCartConsent({\n cartUid: createdCart.uid,\n consent,\n returnUrl: buildStripeReturnUrl(createdCart.biId),\n });\n\n // ── Step 5: Fire the backend workflow ─────────────────────────────────\n await client.placeOrder(createdCart.uid);\n\n return createdCart;\n },\n onSuccess: (cart) => {\n optimisticConfirm(cart.biId);\n },\n onError: (err) => {\n if (err instanceof CardValidationError) ctx.setCardError(err.message);\n else ctx.setError(err.message);\n },\n });\n\n return { placeOrder };\n}\n"]}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { CheckoutClient, CartStatusResult, BillingPlansResult, CourseDetail } from '../../types';
|
|
2
|
+
/** Exchange edX session cookies for a viax token on page load. */
|
|
3
|
+
export declare function useViaxSessionQuery(sessionUrl: string | null): import("@tanstack/react-query").UseQueryResult<import("../../utils/provision").EdxSessionResult, Error>;
|
|
4
|
+
/**
|
|
5
|
+
* Fetches billing plans after the viax session is established.
|
|
6
|
+
* Used to display prices in CartSummary before the user starts checkout.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useBillingPlansQuery(client: CheckoutClient | null, sessionReady: boolean): import("@tanstack/react-query").UseQueryResult<BillingPlansResult, Error>;
|
|
9
|
+
/**
|
|
10
|
+
* Fetches the live active subscriber count after the viax session is ready.
|
|
11
|
+
* Refreshes every 5 minutes so the number stays roughly current.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useActiveSubscriptionsQuery(client: CheckoutClient | null, sessionReady: boolean): import("@tanstack/react-query").UseQueryResult<{
|
|
14
|
+
active: number;
|
|
15
|
+
}, Error>;
|
|
16
|
+
/** Checks whether the authenticated learner already has an active subscription. */
|
|
17
|
+
export declare function useCurrentUserSubscriberQuery(client: CheckoutClient | null, sessionReady: boolean): import("@tanstack/react-query").UseQueryResult<import("../../types").CurrentUserSubscriberResult | null, Error>;
|
|
18
|
+
/**
|
|
19
|
+
* Fetches the user's country from their edX account profile.
|
|
20
|
+
* Derives the LMS base URL from the session URL (same origin).
|
|
21
|
+
* Returns null if the fetch fails or the field is absent — checkout stays open in that case.
|
|
22
|
+
*/
|
|
23
|
+
export declare function useUserCountryQuery(sessionUrl: string | null, username: string | null): import("@tanstack/react-query").UseQueryResult<string | null, Error>;
|
|
24
|
+
/** Polls the cart state machine every second; stops on terminal states. */
|
|
25
|
+
export declare function useCartStatusQuery(client: CheckoutClient | null, cartBiId: string | null): import("@tanstack/react-query").UseQueryResult<CartStatusResult, Error>;
|
|
26
|
+
/** Looks up the created subscription biId after success. */
|
|
27
|
+
export declare function useSubscriptionIdQuery(client: CheckoutClient | null, cartBiId: string | null, enabled: boolean): import("@tanstack/react-query").UseQueryResult<import("../../types").SubscriptionDetails | null, Error>;
|
|
28
|
+
/**
|
|
29
|
+
* Fetches course details when the user arrived via ?course_run_key=.
|
|
30
|
+
* Skipped entirely when no course key is present (standard checkout).
|
|
31
|
+
*/
|
|
32
|
+
export declare function useCourseDetailQuery(client: CheckoutClient | null, sessionReady: boolean, courseRunKey: string | null): import("@tanstack/react-query").UseQueryResult<CourseDetail | null, Error>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* query/useCheckoutQueries.ts — read-side TanStack Query hooks.
|
|
3
|
+
*
|
|
4
|
+
* useViaxSessionQuery runs on mount to exchange the edX session for a viax
|
|
5
|
+
* token. Billing plans and subscriber count wait until the session is ready.
|
|
6
|
+
*/
|
|
7
|
+
import { useQuery } from '@tanstack/react-query';
|
|
8
|
+
import { isTerminalStatus } from '../../constants';
|
|
9
|
+
import { checkoutKeys } from './keys';
|
|
10
|
+
import { fetchEdxSession, fetchUserCountry } from '../../utils/provision';
|
|
11
|
+
/** Exchange edX session cookies for a viax token on page load. */
|
|
12
|
+
export function useViaxSessionQuery(sessionUrl) {
|
|
13
|
+
return useQuery({
|
|
14
|
+
queryKey: checkoutKeys.viaxSession(),
|
|
15
|
+
enabled: !!sessionUrl,
|
|
16
|
+
queryFn: () => fetchEdxSession(sessionUrl),
|
|
17
|
+
staleTime: 5 * 60 * 1000,
|
|
18
|
+
retry: 1,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Fetches billing plans after the viax session is established.
|
|
23
|
+
* Used to display prices in CartSummary before the user starts checkout.
|
|
24
|
+
*/
|
|
25
|
+
export function useBillingPlansQuery(client, sessionReady) {
|
|
26
|
+
return useQuery({
|
|
27
|
+
queryKey: checkoutKeys.billingPlans(),
|
|
28
|
+
enabled: !!client && sessionReady,
|
|
29
|
+
queryFn: () => client.getBillingPlans(),
|
|
30
|
+
staleTime: Infinity,
|
|
31
|
+
retry: 2,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Fetches the live active subscriber count after the viax session is ready.
|
|
36
|
+
* Refreshes every 5 minutes so the number stays roughly current.
|
|
37
|
+
*/
|
|
38
|
+
export function useActiveSubscriptionsQuery(client, sessionReady) {
|
|
39
|
+
return useQuery({
|
|
40
|
+
queryKey: ['checkout', 'activeSubscriptions'],
|
|
41
|
+
enabled: !!client && sessionReady,
|
|
42
|
+
queryFn: () => client.getActiveSubscriptions(),
|
|
43
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
44
|
+
retry: 1,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/** Checks whether the authenticated learner already has an active subscription. */
|
|
48
|
+
export function useCurrentUserSubscriberQuery(client, sessionReady) {
|
|
49
|
+
return useQuery({
|
|
50
|
+
queryKey: ['checkout', 'currentUserSubscriber'],
|
|
51
|
+
enabled: !!client && sessionReady,
|
|
52
|
+
queryFn: () => client.isCurrentUserSubscriber(),
|
|
53
|
+
staleTime: 5 * 60 * 1000,
|
|
54
|
+
retry: 1,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Fetches the user's country from their edX account profile.
|
|
59
|
+
* Derives the LMS base URL from the session URL (same origin).
|
|
60
|
+
* Returns null if the fetch fails or the field is absent — checkout stays open in that case.
|
|
61
|
+
*/
|
|
62
|
+
export function useUserCountryQuery(sessionUrl, username) {
|
|
63
|
+
return useQuery({
|
|
64
|
+
queryKey: ['checkout', 'userCountry', username],
|
|
65
|
+
enabled: !!sessionUrl && !!username,
|
|
66
|
+
queryFn: () => {
|
|
67
|
+
const lmsBaseUrl = new URL(sessionUrl).origin;
|
|
68
|
+
return fetchUserCountry(lmsBaseUrl, username);
|
|
69
|
+
},
|
|
70
|
+
staleTime: Infinity,
|
|
71
|
+
retry: 1,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Polls the cart state machine every second; stops on terminal states. */
|
|
75
|
+
export function useCartStatusQuery(client, cartBiId) {
|
|
76
|
+
return useQuery({
|
|
77
|
+
queryKey: checkoutKeys.status(cartBiId !== null && cartBiId !== void 0 ? cartBiId : 'none'),
|
|
78
|
+
enabled: !!client && !!cartBiId,
|
|
79
|
+
queryFn: () => client.getCartStatus(cartBiId),
|
|
80
|
+
refetchInterval: (query) => { var _a, _b; return isTerminalStatus((_b = (_a = query.state.data) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : null) ? false : 1000; },
|
|
81
|
+
refetchOnWindowFocus: false,
|
|
82
|
+
gcTime: 0,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/** Looks up the created subscription biId after success. */
|
|
86
|
+
export function useSubscriptionIdQuery(client, cartBiId, enabled) {
|
|
87
|
+
return useQuery({
|
|
88
|
+
queryKey: checkoutKeys.subscriptionId(cartBiId !== null && cartBiId !== void 0 ? cartBiId : 'none'),
|
|
89
|
+
enabled: enabled && !!client && !!cartBiId,
|
|
90
|
+
queryFn: () => client.findSubscriptionBiId(cartBiId),
|
|
91
|
+
staleTime: Infinity,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Fetches course details when the user arrived via ?course_run_key=.
|
|
96
|
+
* Skipped entirely when no course key is present (standard checkout).
|
|
97
|
+
*/
|
|
98
|
+
export function useCourseDetailQuery(client, sessionReady, courseRunKey) {
|
|
99
|
+
return useQuery({
|
|
100
|
+
queryKey: checkoutKeys.courseDetail(courseRunKey !== null && courseRunKey !== void 0 ? courseRunKey : 'none'),
|
|
101
|
+
enabled: !!client && sessionReady && !!courseRunKey,
|
|
102
|
+
queryFn: () => client.getCourseDetail(courseRunKey),
|
|
103
|
+
staleTime: Infinity,
|
|
104
|
+
retry: 1,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=useCheckoutQueries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCheckoutQueries.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutQueries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * query/useCheckoutQueries.ts — read-side TanStack Query hooks.\n *\n * useViaxSessionQuery runs on mount to exchange the edX session for a viax\n * token. Billing plans and subscriber count wait until the session is ready.\n */\nimport { useQuery } from '@tanstack/react-query';\nimport { isTerminalStatus } from '../../constants';\nimport { checkoutKeys } from './keys';\nimport { fetchEdxSession, fetchUserCountry } from '../../utils/provision';\nimport type {\n CheckoutClient, CartStatusResult, BillingPlansResult, CourseDetail,\n} from '../../types';\n\n/** Exchange edX session cookies for a viax token on page load. */\nexport function useViaxSessionQuery(sessionUrl: string | null) {\n return useQuery({\n queryKey: checkoutKeys.viaxSession(),\n enabled: !!sessionUrl,\n queryFn: () => fetchEdxSession(sessionUrl!),\n staleTime: 5 * 60 * 1000,\n retry: 1,\n });\n}\n\n/**\n * Fetches billing plans after the viax session is established.\n * Used to display prices in CartSummary before the user starts checkout.\n */\nexport function useBillingPlansQuery(client: CheckoutClient | null, sessionReady: boolean) {\n return useQuery({\n queryKey: checkoutKeys.billingPlans(),\n enabled: !!client && sessionReady,\n queryFn: (): Promise<BillingPlansResult> => client!.getBillingPlans(),\n staleTime: Infinity,\n retry: 2,\n });\n}\n\n/**\n * Fetches the live active subscriber count after the viax session is ready.\n * Refreshes every 5 minutes so the number stays roughly current.\n */\nexport function useActiveSubscriptionsQuery(client: CheckoutClient | null, sessionReady: boolean) {\n return useQuery({\n queryKey: ['checkout', 'activeSubscriptions'],\n enabled: !!client && sessionReady,\n queryFn: () => client!.getActiveSubscriptions(),\n staleTime: 5 * 60 * 1000, // 5 minutes\n retry: 1,\n });\n}\n\n/** Checks whether the authenticated learner already has an active subscription. */\nexport function useCurrentUserSubscriberQuery(client: CheckoutClient | null, sessionReady: boolean) {\n return useQuery({\n queryKey: ['checkout', 'currentUserSubscriber'],\n enabled: !!client && sessionReady,\n queryFn: () => client!.isCurrentUserSubscriber(),\n staleTime: 5 * 60 * 1000,\n retry: 1,\n });\n}\n\n/**\n * Fetches the user's country from their edX account profile.\n * Derives the LMS base URL from the session URL (same origin).\n * Returns null if the fetch fails or the field is absent — checkout stays open in that case.\n */\nexport function useUserCountryQuery(sessionUrl: string | null, username: string | null) {\n return useQuery({\n queryKey: ['checkout', 'userCountry', username],\n enabled: !!sessionUrl && !!username,\n queryFn: () => {\n const lmsBaseUrl = new URL(sessionUrl!).origin;\n return fetchUserCountry(lmsBaseUrl, username!);\n },\n staleTime: Infinity,\n retry: 1,\n });\n}\n\n/** Polls the cart state machine every second; stops on terminal states. */\nexport function useCartStatusQuery(client: CheckoutClient | null, cartBiId: string | null) {\n return useQuery({\n queryKey: checkoutKeys.status(cartBiId ?? 'none'),\n enabled: !!client && !!cartBiId,\n queryFn: (): Promise<CartStatusResult> => client!.getCartStatus(cartBiId!),\n refetchInterval: (query) =>\n isTerminalStatus(query.state.data?.status ?? null) ? false : 1000,\n refetchOnWindowFocus: false,\n gcTime: 0,\n });\n}\n\n/** Looks up the created subscription biId after success. */\nexport function useSubscriptionIdQuery(\n client: CheckoutClient | null,\n cartBiId: string | null,\n enabled: boolean,\n) {\n return useQuery({\n queryKey: checkoutKeys.subscriptionId(cartBiId ?? 'none'),\n enabled: enabled && !!client && !!cartBiId,\n queryFn: () => client!.findSubscriptionBiId(cartBiId!),\n staleTime: Infinity,\n });\n}\n\n/**\n * Fetches course details when the user arrived via ?course_run_key=.\n * Skipped entirely when no course key is present (standard checkout).\n */\nexport function useCourseDetailQuery(\n client: CheckoutClient | null,\n sessionReady: boolean,\n courseRunKey: string | null,\n) {\n return useQuery({\n queryKey: checkoutKeys.courseDetail(courseRunKey ?? 'none'),\n enabled: !!client && sessionReady && !!courseRunKey,\n queryFn: (): Promise<CourseDetail | null> => client!.getCourseDetail(courseRunKey!),\n staleTime: Infinity,\n retry: 1,\n });\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"urls.js","sourceRoot":"","sources":["../../../../src/data/services/subs/urls.js"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,EAAE,CAAC,CAAC,GAAG,aAAa,EAAE,CAAC,UAAU,MAAM,CAAC,CAAC;AAErE,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,EAAE,CAAC,CAAC,GAAG,SAAS,EAAE,qBAAqB,CAAC,CAAC","sourcesContent":["import { getSiteConfig } from '@openedx/frontend-base';\n\nexport const getApiUrl = () => (`${getSiteConfig().lmsBaseUrl}/api`);\n\nexport const getSubsInitApiUrl = () => (`${getApiUrl()}/subscription/init/`);\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './messages';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/i18n/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export { default } from './messages';\n"]}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,EAAE,OAAO,IAAI,0BAA0B,EAAE,MAAM,UAAU,CAAC","sourcesContent":["export { default as subscriptionCheckoutApp } from './app';\nexport { default as subscriptionCheckoutRoutes } from './routes';\n"]}
|
package/dist/messages.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineMessages } from '@openedx/frontend-base';
|
|
2
|
+
const messages = defineMessages({
|
|
3
|
+
'template.page.title': {
|
|
4
|
+
id: 'template.page.title',
|
|
5
|
+
defaultMessage: 'Template | {siteName}',
|
|
6
|
+
description: 'Document title for the template app',
|
|
7
|
+
},
|
|
8
|
+
});
|
|
9
|
+
export default messages;
|
|
10
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,MAAM,QAAQ,GAAG,cAAc,CAAC;IAC9B,qBAAqB,EAAE;QACrB,EAAE,EAAE,qBAAqB;QACzB,cAAc,EAAE,uBAAuB;QACvC,WAAW,EAAE,qCAAqC;KACnD;CACF,CAAC,CAAC;AAEH,eAAe,QAAQ,CAAC","sourcesContent":["import { defineMessages } from '@openedx/frontend-base';\n\nconst messages = defineMessages({\n 'template.page.title': {\n id: 'template.page.title',\n defaultMessage: 'Template | {siteName}',\n description: 'Document title for the template app',\n },\n});\n\nexport default messages;\n"]}
|
package/dist/routes.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
declare const routes: {
|
|
2
|
+
id: string;
|
|
3
|
+
path: string;
|
|
4
|
+
loader: (args: any) => Promise<null>;
|
|
5
|
+
handle: {
|
|
6
|
+
roles: string[];
|
|
7
|
+
};
|
|
8
|
+
lazy(): Promise<{
|
|
9
|
+
Component: () => import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
}>;
|
|
11
|
+
children: {
|
|
12
|
+
index: boolean;
|
|
13
|
+
lazy(): Promise<{
|
|
14
|
+
Component: import("react").FC<{}>;
|
|
15
|
+
}>;
|
|
16
|
+
}[];
|
|
17
|
+
}[];
|
|
18
|
+
export default routes;
|
package/dist/routes.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { authenticatedLoader, } from '@openedx/frontend-base';
|
|
11
|
+
import { acaCheckoutRole } from './constants';
|
|
12
|
+
// composed loader that checks for authentication and subscription access
|
|
13
|
+
const protectedLoader = (args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14
|
+
yield authenticatedLoader(args);
|
|
15
|
+
return null;
|
|
16
|
+
});
|
|
17
|
+
const routes = [
|
|
18
|
+
{
|
|
19
|
+
id: 'org.openedx.frontend.route.template.main',
|
|
20
|
+
path: 'aca-checkout',
|
|
21
|
+
loader: protectedLoader,
|
|
22
|
+
handle: {
|
|
23
|
+
roles: [acaCheckoutRole],
|
|
24
|
+
},
|
|
25
|
+
lazy() {
|
|
26
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
const module = yield import(
|
|
28
|
+
/* webpackChunkName: "template-main" */ './Main');
|
|
29
|
+
return { Component: module.default };
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
children: [
|
|
33
|
+
{
|
|
34
|
+
index: true,
|
|
35
|
+
lazy() {
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
const module = yield import(
|
|
38
|
+
/* webpackChunkName: "CheckoutPage" */ './checkoutPage/CheckoutPage');
|
|
39
|
+
return { Component: module.default };
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
export default routes;
|
|
47
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.tsx"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EACL,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,yEAAyE;AACzE,MAAM,eAAe,GAAG,CAAO,IAAI,EAAE,EAAE;IACrC,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAEhC,OAAO,IAAI,CAAC;AACd,CAAC,CAAA,CAAC;AAEF,MAAM,MAAM,GAAG;IACb;QACE,EAAE,EAAE,0CAA0C;QAC9C,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,eAAe;QACvB,MAAM,EAAE;YACN,KAAK,EAAE,CAAC,eAAe,CAAC;SACzB;QACK,IAAI;;gBACR,MAAM,MAAM,GAAG,MAAM,MAAM;gBACzB,uCAAuC,CAAC,QAAQ,CACjD,CAAC;gBACF,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;YACvC,CAAC;SAAA;QACD,QAAQ,EAAE;YACR;gBACE,KAAK,EAAE,IAAI;gBACL,IAAI;;wBACR,MAAM,MAAM,GAAG,MAAM,MAAM;wBACzB,sCAAsC,CAAC,6BAA6B,CACrE,CAAC;wBACF,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;oBACvC,CAAC;iBAAA;aACF;SACF;KACF;CACF,CAAC;AAEF,eAAe,MAAM,CAAC","sourcesContent":["import {\n authenticatedLoader,\n} from '@openedx/frontend-base';\n\nimport { acaCheckoutRole } from './constants';\n\n// composed loader that checks for authentication and subscription access\nconst protectedLoader = async (args) => {\n await authenticatedLoader(args);\n\n return null;\n};\n\nconst routes = [\n {\n id: 'org.openedx.frontend.route.template.main',\n path: 'aca-checkout',\n loader: protectedLoader,\n handle: {\n roles: [acaCheckoutRole],\n },\n async lazy() {\n const module = await import(\n /* webpackChunkName: \"template-main\" */ './Main'\n );\n return { Component: module.default };\n },\n children: [\n {\n index: true,\n async lazy() {\n const module = await import(\n /* webpackChunkName: \"CheckoutPage\" */ './checkoutPage/CheckoutPage'\n );\n return { Component: module.default };\n },\n },\n ],\n },\n];\n\nexport default routes;\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Slot } from '@openedx/frontend-base';
|
|
3
|
+
export const exampleSlotId = 'org.openedx.frontend.slot.template.example.v1';
|
|
4
|
+
const ExampleSlot = () => (_jsx(Slot, { id: exampleSlotId }));
|
|
5
|
+
export default ExampleSlot;
|
|
6
|
+
//# sourceMappingURL=ExampleSlot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExampleSlot.js","sourceRoot":"","sources":["../../../src/slots/ExampleSlot/ExampleSlot.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAE9C,MAAM,CAAC,MAAM,aAAa,GAAG,+CAA+C,CAAC;AAE7E,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,CACxB,KAAC,IAAI,IAAC,EAAE,EAAE,aAAa,GAAI,CAC5B,CAAC;AAEF,eAAe,WAAW,CAAC","sourcesContent":["import { Slot } from '@openedx/frontend-base';\n\nexport const exampleSlotId = 'org.openedx.frontend.slot.template.example.v1';\n\nconst ExampleSlot = () => (\n <Slot id={exampleSlotId} />\n);\n\nexport default ExampleSlot;\n"]}
|
package/dist/slots.d.ts
ADDED
package/dist/slots.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slots.js","sourceRoot":"","sources":["../src/slots.tsx"],"names":[],"mappings":"AAEA,MAAM,KAAK,GAAoB,EAAE,CAAC;AAElC,eAAe,KAAK,CAAC","sourcesContent":["import { SlotOperation } from '@openedx/frontend-base';\n\nconst slots: SlotOperation[] = [];\n\nexport default slots;\n"]}
|
package/dist/style.scss
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
@use "@openedx/paragon/styles/css/core/custom-media-breakpoints.css";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
//components
|
|
5
|
+
@use "./styles/components/checkout.scss";
|
|
6
|
+
@use "./styles/components/enrollmentSummary.scss";
|
|
7
|
+
@use "./styles/components/paymentForm.scss";
|
|
8
|
+
@use "./styles/components/checkoutStepper.scss";
|
|
9
|
+
@use "./styles/components/paymentFields.scss";
|
|
10
|
+
@use "./styles/components/brandMarks.scss";
|
|
11
|
+
@use "./styles/components/statusScreens.scss";
|
|
12
|
+
@use "./styles/components/edxUnlimitedBanner.scss";
|
|
13
|
+
@use "./styles/components/cartSummary.scss";
|
|
14
|
+
@use "./styles/components/courseEnrollmentCard.scss";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.brand-marks {
|
|
2
|
+
&__card-brands {
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
flex-shrink: 0;
|
|
6
|
+
gap: 4px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
&__paypal {
|
|
10
|
+
font-family: Arial, sans-serif;
|
|
11
|
+
font-weight: 700;
|
|
12
|
+
font-style: italic;
|
|
13
|
+
font-size: 1.05rem;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&__paypal-pay {
|
|
17
|
+
color: #003087;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&__paypal-pal {
|
|
21
|
+
color: #009cde;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&__edx-logo {
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
flex-shrink: 0;
|
|
29
|
+
padding: 0.5rem 0.625rem;
|
|
30
|
+
margin-right: 0.75rem;
|
|
31
|
+
background: #fff;
|
|
32
|
+
border: 1px solid #e7e9ef;
|
|
33
|
+
border-radius: 8px;
|
|
34
|
+
|
|
35
|
+
img {
|
|
36
|
+
display: block;
|
|
37
|
+
width: 48px;
|
|
38
|
+
height: 25px;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// styles/components/cartSummary.scss
|
|
2
|
+
|
|
3
|
+
.cart-summary {
|
|
4
|
+
// No background — inherits the page background (off-white in the design)
|
|
5
|
+
background: transparent;
|
|
6
|
+
|
|
7
|
+
&__stats {
|
|
8
|
+
display: flex;
|
|
9
|
+
gap: 1.5rem;
|
|
10
|
+
margin-top: 1rem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
&__stat {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: 0.5rem;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&__pricing {
|
|
20
|
+
line-height: 1.35;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&__count-skeleton {
|
|
24
|
+
display: inline-flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
width: 6rem;
|
|
27
|
+
line-height: 1;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
div.checkout__container {
|
|
2
|
+
padding-inline: 1.125rem; /* 18px */
|
|
3
|
+
padding-block: 3rem; /* 48px */
|
|
4
|
+
div.payment__options {
|
|
5
|
+
> div {
|
|
6
|
+
padding: 32px;
|
|
7
|
+
background-color: white;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@media (--pgn-size-breakpoint-min-width-lg) {
|
|
13
|
+
div.checkout__container {
|
|
14
|
+
padding:0;
|
|
15
|
+
div.payment__options {
|
|
16
|
+
background-color: var(--pgn-color-light-100);
|
|
17
|
+
> div {
|
|
18
|
+
padding: 32px 32px 64px 32px;
|
|
19
|
+
background-color: transparent;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
div.payment__summary {
|
|
23
|
+
padding: 48px 48px 32px 48px;
|
|
24
|
+
}
|
|
25
|
+
div.gap-5 {
|
|
26
|
+
gap: 0 !important;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|