@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,48 @@
|
|
|
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
|
+
* api/billingPlans.ts — fetch billing plans without authentication.
|
|
12
|
+
* Called on component mount to show prices before the user clicks Place order.
|
|
13
|
+
*/
|
|
14
|
+
import { GET_BILLING_PLANS } from './queries';
|
|
15
|
+
export function fetchBillingPlans(apiUrl) {
|
|
16
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
17
|
+
var _a, _b, _c, _d, _e;
|
|
18
|
+
const res = yield fetch(apiUrl, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'Content-Type': 'application/json' },
|
|
21
|
+
body: JSON.stringify({ query: GET_BILLING_PLANS }),
|
|
22
|
+
});
|
|
23
|
+
if (!res.ok)
|
|
24
|
+
throw new Error(`Failed to fetch billing plans (${res.status})`);
|
|
25
|
+
const json = yield res.json();
|
|
26
|
+
if ((_a = json.errors) === null || _a === void 0 ? void 0 : _a.length)
|
|
27
|
+
throw new Error(((_b = json.errors[0]) === null || _b === void 0 ? void 0 : _b.message) || 'GraphQL error');
|
|
28
|
+
const edges = (_e = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.filterCustomerBillingPlan) === null || _d === void 0 ? void 0 : _d.edges) !== null && _e !== void 0 ? _e : [];
|
|
29
|
+
const plans = edges.map(({ node }) => {
|
|
30
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
31
|
+
return ({
|
|
32
|
+
uid: node.uid,
|
|
33
|
+
stripePriceId: node.tStripePriceId,
|
|
34
|
+
amount: (_b = (_a = node.tStripeAmount) === null || _a === void 0 ? void 0 : _a.amount) !== null && _b !== void 0 ? _b : '0',
|
|
35
|
+
currency: (_e = (_d = (_c = node.tStripeAmount) === null || _c === void 0 ? void 0 : _c.units) === null || _d === void 0 ? void 0 : _d.code) !== null && _e !== void 0 ? _e : 'USD',
|
|
36
|
+
isTemplate: node.bipIsTemplate,
|
|
37
|
+
ownershipModel: {
|
|
38
|
+
uid: (_g = (_f = node.bipOwnershipModel) === null || _f === void 0 ? void 0 : _f.uid) !== null && _g !== void 0 ? _g : '',
|
|
39
|
+
omName: (_j = (_h = node.bipOwnershipModel) === null || _h === void 0 ? void 0 : _h.omName) !== null && _j !== void 0 ? _j : '',
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
if (!plans.length)
|
|
44
|
+
throw new Error('No billing plans found.');
|
|
45
|
+
return { plans };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=fetchBillingPlans.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetchBillingPlans.js","sourceRoot":"","sources":["../../src/utils/fetchBillingPlans.ts"],"names":[],"mappings":";;;;;;;;;AAAA;;;GAGG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG9C,MAAM,UAAgB,iBAAiB,CAAC,MAAc;;;QACpD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE;YAC9B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC;SACnD,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QAE9E,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,MAAA,IAAI,CAAC,MAAM,0CAAE,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,CAAA,MAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,0CAAE,OAAO,KAAI,eAAe,CAAC,CAAC;QAErF,MAAM,KAAK,GAQL,MAAA,MAAA,MAAA,IAAI,CAAC,IAAI,0CAAE,yBAAyB,0CAAE,KAAK,mCAAI,EAAE,CAAC;QAExD,MAAM,KAAK,GAAkB,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;;YAAC,OAAA,CAAC;gBACpD,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,aAAa,EAAE,IAAI,CAAC,cAAc;gBAClC,MAAM,EAAE,MAAA,MAAA,IAAI,CAAC,aAAa,0CAAE,MAAM,mCAAI,GAAG;gBACzC,QAAQ,EAAE,MAAA,MAAA,MAAA,IAAI,CAAC,aAAa,0CAAE,KAAK,0CAAE,IAAI,mCAAI,KAAK;gBAClD,UAAU,EAAE,IAAI,CAAC,aAAa;gBAC9B,cAAc,EAAE;oBACd,GAAG,EAAE,MAAA,MAAA,IAAI,CAAC,iBAAiB,0CAAE,GAAG,mCAAI,EAAE;oBACtC,MAAM,EAAE,MAAA,MAAA,IAAI,CAAC,iBAAiB,0CAAE,MAAM,mCAAI,EAAE;iBAC7C;aACF,CAAC,CAAA;SAAA,CAAC,CAAC;QAEJ,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC9D,OAAO,EAAE,KAAK,EAAE,CAAC;IACnB,CAAC;CAAA","sourcesContent":["/**\n * api/billingPlans.ts — fetch billing plans without authentication.\n * Called on component mount to show prices before the user clicks Place order.\n */\nimport { GET_BILLING_PLANS } from './queries';\nimport type { BillingPlan, BillingPlansResult } from '../types';\n\nexport async function fetchBillingPlans(apiUrl: string): Promise<BillingPlansResult> {\n const res = await fetch(apiUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ query: GET_BILLING_PLANS }),\n });\n\n if (!res.ok) throw new Error(`Failed to fetch billing plans (${res.status})`);\n\n const json = await res.json();\n if (json.errors?.length) throw new Error(json.errors[0]?.message || 'GraphQL error');\n\n const edges: {\n node: {\n uid: string,\n tStripePriceId: string,\n tStripeAmount?: { amount: string, units?: { code: string } },\n bipIsTemplate: boolean,\n bipOwnershipModel?: { uid: string, omName: string },\n },\n }[] = json.data?.filterCustomerBillingPlan?.edges ?? [];\n\n const plans: BillingPlan[] = edges.map(({ node }) => ({\n uid: node.uid,\n stripePriceId: node.tStripePriceId,\n amount: node.tStripeAmount?.amount ?? '0',\n currency: node.tStripeAmount?.units?.code ?? 'USD',\n isTemplate: node.bipIsTemplate,\n ownershipModel: {\n uid: node.bipOwnershipModel?.uid ?? '',\n omName: node.bipOwnershipModel?.omName ?? '',\n },\n }));\n\n if (!plans.length) throw new Error('No billing plans found.');\n return { plans };\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* api/graphqlClient.ts — authed GraphQL client.
|
|
3
|
+
*
|
|
4
|
+
* No token is required at construction. Call setToken(viaxToken) after
|
|
5
|
+
* GET /edx-session returns — typically on checkout page mount.
|
|
6
|
+
*
|
|
7
|
+
* On 401 the token is cleared and the request fails — the mutation will
|
|
8
|
+
* surface the error and the user can retry.
|
|
9
|
+
*/
|
|
10
|
+
import type { GqlRunner } from '../types';
|
|
11
|
+
export declare function createGqlClient({ apiUrl }: {
|
|
12
|
+
apiUrl: string;
|
|
13
|
+
}): {
|
|
14
|
+
gql: GqlRunner;
|
|
15
|
+
setToken: (token: string) => void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
export function createGqlClient({ apiUrl }) {
|
|
11
|
+
if (!apiUrl)
|
|
12
|
+
throw new Error('createGqlClient: apiUrl is required');
|
|
13
|
+
let token = null;
|
|
14
|
+
const setToken = (raw) => {
|
|
15
|
+
token = raw.startsWith('Bearer ') ? raw : `Bearer ${raw}`;
|
|
16
|
+
};
|
|
17
|
+
const gql = (query_1, variables_1, ...args_1) => __awaiter(this, [query_1, variables_1, ...args_1], void 0, function* (query, variables, retry = false) {
|
|
18
|
+
var _a, _b;
|
|
19
|
+
if (!token)
|
|
20
|
+
throw new Error('No viax token available. Please try placing your order again.');
|
|
21
|
+
const res = yield fetch(apiUrl, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: { 'Content-Type': 'application/json', Authorization: token },
|
|
24
|
+
body: JSON.stringify({ query, variables }),
|
|
25
|
+
});
|
|
26
|
+
if (res.status === 401 && !retry) {
|
|
27
|
+
token = null;
|
|
28
|
+
throw new Error('Session expired. Please try placing your order again.');
|
|
29
|
+
}
|
|
30
|
+
const json = yield res.json();
|
|
31
|
+
if ((_a = json.errors) === null || _a === void 0 ? void 0 : _a.length) {
|
|
32
|
+
const msg = ((_b = json.errors[0]) === null || _b === void 0 ? void 0 : _b.message) || 'GraphQL error';
|
|
33
|
+
throw new Error(msg);
|
|
34
|
+
}
|
|
35
|
+
return json.data;
|
|
36
|
+
});
|
|
37
|
+
return { gql, setToken };
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=graphqlClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphqlClient.js","sourceRoot":"","sources":["../../src/utils/graphqlClient.ts"],"names":[],"mappings":";;;;;;;;;AAiBA,MAAM,UAAU,eAAe,CAC7B,EAAE,MAAM,EAAsB;IAE9B,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAEpE,IAAI,KAAK,GAAkB,IAAI,CAAC;IAEhC,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAQ,EAAE;QACrC,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,GAAG,EAAE,CAAC;IAC5D,CAAC,CAAC;IAEF,MAAM,GAAG,GAAc,kCAAwC,EAAE,uEAAnC,KAAK,EAAE,SAAS,EAAE,KAAK,GAAG,KAAK;;QAC3D,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;QAE7F,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE;YAC9B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,KAAK,EAAE;YACrE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;SAC3C,CAAC,CAAC;QAEH,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YACjC,KAAK,GAAG,IAAI,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;QAC3E,CAAC;QAED,MAAM,IAAI,GAAgB,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3C,IAAI,MAAA,IAAI,CAAC,MAAM,0CAAE,MAAM,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,CAAA,MAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,0CAAE,OAAO,KAAI,eAAe,CAAC;YACvD,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC;IACnB,CAAC,CAAA,CAAC;IAEF,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;AAC3B,CAAC","sourcesContent":["/**\n * api/graphqlClient.ts — authed GraphQL client.\n *\n * No token is required at construction. Call setToken(viaxToken) after\n * GET /edx-session returns — typically on checkout page mount.\n *\n * On 401 the token is cleared and the request fails — the mutation will\n * surface the error and the user can retry.\n */\nimport type { GqlRunner } from '../types';\n\ninterface GqlResponse {\n\n data?: any,\n errors?: { message?: string }[],\n}\n\nexport function createGqlClient(\n { apiUrl }: { apiUrl: string },\n): { gql: GqlRunner, setToken: (token: string) => void } {\n if (!apiUrl) throw new Error('createGqlClient: apiUrl is required');\n\n let token: string | null = null;\n\n const setToken = (raw: string): void => {\n token = raw.startsWith('Bearer ') ? raw : `Bearer ${raw}`;\n };\n\n const gql: GqlRunner = async (query, variables, retry = false) => {\n if (!token) throw new Error('No viax token available. Please try placing your order again.');\n\n const res = await fetch(apiUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: token },\n body: JSON.stringify({ query, variables }),\n });\n\n if (res.status === 401 && !retry) {\n token = null;\n throw new Error('Session expired. Please try placing your order again.');\n }\n\n const json: GqlResponse = await res.json();\n if (json.errors?.length) {\n const msg = json.errors[0]?.message || 'GraphQL error';\n throw new Error(msg);\n }\n return json.data;\n };\n\n return { gql, setToken };\n}\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function formatTrialDate(date) {
|
|
2
|
+
return date.toLocaleDateString('en-US', {
|
|
3
|
+
month: 'long',
|
|
4
|
+
day: 'numeric',
|
|
5
|
+
year: 'numeric',
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
export function getTrialDateRange(trialStart, trialEnd, trialDays = 7) {
|
|
9
|
+
const today = new Date();
|
|
10
|
+
const endDate = new Date(today);
|
|
11
|
+
endDate.setDate(today.getDate() + trialDays);
|
|
12
|
+
return {
|
|
13
|
+
displayStart: trialStart !== null && trialStart !== void 0 ? trialStart : formatTrialDate(today),
|
|
14
|
+
displayEnd: trialEnd !== null && trialEnd !== void 0 ? trialEnd : formatTrialDate(endDate),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,eAAe,CAAC,IAAU;IACxC,OAAO,IAAI,CAAC,kBAAkB,CAAC,OAAO,EAAE;QACtC,KAAK,EAAE,MAAM;QACb,GAAG,EAAE,SAAS;QACd,IAAI,EAAE,SAAS;KAChB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,UAA0B,EAC1B,QAAwB,EACxB,SAAS,GAAG,CAAC;IAEb,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC;IACzB,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAChC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,SAAS,CAAC,CAAC;IAE7C,OAAO;QACL,YAAY,EAAE,UAAU,aAAV,UAAU,cAAV,UAAU,GAAI,eAAe,CAAC,KAAK,CAAC;QAClD,UAAU,EAAE,QAAQ,aAAR,QAAQ,cAAR,QAAQ,GAAI,eAAe,CAAC,OAAO,CAAC;KACjD,CAAC;AACJ,CAAC","sourcesContent":["import { TrialDateRange } from '@src/types';\n\nexport function formatTrialDate(date: Date): string {\n return date.toLocaleDateString('en-US', {\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n });\n}\n\nexport function getTrialDateRange(\n trialStart?: string | null,\n trialEnd?: string | null,\n trialDays = 7,\n): TrialDateRange {\n const today = new Date();\n const endDate = new Date(today);\n endDate.setDate(today.getDate() + trialDays);\n\n return {\n displayStart: trialStart ?? formatTrialDate(today),\n displayEnd: trialEnd ?? formatTrialDate(endDate),\n };\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BillingCycle } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Map ?plan= values to the internal billing cycle.
|
|
4
|
+
* Accepts monthly, annual, and annually (the public query value).
|
|
5
|
+
*/
|
|
6
|
+
export declare function parsePlanParam(raw: string | null | undefined): BillingCycle | null;
|
|
7
|
+
export declare function getPlanFromSearch(search: string): BillingCycle;
|
|
8
|
+
export declare function getPlanFromUrl(): BillingCycle;
|
|
9
|
+
export declare function planToQueryValue(cycle: BillingCycle): 'monthly' | 'annually';
|
|
10
|
+
/**
|
|
11
|
+
* Set ?plan= while preserving other params.
|
|
12
|
+
* Avoid URLSearchParams — it treats bare `+` as a space and would break
|
|
13
|
+
* course_run_key values like HarvardX+CS50+X.
|
|
14
|
+
*/
|
|
15
|
+
export declare function withPlanQuery(search: string, cycle: BillingCycle): string;
|
|
16
|
+
export declare function writePlanQueryParam(cycle: BillingCycle): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const PLAN_QUERY_KEY = 'plan';
|
|
2
|
+
/**
|
|
3
|
+
* Map ?plan= values to the internal billing cycle.
|
|
4
|
+
* Accepts monthly, annual, and annually (the public query value).
|
|
5
|
+
*/
|
|
6
|
+
export function parsePlanParam(raw) {
|
|
7
|
+
if (!raw)
|
|
8
|
+
return null;
|
|
9
|
+
const value = raw.trim().toLowerCase();
|
|
10
|
+
if (value === 'monthly')
|
|
11
|
+
return 'monthly';
|
|
12
|
+
if (value === 'annual' || value === 'annually')
|
|
13
|
+
return 'annual';
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
export function getPlanFromSearch(search) {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
const match = /[?&]plan=([^&]*)/i.exec(search);
|
|
19
|
+
if (!match)
|
|
20
|
+
return 'monthly';
|
|
21
|
+
try {
|
|
22
|
+
return (_a = parsePlanParam(decodeURIComponent(match[1]))) !== null && _a !== void 0 ? _a : 'monthly';
|
|
23
|
+
}
|
|
24
|
+
catch (_c) {
|
|
25
|
+
return (_b = parsePlanParam(match[1])) !== null && _b !== void 0 ? _b : 'monthly';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function getPlanFromUrl() {
|
|
29
|
+
if (typeof window === 'undefined')
|
|
30
|
+
return 'monthly';
|
|
31
|
+
return getPlanFromSearch(window.location.search);
|
|
32
|
+
}
|
|
33
|
+
export function planToQueryValue(cycle) {
|
|
34
|
+
return cycle === 'annual' ? 'annually' : 'monthly';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Set ?plan= while preserving other params.
|
|
38
|
+
* Avoid URLSearchParams — it treats bare `+` as a space and would break
|
|
39
|
+
* course_run_key values like HarvardX+CS50+X.
|
|
40
|
+
*/
|
|
41
|
+
export function withPlanQuery(search, cycle) {
|
|
42
|
+
const planValue = planToQueryValue(cycle);
|
|
43
|
+
const raw = search.startsWith('?') ? search.slice(1) : search;
|
|
44
|
+
const parts = raw
|
|
45
|
+
.split('&')
|
|
46
|
+
.filter((part) => (part.length > 0
|
|
47
|
+
&& !part.toLowerCase().startsWith(`${PLAN_QUERY_KEY}=`)));
|
|
48
|
+
parts.push(`${PLAN_QUERY_KEY}=${planValue}`);
|
|
49
|
+
return `?${parts.join('&')}`;
|
|
50
|
+
}
|
|
51
|
+
export function writePlanQueryParam(cycle) {
|
|
52
|
+
if (typeof window === 'undefined')
|
|
53
|
+
return;
|
|
54
|
+
const next = `${window.location.pathname}${withPlanQuery(window.location.search, cycle)}${window.location.hash}`;
|
|
55
|
+
window.history.replaceState(window.history.state, '', next);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=planQuery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planQuery.js","sourceRoot":"","sources":["../../src/utils/planQuery.ts"],"names":[],"mappings":"AAEA,MAAM,cAAc,GAAG,MAAM,CAAC;AAE9B;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAA8B;IAC3D,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACvC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,UAAU;QAAE,OAAO,QAAQ,CAAC;IAChE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAc;;IAC9C,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,IAAI,CAAC;QACH,OAAO,MAAA,cAAc,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,mCAAI,SAAS,CAAC;IACnE,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,MAAA,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,mCAAI,SAAS,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,SAAS,CAAC;IACpD,OAAO,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAmB;IAClD,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;AACrD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,KAAmB;IAC/D,MAAM,SAAS,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9D,MAAM,KAAK,GAAG,GAAG;SACd,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAChB,IAAI,CAAC,MAAM,GAAG,CAAC;WACZ,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,cAAc,GAAG,CAAC,CACxD,CAAC,CAAC;IACL,KAAK,CAAC,IAAI,CAAC,GAAG,cAAc,IAAI,SAAS,EAAE,CAAC,CAAC;IAC7C,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAmB;IACrD,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO;IAC1C,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;IACjH,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;AAC9D,CAAC","sourcesContent":["import type { BillingCycle } from '../types';\n\nconst PLAN_QUERY_KEY = 'plan';\n\n/**\n * Map ?plan= values to the internal billing cycle.\n * Accepts monthly, annual, and annually (the public query value).\n */\nexport function parsePlanParam(raw: string | null | undefined): BillingCycle | null {\n if (!raw) return null;\n const value = raw.trim().toLowerCase();\n if (value === 'monthly') return 'monthly';\n if (value === 'annual' || value === 'annually') return 'annual';\n return null;\n}\n\nexport function getPlanFromSearch(search: string): BillingCycle {\n const match = /[?&]plan=([^&]*)/i.exec(search);\n if (!match) return 'monthly';\n try {\n return parsePlanParam(decodeURIComponent(match[1])) ?? 'monthly';\n } catch {\n return parsePlanParam(match[1]) ?? 'monthly';\n }\n}\n\nexport function getPlanFromUrl(): BillingCycle {\n if (typeof window === 'undefined') return 'monthly';\n return getPlanFromSearch(window.location.search);\n}\n\nexport function planToQueryValue(cycle: BillingCycle): 'monthly' | 'annually' {\n return cycle === 'annual' ? 'annually' : 'monthly';\n}\n\n/**\n * Set ?plan= while preserving other params.\n * Avoid URLSearchParams — it treats bare `+` as a space and would break\n * course_run_key values like HarvardX+CS50+X.\n */\nexport function withPlanQuery(search: string, cycle: BillingCycle): string {\n const planValue = planToQueryValue(cycle);\n const raw = search.startsWith('?') ? search.slice(1) : search;\n const parts = raw\n .split('&')\n .filter((part) => (\n part.length > 0\n && !part.toLowerCase().startsWith(`${PLAN_QUERY_KEY}=`)\n ));\n parts.push(`${PLAN_QUERY_KEY}=${planValue}`);\n return `?${parts.join('&')}`;\n}\n\nexport function writePlanQueryParam(cycle: BillingCycle): void {\n if (typeof window === 'undefined') return;\n const next = `${window.location.pathname}${withPlanQuery(window.location.search, cycle)}${window.location.hash}`;\n window.history.replaceState(window.history.state, '', next);\n}\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* api/provision.ts — exchange the edX session for a viax token.
|
|
3
|
+
*
|
|
4
|
+
* {LMS_BASE_URL}/api/edx-session
|
|
5
|
+
* The LMS reads the edX JWT from browser cookies, exchanges it with viax,
|
|
6
|
+
* and returns a per-user token for authenticated API calls.
|
|
7
|
+
*
|
|
8
|
+
* Dev uses POST with a hardcoded JWT payload (local LMS cookie exchange is
|
|
9
|
+
* unavailable). Stage and prod use POST and rely on the browser session cookies.
|
|
10
|
+
*/
|
|
11
|
+
export interface EdxSessionResult {
|
|
12
|
+
viaxToken: string;
|
|
13
|
+
customerUid: string;
|
|
14
|
+
partyUid?: string;
|
|
15
|
+
edxUserId?: string;
|
|
16
|
+
username?: string;
|
|
17
|
+
email?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface GeolocationResult {
|
|
20
|
+
status: 'resolved' | 'not_found' | 'error';
|
|
21
|
+
ip_address: string;
|
|
22
|
+
ip_address_type: string | null;
|
|
23
|
+
country_code: string | null;
|
|
24
|
+
country_name: string | null;
|
|
25
|
+
region_code: string | null;
|
|
26
|
+
region_name: string | null;
|
|
27
|
+
city: string | null;
|
|
28
|
+
postal_code: string | null;
|
|
29
|
+
latitude: number | null;
|
|
30
|
+
longitude: number | null;
|
|
31
|
+
accuracy_radius_km: number | null;
|
|
32
|
+
confidence: string | null;
|
|
33
|
+
source: string | null;
|
|
34
|
+
cache_status: string | null;
|
|
35
|
+
looked_up_at: string | null;
|
|
36
|
+
fallback_reason: string | null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Fetch the ISO 3166-1 alpha-2 country code from the user's edX account profile.
|
|
40
|
+
* Returns null if the request fails or the field is absent.
|
|
41
|
+
*/
|
|
42
|
+
export declare function fetchUserCountry(lmsBaseUrl: string, username: string): Promise<string | null>;
|
|
43
|
+
/** Fetch the learner's current public IP for geolocation lookup. */
|
|
44
|
+
export declare function fetchPublicIp(): Promise<string>;
|
|
45
|
+
/** Look up geolocation data for an explicit IP address. */
|
|
46
|
+
export declare function fetchIpGeolocation(geolocationUrl: string, ipAddress: string): Promise<GeolocationResult>;
|
|
47
|
+
/** Exchange the learner's edX session (cookies) for a viax token. */
|
|
48
|
+
export declare function fetchEdxSession(sessionUrl: string): Promise<EdxSessionResult>;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* api/provision.ts — exchange the edX session for a viax token.
|
|
3
|
+
*
|
|
4
|
+
* {LMS_BASE_URL}/api/edx-session
|
|
5
|
+
* The LMS reads the edX JWT from browser cookies, exchanges it with viax,
|
|
6
|
+
* and returns a per-user token for authenticated API calls.
|
|
7
|
+
*
|
|
8
|
+
* Dev uses POST with a hardcoded JWT payload (local LMS cookie exchange is
|
|
9
|
+
* unavailable). Stage and prod use POST and rely on the browser session cookies.
|
|
10
|
+
*/
|
|
11
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
12
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
13
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
14
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
15
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
16
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
17
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
import { EnvironmentTypes, getSiteConfig } from '@openedx/frontend-base';
|
|
21
|
+
/** Hardcoded edX JWT used only for local/dev checkout against stage Viax. */
|
|
22
|
+
const DEV_SESSION_BODY = {
|
|
23
|
+
payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIiwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MDE4NzAyNSwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkwMTgzNDI1LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJUVl8yNjA5X0hWUEsiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiIyNDlmZDU0MjUwMzcxZTFmOTFkNmU0MjA2MmViMjAzZCIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1ODM3LCJlbWFpbCI6InFrazQyNjEzQGxhb2lhLmNvbSIsIm5hbWUiOiJ0YW52aSB2b3JhIiwiZmFtaWx5X25hbWUiOiIiLCJnaXZlbl9uYW1lIjoiIiwiYWRtaW5pc3RyYXRvciI6ZmFsc2UsInN1cGVydXNlciI6ZmFsc2V9',
|
|
24
|
+
signature: 'DWvWquDIUopCJpd_fPV2e352TBFwy-O9NYfXD0fojH37yQmtYJJOidDUzHtcj53X5PF2GnK0gyTkHxMOPZ7OhWvDgmoWV4qQW6dfgqAYjFrvwh5CaKQqtU1vAXM7i-h6hxjHJNpM2VFWhUIMafU3cD5WmCRC6mD_rr4J6ddNRAbFjretuyrXOcVpKM9_nRMP9zMegcwaiSj4LeZYdDL_e32QwgJrcpwmXTV8YRok-Ouk0rZdFOEPkbHvpNa7wDNv3e14XD_9rRJh2N0eK0vd7vvaBUPQ-UHGOOntFvUJQFaN5JDLtbym-Kx1XATTzKDE9Ti-HjH1qcBUoACNEX1x5w'
|
|
25
|
+
};
|
|
26
|
+
const EDX_JWT_PAYLOAD_COOKIE_NAMES = [
|
|
27
|
+
'stage-edx-jwt-cookie-header-payload',
|
|
28
|
+
'edx-jwt-cookie-header-payload',
|
|
29
|
+
];
|
|
30
|
+
const EDX_JWT_SIGNATURE_COOKIE_NAMES = [
|
|
31
|
+
'stage-edx-jwt-cookie-signature',
|
|
32
|
+
'edx-jwt-cookie-signature',
|
|
33
|
+
];
|
|
34
|
+
function getCookieValue(name) {
|
|
35
|
+
const cookie = document.cookie
|
|
36
|
+
.split('; ')
|
|
37
|
+
.find((cookiePart) => cookiePart.startsWith(`${name}=`));
|
|
38
|
+
if (!cookie)
|
|
39
|
+
return null;
|
|
40
|
+
return decodeURIComponent(cookie.substring(name.length + 1));
|
|
41
|
+
}
|
|
42
|
+
function getFirstCookieValue(names) {
|
|
43
|
+
for (const name of names) {
|
|
44
|
+
const value = getCookieValue(name);
|
|
45
|
+
if (value)
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
function getEdxJwtCookieBody() {
|
|
51
|
+
const payload = getFirstCookieValue(EDX_JWT_PAYLOAD_COOKIE_NAMES);
|
|
52
|
+
const signature = getFirstCookieValue(EDX_JWT_SIGNATURE_COOKIE_NAMES);
|
|
53
|
+
return payload && signature ? { payload, signature } : null;
|
|
54
|
+
}
|
|
55
|
+
function parseSessionResponse(data) {
|
|
56
|
+
var _a, _b, _c, _d;
|
|
57
|
+
const viaxToken = ((_c = (_b = (_a = data.viaxToken) !== null && _a !== void 0 ? _a : data.token) !== null && _b !== void 0 ? _b : data.access_token) !== null && _c !== void 0 ? _c : data.accessToken);
|
|
58
|
+
const customerUid = ((_d = data.customerUid) !== null && _d !== void 0 ? _d : data.customer_uid);
|
|
59
|
+
if (!viaxToken)
|
|
60
|
+
throw new Error('Session response missing viax token.');
|
|
61
|
+
if (!customerUid)
|
|
62
|
+
throw new Error('Session response missing customerUid.');
|
|
63
|
+
return {
|
|
64
|
+
viaxToken,
|
|
65
|
+
customerUid,
|
|
66
|
+
partyUid: data.partyUid,
|
|
67
|
+
edxUserId: data.edxUserId,
|
|
68
|
+
username: data.username,
|
|
69
|
+
email: data.email,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Fetch the ISO 3166-1 alpha-2 country code from the user's edX account profile.
|
|
74
|
+
* Returns null if the request fails or the field is absent.
|
|
75
|
+
*/
|
|
76
|
+
export function fetchUserCountry(lmsBaseUrl, username) {
|
|
77
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
try {
|
|
79
|
+
const res = yield fetch(`${lmsBaseUrl}/api/user/v1/accounts/${encodeURIComponent(username)}`, {
|
|
80
|
+
credentials: 'include',
|
|
81
|
+
});
|
|
82
|
+
if (!res.ok)
|
|
83
|
+
return null;
|
|
84
|
+
const data = yield res.json();
|
|
85
|
+
return typeof data.country === 'string' && data.country ? data.country : null;
|
|
86
|
+
}
|
|
87
|
+
catch (_a) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/** Fetch the learner's current public IP for geolocation lookup. */
|
|
93
|
+
export function fetchPublicIp() {
|
|
94
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
95
|
+
const res = yield fetch('https://api.ipify.org?format=json', {
|
|
96
|
+
headers: { Accept: 'application/json' },
|
|
97
|
+
});
|
|
98
|
+
if (!res.ok) {
|
|
99
|
+
throw new Error(`Public IP lookup failed (${res.status}).`);
|
|
100
|
+
}
|
|
101
|
+
const data = yield res.json();
|
|
102
|
+
if (typeof data.ip !== 'string' || !data.ip) {
|
|
103
|
+
throw new Error('Public IP lookup returned an invalid address.');
|
|
104
|
+
}
|
|
105
|
+
return data.ip;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** Look up geolocation data for an explicit IP address. */
|
|
109
|
+
export function fetchIpGeolocation(geolocationUrl, ipAddress) {
|
|
110
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
111
|
+
const edxJwtBody = getEdxJwtCookieBody();
|
|
112
|
+
const requestBody = edxJwtBody
|
|
113
|
+
? Object.assign({ ip_address: ipAddress }, edxJwtBody) : { ip_address: ipAddress };
|
|
114
|
+
const res = yield fetch(geolocationUrl, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
credentials: 'include',
|
|
117
|
+
headers: {
|
|
118
|
+
Accept: 'application/json',
|
|
119
|
+
'Content-Type': 'application/json',
|
|
120
|
+
},
|
|
121
|
+
body: JSON.stringify(requestBody),
|
|
122
|
+
});
|
|
123
|
+
if (!res.ok) {
|
|
124
|
+
throw new Error(`Geolocation lookup failed (${res.status}).`);
|
|
125
|
+
}
|
|
126
|
+
return res.json();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
function isDevelopment() {
|
|
130
|
+
return getSiteConfig().environment === EnvironmentTypes.DEVELOPMENT;
|
|
131
|
+
}
|
|
132
|
+
/** Exchange the learner's edX session (cookies) for a viax token. */
|
|
133
|
+
export function fetchEdxSession(sessionUrl) {
|
|
134
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
135
|
+
const headers = { Accept: 'application/json' };
|
|
136
|
+
let res;
|
|
137
|
+
if (isDevelopment()) {
|
|
138
|
+
res = yield fetch(sessionUrl, {
|
|
139
|
+
method: 'POST',
|
|
140
|
+
credentials: 'include',
|
|
141
|
+
headers: Object.assign(Object.assign({}, headers), { 'Content-Type': 'application/json' }),
|
|
142
|
+
body: JSON.stringify(DEV_SESSION_BODY),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
res = yield fetch(sessionUrl, {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
credentials: 'include',
|
|
149
|
+
headers,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (!res.ok) {
|
|
153
|
+
throw new Error(`Failed to establish checkout session (${res.status}). Please log into edX and try again.`);
|
|
154
|
+
}
|
|
155
|
+
const data = yield res.json();
|
|
156
|
+
if (data.error)
|
|
157
|
+
throw new Error(String(data.error));
|
|
158
|
+
return parseSessionResponse(data);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=provision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provision.js","sourceRoot":"","sources":["../../src/utils/provision.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/provision.ts — exchange the edX session for a viax token.\n *\n * {LMS_BASE_URL}/api/edx-session\n * The LMS reads the edX JWT from browser cookies, exchanges it with viax,\n * and returns a per-user token for authenticated API calls.\n *\n * Dev uses POST with a hardcoded JWT payload (local LMS cookie exchange is\n * unavailable). Stage and prod use POST and rely on the browser session cookies.\n */\n\nimport { EnvironmentTypes, getSiteConfig } from '@openedx/frontend-base';\n\nexport interface EdxSessionResult {\n viaxToken: string, // raw JWT — add 'Bearer ' prefix before use\n customerUid: string,\n partyUid?: string,\n edxUserId?: string,\n username?: string,\n email?: string,\n}\n\nexport interface GeolocationResult {\n status: 'resolved' | 'not_found' | 'error',\n ip_address: string,\n ip_address_type: string | null,\n country_code: string | null,\n country_name: string | null,\n region_code: string | null,\n region_name: string | null,\n city: string | null,\n postal_code: string | null,\n latitude: number | null,\n longitude: number | null,\n accuracy_radius_km: number | null,\n confidence: string | null,\n source: string | null,\n cache_status: string | null,\n looked_up_at: string | null,\n fallback_reason: string | null,\n}\n\n/** Hardcoded edX JWT used only for local/dev checkout against stage Viax. */\nconst DEV_SESSION_BODY = {\n payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIiwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MDE4NzAyNSwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkwMTgzNDI1LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJUVl8yNjA5X0hWUEsiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiIyNDlmZDU0MjUwMzcxZTFmOTFkNmU0MjA2MmViMjAzZCIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1ODM3LCJlbWFpbCI6InFrazQyNjEzQGxhb2lhLmNvbSIsIm5hbWUiOiJ0YW52aSB2b3JhIiwiZmFtaWx5X25hbWUiOiIiLCJnaXZlbl9uYW1lIjoiIiwiYWRtaW5pc3RyYXRvciI6ZmFsc2UsInN1cGVydXNlciI6ZmFsc2V9',\n signature: 'DWvWquDIUopCJpd_fPV2e352TBFwy-O9NYfXD0fojH37yQmtYJJOidDUzHtcj53X5PF2GnK0gyTkHxMOPZ7OhWvDgmoWV4qQW6dfgqAYjFrvwh5CaKQqtU1vAXM7i-h6hxjHJNpM2VFWhUIMafU3cD5WmCRC6mD_rr4J6ddNRAbFjretuyrXOcVpKM9_nRMP9zMegcwaiSj4LeZYdDL_e32QwgJrcpwmXTV8YRok-Ouk0rZdFOEPkbHvpNa7wDNv3e14XD_9rRJh2N0eK0vd7vvaBUPQ-UHGOOntFvUJQFaN5JDLtbym-Kx1XATTzKDE9Ti-HjH1qcBUoACNEX1x5w'\n};\n\nconst EDX_JWT_PAYLOAD_COOKIE_NAMES = [\n 'stage-edx-jwt-cookie-header-payload',\n 'edx-jwt-cookie-header-payload',\n];\n\nconst EDX_JWT_SIGNATURE_COOKIE_NAMES = [\n 'stage-edx-jwt-cookie-signature',\n 'edx-jwt-cookie-signature',\n];\n\nfunction getCookieValue(name: string): string | null {\n const cookie = document.cookie\n .split('; ')\n .find((cookiePart) => cookiePart.startsWith(`${name}=`));\n\n if (!cookie) return null;\n\n return decodeURIComponent(cookie.substring(name.length + 1));\n}\n\nfunction getFirstCookieValue(names: string[]): string | null {\n for (const name of names) {\n const value = getCookieValue(name);\n if (value) return value;\n }\n\n return null;\n}\n\nfunction getEdxJwtCookieBody(): { payload: string, signature: string } | null {\n const payload = getFirstCookieValue(EDX_JWT_PAYLOAD_COOKIE_NAMES);\n const signature = getFirstCookieValue(EDX_JWT_SIGNATURE_COOKIE_NAMES);\n\n return payload && signature ? { payload, signature } : null;\n}\n\nfunction parseSessionResponse(data: Record<string, unknown>): EdxSessionResult {\n const viaxToken = (\n data.viaxToken ?? data.token ?? data.access_token ?? data.accessToken\n ) as string | undefined;\n const customerUid = (data.customerUid ?? data.customer_uid) as string | undefined;\n\n if (!viaxToken) throw new Error('Session response missing viax token.');\n if (!customerUid) throw new Error('Session response missing customerUid.');\n\n return {\n viaxToken,\n customerUid,\n partyUid: data.partyUid as string | undefined,\n edxUserId: data.edxUserId as string | undefined,\n username: data.username as string | undefined,\n email: data.email as string | undefined,\n };\n}\n\n/**\n * Fetch the ISO 3166-1 alpha-2 country code from the user's edX account profile.\n * Returns null if the request fails or the field is absent.\n */\nexport async function fetchUserCountry(lmsBaseUrl: string, username: string): Promise<string | null> {\n try {\n const res = await fetch(`${lmsBaseUrl}/api/user/v1/accounts/${encodeURIComponent(username)}`, {\n credentials: 'include',\n });\n if (!res.ok) return null;\n const data = await res.json() as Record<string, unknown>;\n return typeof data.country === 'string' && data.country ? data.country : null;\n } catch {\n return null;\n }\n}\n\n/** Fetch the learner's current public IP for geolocation lookup. */\nexport async function fetchPublicIp(): Promise<string> {\n const res = await fetch('https://api.ipify.org?format=json', {\n headers: { Accept: 'application/json' },\n });\n\n if (!res.ok) {\n throw new Error(`Public IP lookup failed (${res.status}).`);\n }\n\n const data = await res.json() as { ip?: unknown };\n if (typeof data.ip !== 'string' || !data.ip) {\n throw new Error('Public IP lookup returned an invalid address.');\n }\n\n return data.ip;\n}\n\n/** Look up geolocation data for an explicit IP address. */\nexport async function fetchIpGeolocation(\n geolocationUrl: string,\n ipAddress: string,\n): Promise<GeolocationResult> {\n const edxJwtBody = getEdxJwtCookieBody();\n const requestBody = edxJwtBody\n ? { ip_address: ipAddress, ...edxJwtBody }\n : { ip_address: ipAddress };\n\n const res = await fetch(geolocationUrl, {\n method: 'POST',\n credentials: 'include',\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(requestBody),\n });\n\n if (!res.ok) {\n throw new Error(`Geolocation lookup failed (${res.status}).`);\n }\n\n return res.json() as Promise<GeolocationResult>;\n}\n\nfunction isDevelopment(): boolean {\n return getSiteConfig().environment === EnvironmentTypes.DEVELOPMENT;\n}\n\n/** Exchange the learner's edX session (cookies) for a viax token. */\nexport async function fetchEdxSession(sessionUrl: string): Promise<EdxSessionResult> {\n const headers: Record<string, string> = { Accept: 'application/json' };\n\n let res: Response;\n if (isDevelopment()) {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers: {\n ...headers,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(DEV_SESSION_BODY),\n });\n } else {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers,\n });\n }\n\n if (!res.ok) {\n throw new Error(\n `Failed to establish checkout session (${res.status}). Please log into edX and try again.`,\n );\n }\n\n const data = await res.json() as Record<string, unknown> & { error?: string };\n if (data.error) throw new Error(String(data.error));\n\n return parseSessionResponse(data);\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* api/queries.ts — GraphQL documents for the checkout flow.
|
|
3
|
+
* Kept as plain strings so they can be reused, logged, or tested in isolation.
|
|
4
|
+
*
|
|
5
|
+
* Mutation reference (from API doc):
|
|
6
|
+
* updateTwouCustomer — toggle Stripe test clock on/off
|
|
7
|
+
* createTwouCart — user proceeds to payment
|
|
8
|
+
* updateTwouCartItem — attach billing plan / payment method to cart
|
|
9
|
+
* updateTwouCart — persist auto-renewal consent (SUBS-228)
|
|
10
|
+
* startBusinessProcessableTransitionV2 — place order
|
|
11
|
+
* updateTwouCartItem — retry: attach new payment method
|
|
12
|
+
*/
|
|
13
|
+
/** Creates a new cart during checkout. */
|
|
14
|
+
export declare const CREATE_CART = "mutation CreateCart($input: TwouCartInput!) {\n createTwouCart(input: $input) { biId uid }\n}";
|
|
15
|
+
/** Updates a cart item — attach billing plan / payment method. */
|
|
16
|
+
export declare const UPDATE_CART_ITEM = "mutation UpdateCartItem($input: TwouCartItemInput!) {\n updateTwouCartItem(input: $input) { uid }\n}";
|
|
17
|
+
/** Persists auto-renewal consent fields on the TwouCart BI (SUBS-228). */
|
|
18
|
+
export declare const UPDATE_CART = "mutation UpdateCart($input: TwouCartInput!) {\n updateTwouCart(input: $input) { uid }\n}";
|
|
19
|
+
/** Triggers the backend workflow to place the order. */
|
|
20
|
+
export declare const PLACE_ORDER = "mutation PlaceOrder($input: StartBusinessProcessableTransitionInputV2!) {\n startBusinessProcessableTransitionV2(input: $input) { uid bpStatus { code } }\n}";
|
|
21
|
+
export declare const GET_CART_ITEM = "query GetCartItem($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n uid\n hiConsistsOf { ... on TwouCartItem { uid } }\n }\n}";
|
|
22
|
+
export declare const GET_CART_STATUS = "query GetCartStatus($biId: String!) {\n getBusinessInteraction(biId: $biId) {\n bpStatus { code }\n hiConsistsOf { ... on TwouCartItem { tStripeSubscriptionId } }\n ... on TwouCart {\n tStripeConfirmationRedirectUrl\n tPaypalApprovalUrl\n cmComments { edges { node { comText } } }\n }\n }\n}";
|
|
23
|
+
export declare const FIND_SUBSCRIPTION = "query FindSubscription($filter: String!) {\n filterTwouSubscription(_first: 1, _filter: $filter) {\n edges {\n node {\n biId\n tRenewalType\n hiConsistsOf {\n ... on TwouSubscriptionItem {\n paybBillingPlan {\n tStripePriceId\n bipOwnershipModel { omName }\n }\n paybPayment {\n ... on TwouStripePayment {\n tStripePaymentMethodId\n tPaymentMethodLast4\n tPaymentMethodBrand\n }\n }\n }\n }\n }\n }\n }\n}";
|
|
24
|
+
/** Fetch the viax customer uid by email using filterTwouCustomer. */
|
|
25
|
+
export declare const GET_CUSTOMER = "query GetCustomer($filter: String!) {\n filterTwouCustomer(_first: 1, _filter: $filter) {\n edges {\n node {\n uid\n parId\n parParty {\n paName\n paContacts {\n edges {\n node {\n ... on EmailAddressContactMedium {\n email\n }\n }\n }\n }\n ... on Individual {\n indFirstName\n indLastName\n }\n }\n }\n }\n }\n}";
|
|
26
|
+
/** Fetch the TwouSubscriptionPlan uid to use as biiSalable in createTwouCart. */
|
|
27
|
+
export declare const GET_PRODUCT = "query GetProduct($maId: String!) {\n getProduct(maId: $maId) {\n maName\n uid\n ... on TwouSubscriptionPlan {\n saOwnershipModels {\n uid\n omName\n }\n }\n }\n}";
|
|
28
|
+
export declare const GET_BILLING_PLANS = "query GetBillingPlans {\n filterCustomerBillingPlan(_first: 10) {\n edges {\n node {\n uid\n tStripePriceId\n tStripeAmount {\n amount\n units { code }\n }\n bipIsTemplate\n bipOwnershipModel { uid omName }\n }\n }\n }\n}";
|
|
29
|
+
/** Fetch the number of active subscriptions — unauthenticated. */
|
|
30
|
+
export declare const GET_ACTIVE_SUBSCRIPTIONS_STATE = "query activeSubscriptions {\n tActiveSubscriptionsState\n}";
|
|
31
|
+
/** Check whether the currently authenticated user already has an active subscription.
|
|
32
|
+
* tIsCurrentUserSubscriber is a JSON scalar — returns the full object, no subfield selection allowed. */
|
|
33
|
+
export declare const GET_IS_CURRENT_USER_SUBSCRIBER = "query IsCurrentUserSubscriber {\n tIsCurrentUserSubscriber\n}";
|
|
34
|
+
/** Fetch a course for the enrollment card when ?course_run_key= is present. */
|
|
35
|
+
export declare const GET_COURSE_DETAIL = "query CourseDetail($filter: String!) {\n filterTwouCourse(_first: 1, _filter: $filter) {\n edges {\n node {\n maId\n maName\n maDescription\n tShortDescription\n imageUrl\n tCourseKey\n tBrandText\n tPrimarySubjectArea\n tLevelType\n tLanguage\n tPacingType { code }\n tDurationLow\n tDurationHigh\n tDurationUnit { code }\n tEffortLow\n tEffortHigh\n tEffortUnit { code }\n tUrlCourse\n tB2cSubscriptionInclusion\n tHasOfacRestrictions\n tCatalogActive\n }\n }\n }\n}";
|