@edx/frontend-app-alacarte-checkout 1.10.0 → 1.11.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.
@@ -1,9 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
2
3
  import { Alert, Container } from '@openedx/paragon';
3
4
  import { getAppConfig, getSiteConfig } from '@openedx/frontend-base';
4
5
  import { appId } from '../constants';
5
6
  import CheckoutGate from '../components/CheckoutGate/CheckoutGate';
7
+ import DemoPdpBanner from '../components/DemoPdpBanner/DemoPdpBanner';
6
8
  const PageWrapper = ({ children }) => (_jsx(Container, { children: children }));
9
+ // TEMPORARY — Set to true to show the demo PDP banner before checkout.
10
+ // When true, the banner is displayed first; clicking "Earn a certificate"
11
+ // transitions to the real checkout flow. Set to false to skip the banner.
12
+ const SHOW_DEMO_PDP = true;
7
13
  const CheckoutPage = () => {
8
14
  const config = getAppConfig(appId);
9
15
  const siteConfig = getSiteConfig();
@@ -13,6 +19,12 @@ const CheckoutPage = () => {
13
19
  const isDevelopment = siteConfig.environment === 'development';
14
20
  const edxSessionUrl = `${isDevelopment ? 'https://courses.stage.edx.org' : LMS_BASE_URL}/api/edx-session`;
15
21
  const geolocationUrl = `${isDevelopment ? 'https://courses.stage.edx.org' : LMS_BASE_URL}/api/geolocation/lookup`;
22
+ // When the demo banner is enabled, start on the "PDP" view; clicking the
23
+ // CTA flips showCheckout to true and the real checkout renders.
24
+ const [showCheckout, setShowCheckout] = useState(!SHOW_DEMO_PDP);
25
+ if (!showCheckout) {
26
+ return _jsx(DemoPdpBanner, { onEnroll: () => setShowCheckout(true) });
27
+ }
16
28
  if (!stripePublishableKey || !apiUrl || !edxSessionUrl) {
17
29
  return (_jsx(PageWrapper, { children: _jsxs(Alert, { variant: "danger", children: ["We're having trouble loading the checkout. Please refresh the page or try again in a few moments. If the problem persists,", ' ', _jsx("a", { href: "https://courses.edx.org/support/contact_us", children: "contact support" }), "."] }) }));
18
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"CheckoutPage.js","sourceRoot":"","sources":["../../src/checkoutPage/CheckoutPage.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,YAAY,MAAM,yCAAyC,CAAC;AAEnE,MAAM,WAAW,GAA4C,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAC7E,KAAC,SAAS,cAAE,QAAQ,GAAa,CAClC,CAAC;AAEF,MAAM,YAAY,GAAa,GAAG,EAAE;IAClC,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,MAAM,YAAY,GAAG,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC9D,MAAM,oBAAoB,GAAG,MAAM,CAAC,iBAA2B,CAAC;IAChE,MAAM,MAAM,GAAG,MAAM,CAAC,YAAsB,CAAC;IAC7C,MAAM,aAAa,GAAG,UAAU,CAAC,WAAW,KAAK,aAAa,CAAC;IAC/D,MAAM,aAAa,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,YAAY,kBAAkB,CAAC;IAC1G,MAAM,cAAc,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,YAAY,yBAAyB,CAAC;IAElH,IAAI,CAAC,oBAAoB,IAAI,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QACvD,OAAO,CACL,KAAC,WAAW,cACV,MAAC,KAAK,IAAC,OAAO,EAAC,QAAQ,2IAEwC,GAAG,EAChE,YAAG,IAAI,EAAC,4CAA4C,gCAEhD,SAEE,GACI,CACf,CAAC;IACJ,CAAC;IAED,OAAO,CACL,KAAC,WAAW,cACV,KAAC,YAAY,IACX,oBAAoB,EAAE,oBAAoB,EAC1C,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,aAAa,EAC5B,cAAc,EAAE,cAAc,EAC9B,mBAAmB,EAAE,GAAG,YAAY,YAAY,EAChD,OAAO,EAAE,YAAY,IAAI,GAAG,EAC5B,UAAU,EAAE,CAAC,iBAAiB,EAAE,EAAE;gBAChC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;gBAC/B,kFAAkF;gBAClF,kBAAkB;YACpB,CAAC,GACD,GACU,CACf,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,YAAY,CAAC","sourcesContent":["import React from 'react';\nimport { Alert, Container } from '@openedx/paragon';\nimport { getAppConfig, getSiteConfig } from '@openedx/frontend-base';\nimport { appId } from '@src/constants';\nimport CheckoutGate from '../components/CheckoutGate/CheckoutGate';\n\nconst PageWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (\n <Container>{children}</Container>\n);\n\nconst CheckoutPage: React.FC = () => {\n const config = getAppConfig(appId);\n const siteConfig = getSiteConfig();\n const LMS_BASE_URL = siteConfig.lmsBaseUrl.replace(/\\/$/, '');\n const stripePublishableKey = config.STRIPE_PUBLIC_KEY as string;\n const apiUrl = config.VIAX_API_URL as string;\n const isDevelopment = siteConfig.environment === 'development';\n const edxSessionUrl = `${isDevelopment ? 'https://courses.stage.edx.org' : LMS_BASE_URL}/api/edx-session`;\n const geolocationUrl = `${isDevelopment ? 'https://courses.stage.edx.org' : LMS_BASE_URL}/api/geolocation/lookup`;\n\n if (!stripePublishableKey || !apiUrl || !edxSessionUrl) {\n return (\n <PageWrapper>\n <Alert variant=\"danger\">\n We&apos;re having trouble loading the checkout. Please refresh the\n page or try again in a few moments. If the problem persists,{' '}\n <a href=\"https://courses.edx.org/support/contact_us\">\n contact support\n </a>\n .\n </Alert>\n </PageWrapper>\n );\n }\n\n return (\n <PageWrapper>\n <CheckoutGate\n stripePublishableKey={stripePublishableKey}\n apiUrl={apiUrl}\n edxSessionUrl={edxSessionUrl}\n geolocationUrl={geolocationUrl}\n learnerDashboardUrl={`${LMS_BASE_URL}/dashboard`}\n homeUrl={LMS_BASE_URL || '/'}\n onComplete={(_subscriptionBiId) => {\n console.log(_subscriptionBiId);\n // window.location.href = `/checkout/confirmation?sub=${_subscriptionBiId ?? ''}`;\n // Success screen.\n }}\n />\n </PageWrapper>\n );\n};\n\nexport default CheckoutPage;\n"]}
1
+ {"version":3,"file":"CheckoutPage.js","sourceRoot":"","sources":["../../src/checkoutPage/CheckoutPage.tsx"],"names":[],"mappings":";AAAA,OAAc,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,YAAY,MAAM,yCAAyC,CAAC;AACnE,OAAO,aAAa,MAAM,2CAA2C,CAAC;AAEtE,MAAM,WAAW,GAA4C,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAC7E,KAAC,SAAS,cAAE,QAAQ,GAAa,CAClC,CAAC;AAEF,uEAAuE;AACvE,0EAA0E;AAC1E,0EAA0E;AAC1E,MAAM,aAAa,GAAG,IAAI,CAAC;AAE3B,MAAM,YAAY,GAAa,GAAG,EAAE;IAClC,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,MAAM,YAAY,GAAG,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC9D,MAAM,oBAAoB,GAAG,MAAM,CAAC,iBAA2B,CAAC;IAChE,MAAM,MAAM,GAAG,MAAM,CAAC,YAAsB,CAAC;IAC7C,MAAM,aAAa,GAAG,UAAU,CAAC,WAAW,KAAK,aAAa,CAAC;IAC/D,MAAM,aAAa,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,YAAY,kBAAkB,CAAC;IAC1G,MAAM,cAAc,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,YAAY,yBAAyB,CAAC;IAElH,yEAAyE;IACzE,gEAAgE;IAChE,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,CAAC,aAAa,CAAC,CAAC;IAEjE,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,KAAC,aAAa,IAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,GAAI,CAAC;IAClE,CAAC;IAED,IAAI,CAAC,oBAAoB,IAAI,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QACvD,OAAO,CACL,KAAC,WAAW,cACV,MAAC,KAAK,IAAC,OAAO,EAAC,QAAQ,2IAEwC,GAAG,EAChE,YAAG,IAAI,EAAC,4CAA4C,gCAEhD,SAEE,GACI,CACf,CAAC;IACJ,CAAC;IAED,OAAO,CACL,KAAC,WAAW,cACV,KAAC,YAAY,IACX,oBAAoB,EAAE,oBAAoB,EAC1C,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,aAAa,EAC5B,cAAc,EAAE,cAAc,EAC9B,mBAAmB,EAAE,GAAG,YAAY,YAAY,EAChD,OAAO,EAAE,YAAY,IAAI,GAAG,EAC5B,UAAU,EAAE,CAAC,iBAAiB,EAAE,EAAE;gBAChC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;gBAC/B,kFAAkF;gBAClF,kBAAkB;YACpB,CAAC,GACD,GACU,CACf,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,YAAY,CAAC","sourcesContent":["import React, { useState } from 'react';\nimport { Alert, Container } from '@openedx/paragon';\nimport { getAppConfig, getSiteConfig } from '@openedx/frontend-base';\nimport { appId } from '@src/constants';\nimport CheckoutGate from '../components/CheckoutGate/CheckoutGate';\nimport DemoPdpBanner from '../components/DemoPdpBanner/DemoPdpBanner';\n\nconst PageWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => (\n <Container>{children}</Container>\n);\n\n// TEMPORARY — Set to true to show the demo PDP banner before checkout.\n// When true, the banner is displayed first; clicking \"Earn a certificate\"\n// transitions to the real checkout flow. Set to false to skip the banner.\nconst SHOW_DEMO_PDP = true;\n\nconst CheckoutPage: React.FC = () => {\n const config = getAppConfig(appId);\n const siteConfig = getSiteConfig();\n const LMS_BASE_URL = siteConfig.lmsBaseUrl.replace(/\\/$/, '');\n const stripePublishableKey = config.STRIPE_PUBLIC_KEY as string;\n const apiUrl = config.VIAX_API_URL as string;\n const isDevelopment = siteConfig.environment === 'development';\n const edxSessionUrl = `${isDevelopment ? 'https://courses.stage.edx.org' : LMS_BASE_URL}/api/edx-session`;\n const geolocationUrl = `${isDevelopment ? 'https://courses.stage.edx.org' : LMS_BASE_URL}/api/geolocation/lookup`;\n\n // When the demo banner is enabled, start on the \"PDP\" view; clicking the\n // CTA flips showCheckout to true and the real checkout renders.\n const [showCheckout, setShowCheckout] = useState(!SHOW_DEMO_PDP);\n\n if (!showCheckout) {\n return <DemoPdpBanner onEnroll={() => setShowCheckout(true)} />;\n }\n\n if (!stripePublishableKey || !apiUrl || !edxSessionUrl) {\n return (\n <PageWrapper>\n <Alert variant=\"danger\">\n We&apos;re having trouble loading the checkout. Please refresh the\n page or try again in a few moments. If the problem persists,{' '}\n <a href=\"https://courses.edx.org/support/contact_us\">\n contact support\n </a>\n .\n </Alert>\n </PageWrapper>\n );\n }\n\n return (\n <PageWrapper>\n <CheckoutGate\n stripePublishableKey={stripePublishableKey}\n apiUrl={apiUrl}\n edxSessionUrl={edxSessionUrl}\n geolocationUrl={geolocationUrl}\n learnerDashboardUrl={`${LMS_BASE_URL}/dashboard`}\n homeUrl={LMS_BASE_URL || '/'}\n onComplete={(_subscriptionBiId) => {\n console.log(_subscriptionBiId);\n // window.location.href = `/checkout/confirmation?sub=${_subscriptionBiId ?? ''}`;\n // Success screen.\n }}\n />\n </PageWrapper>\n );\n};\n\nexport default CheckoutPage;\n"]}
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ declare global {
3
+ interface Window {
4
+ activeProgramId?: string;
5
+ activeCourseUid?: string;
6
+ activeCourseRunKey?: string;
7
+ activeDiscountCode?: string;
8
+ }
9
+ }
10
+ interface DemoPdpBannerProps {
11
+ onEnroll: () => void;
12
+ }
13
+ declare const DemoPdpBanner: React.FC<DemoPdpBannerProps>;
14
+ export default DemoPdpBanner;
@@ -0,0 +1,34 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ const DEFAULT_PROGRAM_UID = '01a06551-667b-7c85-9286-f6a60b9eb287';
3
+ const DEFAULT_DISCOUNT_CODE = '';
4
+ function buildCheckoutUrl() {
5
+ var _a;
6
+ const programUid = window.activeProgramId || DEFAULT_PROGRAM_UID;
7
+ const courseUid = window.activeCourseUid || '';
8
+ const courseRunKey = window.activeCourseRunKey || '';
9
+ const discountCode = (_a = window.activeDiscountCode) !== null && _a !== void 0 ? _a : DEFAULT_DISCOUNT_CODE;
10
+ const params = new URLSearchParams();
11
+ if (courseUid && courseRunKey) {
12
+ params.set('course_uid', courseUid);
13
+ params.set('course_run_key', courseRunKey);
14
+ }
15
+ else {
16
+ params.set('program_uid', programUid);
17
+ }
18
+ if (discountCode) {
19
+ params.set('discount_code', discountCode);
20
+ }
21
+ return `/aca-checkout?${params.toString()}`;
22
+ }
23
+ const HarvardShield = () => (_jsxs("svg", { width: "30", height: "30", viewBox: "0 0 30 30", fill: "none", "aria-hidden": "true", children: [_jsx("rect", { width: "30", height: "30", rx: "2", fill: "#A41034" }), _jsx("text", { x: "15", y: "20", textAnchor: "middle", fill: "#fff", fontSize: "16", fontWeight: "700", fontFamily: "serif", children: "H" })] }));
24
+ const DemoPdpBanner = ({ onEnroll }) => {
25
+ const handleEarnCertificate = (e) => {
26
+ e.preventDefault();
27
+ const url = buildCheckoutUrl();
28
+ window.history.pushState({}, '', url);
29
+ onEnroll();
30
+ };
31
+ return (_jsxs("div", { className: "demo-pdp", children: [_jsxs("div", { className: "demo-pdp__hero", children: [_jsx("div", { className: "demo-pdp__hero-bg" }), _jsxs("div", { className: "demo-pdp__hero-content", children: [_jsxs("div", { className: "demo-pdp__main", children: [_jsxs("div", { className: "demo-pdp__institution", children: [_jsx(HarvardShield, {}), _jsxs("span", { className: "demo-pdp__institution-name", children: ["Harvard", _jsx("br", {}), "University"] })] }), _jsxs("h1", { className: "demo-pdp__title", children: ["Computer Science for", _jsx("br", {}), "Python Programming"] }), _jsxs("p", { className: "demo-pdp__meta", children: [_jsx("strong", { children: "2 courses" }), ", 6 months"] }), _jsx("button", { type: "button", className: "demo-pdp__cta-primary", onClick: handleEarnCertificate, children: "Earn a certificate" }), _jsxs("label", { className: "demo-pdp__consent", children: [_jsx("input", { type: "checkbox", className: "demo-pdp__checkbox", defaultChecked: false }), _jsx("span", { className: "demo-pdp__consent-text", children: "I would like to receive email from HarvardX and learn about other offerings related to Computer Science for Python Programming." })] })] }), _jsx("div", { className: "demo-pdp__sidebar", children: _jsxs("div", { className: "demo-pdp__card", children: [_jsx("p", { className: "demo-pdp__card-eyebrow", children: "Earn a certificate" }), _jsxs("h2", { className: "demo-pdp__card-title", children: ["Computer Science for", _jsx("br", {}), "Python Programming"] }), _jsxs("p", { className: "demo-pdp__card-enrollments", children: [_jsx("strong", { children: "9,341,307" }), " learners enrolled"] }), _jsxs("div", { className: "demo-pdp__card-courses", children: [_jsx("p", { className: "demo-pdp__card-courses-label", children: "There are 2 courses in this program" }), _jsxs("ul", { className: "demo-pdp__card-courses-list", children: [_jsx("li", { children: "CS50's Introduction to Computer Science" }), _jsx("li", { children: "CS50's Introduction to Programming with Python" })] })] }), _jsxs("div", { className: "demo-pdp__card-pricing", children: [_jsx("span", { className: "demo-pdp__card-price-original", children: "$618 USD" }), _jsx("span", { className: "demo-pdp__card-price-current", children: "$220" })] }), _jsx("button", { type: "button", className: "demo-pdp__cta-sidebar", onClick: handleEarnCertificate, children: "Earn a certificate" })] }) })] })] }), _jsxs("div", { className: "demo-pdp__features", children: [_jsxs("div", { className: "demo-pdp__feature", children: [_jsxs("svg", { className: "demo-pdp__feature-icon", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", children: [_jsx("circle", { cx: "12", cy: "12", r: "10", stroke: "#fff", strokeWidth: "1.5" }), _jsx("path", { d: "M12 7v5l3 3", stroke: "#fff", strokeWidth: "1.5", strokeLinecap: "round" })] }), _jsxs("div", { children: [_jsx("p", { className: "demo-pdp__feature-title", children: "Introductory" }), _jsx("p", { className: "demo-pdp__feature-desc", children: "No prior experience required" })] })] }), _jsxs("div", { className: "demo-pdp__feature", children: [_jsxs("svg", { className: "demo-pdp__feature-icon", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", children: [_jsx("path", { d: "M12 2L2 7l10 5 10-5-10-5z", stroke: "#fff", strokeWidth: "1.5", strokeLinejoin: "round" }), _jsx("path", { d: "M2 17l10 5 10-5M2 12l10 5 10-5", stroke: "#fff", strokeWidth: "1.5", strokeLinejoin: "round" })] }), _jsxs("div", { children: [_jsx("p", { className: "demo-pdp__feature-title", children: "Self-paced" }), _jsx("p", { className: "demo-pdp__feature-desc", children: "Progress at your own speed" })] })] }), _jsxs("div", { className: "demo-pdp__feature", children: [_jsxs("svg", { className: "demo-pdp__feature-icon", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", children: [_jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2", stroke: "#fff", strokeWidth: "1.5" }), _jsx("path", { d: "M3 10h18", stroke: "#fff", strokeWidth: "1.5" }), _jsx("path", { d: "M8 2v4M16 2v4", stroke: "#fff", strokeWidth: "1.5", strokeLinecap: "round" })] }), _jsxs("div", { children: [_jsx("p", { className: "demo-pdp__feature-title", children: "8 months" }), _jsx("p", { className: "demo-pdp__feature-desc", children: "1 - 14 hours per week" })] })] })] })] }));
32
+ };
33
+ export default DemoPdpBanner;
34
+ //# sourceMappingURL=DemoPdpBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DemoPdpBanner.js","sourceRoot":"","sources":["../../../src/components/DemoPdpBanner/DemoPdpBanner.tsx"],"names":[],"mappings":"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","sourcesContent":["import React from 'react';\n\ndeclare global {\n interface Window {\n activeProgramId?: string,\n activeCourseUid?: string,\n activeCourseRunKey?: string,\n activeDiscountCode?: string,\n }\n}\n\nconst DEFAULT_PROGRAM_UID = '01a06551-667b-7c85-9286-f6a60b9eb287';\nconst DEFAULT_DISCOUNT_CODE = '';\n\nfunction buildCheckoutUrl(): string {\n const programUid = window.activeProgramId || DEFAULT_PROGRAM_UID;\n const courseUid = window.activeCourseUid || '';\n const courseRunKey = window.activeCourseRunKey || '';\n const discountCode = window.activeDiscountCode ?? DEFAULT_DISCOUNT_CODE;\n\n const params = new URLSearchParams();\n\n if (courseUid && courseRunKey) {\n params.set('course_uid', courseUid);\n params.set('course_run_key', courseRunKey);\n } else {\n params.set('program_uid', programUid);\n }\n\n if (discountCode) {\n params.set('discount_code', discountCode);\n }\n\n return `/aca-checkout?${params.toString()}`;\n}\n\nconst HarvardShield = () => (\n <svg width=\"30\" height=\"30\" viewBox=\"0 0 30 30\" fill=\"none\" aria-hidden=\"true\">\n <rect width=\"30\" height=\"30\" rx=\"2\" fill=\"#A41034\" />\n <text x=\"15\" y=\"20\" textAnchor=\"middle\" fill=\"#fff\" fontSize=\"16\" fontWeight=\"700\" fontFamily=\"serif\">H</text>\n </svg>\n);\n\ninterface DemoPdpBannerProps {\n onEnroll: () => void,\n}\n\nconst DemoPdpBanner: React.FC<DemoPdpBannerProps> = ({ onEnroll }) => {\n const handleEarnCertificate = (e: React.MouseEvent) => {\n e.preventDefault();\n const url = buildCheckoutUrl();\n window.history.pushState({}, '', url);\n onEnroll();\n };\n\n return (\n <div className=\"demo-pdp\">\n {/* ── Hero Section ── */}\n <div className=\"demo-pdp__hero\">\n <div className=\"demo-pdp__hero-bg\" />\n <div className=\"demo-pdp__hero-content\">\n <div className=\"demo-pdp__main\">\n <div className=\"demo-pdp__institution\">\n <HarvardShield />\n <span className=\"demo-pdp__institution-name\">\n Harvard<br />University\n </span>\n </div>\n\n <h1 className=\"demo-pdp__title\">\n Computer Science for<br />Python Programming\n </h1>\n\n <p className=\"demo-pdp__meta\">\n <strong>2 courses</strong>, 6 months\n </p>\n\n <button\n type=\"button\"\n className=\"demo-pdp__cta-primary\"\n onClick={handleEarnCertificate}\n >\n Earn a certificate\n </button>\n\n {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}\n <label className=\"demo-pdp__consent\">\n <input type=\"checkbox\" className=\"demo-pdp__checkbox\" defaultChecked={false} />\n <span className=\"demo-pdp__consent-text\">\n I would like to receive email from HarvardX and learn about other offerings related to\n Computer Science for Python Programming.\n </span>\n </label>\n </div>\n\n {/* ── Right sidebar card ── */}\n <div className=\"demo-pdp__sidebar\">\n <div className=\"demo-pdp__card\">\n <p className=\"demo-pdp__card-eyebrow\">Earn a certificate</p>\n <h2 className=\"demo-pdp__card-title\">\n Computer Science for<br />Python Programming\n </h2>\n <p className=\"demo-pdp__card-enrollments\">\n <strong>9,341,307</strong> learners enrolled\n </p>\n\n <div className=\"demo-pdp__card-courses\">\n <p className=\"demo-pdp__card-courses-label\">\n There are 2 courses in this program\n </p>\n <ul className=\"demo-pdp__card-courses-list\">\n <li>CS50&apos;s Introduction to Computer Science</li>\n <li>CS50&apos;s Introduction to Programming with Python</li>\n </ul>\n </div>\n\n <div className=\"demo-pdp__card-pricing\">\n <span className=\"demo-pdp__card-price-original\">$618 USD</span>\n <span className=\"demo-pdp__card-price-current\">$220</span>\n </div>\n\n <button\n type=\"button\"\n className=\"demo-pdp__cta-sidebar\"\n onClick={handleEarnCertificate}\n >\n Earn a certificate\n </button>\n </div>\n </div>\n </div>\n </div>\n\n {/* ── Bottom features bar ── */}\n <div className=\"demo-pdp__features\">\n <div className=\"demo-pdp__feature\">\n <svg className=\"demo-pdp__feature-icon\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"#fff\" strokeWidth=\"1.5\" />\n <path d=\"M12 7v5l3 3\" stroke=\"#fff\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n <div>\n <p className=\"demo-pdp__feature-title\">Introductory</p>\n <p className=\"demo-pdp__feature-desc\">No prior experience required</p>\n </div>\n </div>\n <div className=\"demo-pdp__feature\">\n <svg className=\"demo-pdp__feature-icon\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\">\n <path d=\"M12 2L2 7l10 5 10-5-10-5z\" stroke=\"#fff\" strokeWidth=\"1.5\" strokeLinejoin=\"round\" />\n <path d=\"M2 17l10 5 10-5M2 12l10 5 10-5\" stroke=\"#fff\" strokeWidth=\"1.5\" strokeLinejoin=\"round\" />\n </svg>\n <div>\n <p className=\"demo-pdp__feature-title\">Self-paced</p>\n <p className=\"demo-pdp__feature-desc\">Progress at your own speed</p>\n </div>\n </div>\n <div className=\"demo-pdp__feature\">\n <svg className=\"demo-pdp__feature-icon\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\" stroke=\"#fff\" strokeWidth=\"1.5\" />\n <path d=\"M3 10h18\" stroke=\"#fff\" strokeWidth=\"1.5\" />\n <path d=\"M8 2v4M16 2v4\" stroke=\"#fff\" strokeWidth=\"1.5\" strokeLinecap=\"round\" />\n </svg>\n <div>\n <p className=\"demo-pdp__feature-title\">8 months</p>\n <p className=\"demo-pdp__feature-desc\">1 - 14 hours per week</p>\n </div>\n </div>\n </div>\n </div>\n );\n};\n\nexport default DemoPdpBanner;\n"]}
package/dist/style.scss CHANGED
@@ -18,3 +18,4 @@
18
18
  @use "./styles/components/alacarteNotice.scss";
19
19
  @use "./styles/components/alacarteConfirmation.scss";
20
20
  @use "./styles/components/alacarteDebugPanel.scss"; // TEMPORARY — remove alongside AlacarteDebugPanel.
21
+ @use "./styles/components/demoPdpBanner.scss"; // TEMPORARY — demo PDP banner for checkout demo.
@@ -0,0 +1,262 @@
1
+ .demo-pdp {
2
+ margin: -1rem -1rem 0;
3
+ font-family: Inter, system-ui, -apple-system, sans-serif;
4
+
5
+ // ── Hero ──────────────────────────────────────────────────────────────────
6
+ &__hero {
7
+ position: relative;
8
+ background: #1c3325;
9
+ overflow: hidden;
10
+ }
11
+
12
+ &__hero-bg {
13
+ position: absolute;
14
+ inset: 0;
15
+ background:
16
+ radial-gradient(ellipse 60% 80% at 80% 40%, rgba(46, 80, 56, 0.9) 0%, transparent 70%),
17
+ radial-gradient(ellipse 40% 60% at 20% 60%, rgba(36, 65, 46, 0.7) 0%, transparent 60%);
18
+ pointer-events: none;
19
+ }
20
+
21
+ &__hero-content {
22
+ position: relative;
23
+ max-width: 1200px;
24
+ margin: 0 auto;
25
+ padding: 48px 40px 40px;
26
+ display: flex;
27
+ gap: 40px;
28
+ align-items: flex-start;
29
+ }
30
+
31
+ &__main {
32
+ flex: 1;
33
+ min-width: 0;
34
+ }
35
+
36
+ // ── Institution badge ─────────────────────────────────────────────────────
37
+ &__institution {
38
+ display: flex;
39
+ align-items: center;
40
+ gap: 10px;
41
+ margin-bottom: 24px;
42
+ }
43
+
44
+ &__institution-name {
45
+ color: #fff;
46
+ font-size: 0.8rem;
47
+ line-height: 1.25;
48
+ font-weight: 500;
49
+ letter-spacing: 0.02em;
50
+ }
51
+
52
+ // ── Title & meta ──────────────────────────────────────────────────────────
53
+ &__title {
54
+ color: #fff;
55
+ font-size: clamp(1.75rem, 3.5vw, 2.5rem);
56
+ font-weight: 700;
57
+ line-height: 1.2;
58
+ margin: 0 0 12px;
59
+ letter-spacing: -0.01em;
60
+ }
61
+
62
+ &__meta {
63
+ color: rgba(255, 255, 255, 0.85);
64
+ font-size: 1rem;
65
+ margin: 0 0 24px;
66
+
67
+ strong {
68
+ color: #fff;
69
+ }
70
+ }
71
+
72
+ // ── CTA buttons ───────────────────────────────────────────────────────────
73
+ &__cta-primary {
74
+ display: inline-block;
75
+ background: #3a7d44;
76
+ color: #fff;
77
+ font-weight: 600;
78
+ font-size: 0.9rem;
79
+ padding: 10px 24px;
80
+ border: none;
81
+ border-radius: 4px;
82
+ cursor: pointer;
83
+ transition: background 0.15s;
84
+ margin-bottom: 20px;
85
+
86
+ &:hover {
87
+ background: #2d6335;
88
+ }
89
+ }
90
+
91
+ // ── Consent checkbox ──────────────────────────────────────────────────────
92
+ &__consent {
93
+ display: flex;
94
+ align-items: flex-start;
95
+ gap: 8px;
96
+ cursor: pointer;
97
+ max-width: 480px;
98
+ }
99
+
100
+ &__checkbox {
101
+ margin-top: 3px;
102
+ flex-shrink: 0;
103
+ accent-color: #3a7d44;
104
+ }
105
+
106
+ &__consent-text {
107
+ color: rgba(255, 255, 255, 0.7);
108
+ font-size: 0.75rem;
109
+ line-height: 1.5;
110
+ }
111
+
112
+ // ── Sidebar card ──────────────────────────────────────────────────────────
113
+ &__sidebar {
114
+ flex-shrink: 0;
115
+ width: 320px;
116
+ }
117
+
118
+ &__card {
119
+ background: #fff;
120
+ border-radius: 8px;
121
+ padding: 24px;
122
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
123
+ }
124
+
125
+ &__card-eyebrow {
126
+ font-size: 0.75rem;
127
+ color: #666;
128
+ margin: 0 0 4px;
129
+ text-transform: none;
130
+ }
131
+
132
+ &__card-title {
133
+ font-size: 1.1rem;
134
+ font-weight: 700;
135
+ color: #1a1a1a;
136
+ margin: 0 0 8px;
137
+ line-height: 1.3;
138
+ }
139
+
140
+ &__card-enrollments {
141
+ font-size: 0.78rem;
142
+ color: #666;
143
+ margin: 0 0 16px;
144
+
145
+ strong {
146
+ color: #333;
147
+ }
148
+ }
149
+
150
+ &__card-courses {
151
+ margin-bottom: 16px;
152
+ }
153
+
154
+ &__card-courses-label {
155
+ background: #e8f5e9;
156
+ border-left: 3px solid #3a7d44;
157
+ padding: 8px 12px;
158
+ font-size: 0.78rem;
159
+ color: #2d6335;
160
+ margin: 0 0 8px;
161
+ font-weight: 500;
162
+ }
163
+
164
+ &__card-courses-list {
165
+ margin: 0;
166
+ padding-left: 20px;
167
+ font-size: 0.8rem;
168
+ color: #333;
169
+ line-height: 1.7;
170
+ }
171
+
172
+ &__card-pricing {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: 12px;
176
+ margin-bottom: 16px;
177
+ }
178
+
179
+ &__card-price-original {
180
+ font-size: 0.85rem;
181
+ color: #999;
182
+ text-decoration: line-through;
183
+ }
184
+
185
+ &__card-price-current {
186
+ font-size: 1.25rem;
187
+ font-weight: 700;
188
+ color: #1a1a1a;
189
+ }
190
+
191
+ &__cta-sidebar {
192
+ display: block;
193
+ width: 100%;
194
+ background: #c44715;
195
+ color: #fff;
196
+ font-weight: 700;
197
+ font-size: 0.9rem;
198
+ padding: 12px 24px;
199
+ border: none;
200
+ border-radius: 4px;
201
+ cursor: pointer;
202
+ text-align: center;
203
+ transition: background 0.15s;
204
+
205
+ &:hover {
206
+ background: #a93b10;
207
+ }
208
+ }
209
+
210
+ // ── Features bar ──────────────────────────────────────────────────────────
211
+ &__features {
212
+ background: #111;
213
+ display: flex;
214
+ justify-content: center;
215
+ gap: 60px;
216
+ padding: 20px 40px;
217
+ }
218
+
219
+ &__feature {
220
+ display: flex;
221
+ align-items: center;
222
+ gap: 12px;
223
+ }
224
+
225
+ &__feature-icon {
226
+ flex-shrink: 0;
227
+ opacity: 0.8;
228
+ }
229
+
230
+ &__feature-title {
231
+ color: #fff;
232
+ font-size: 0.85rem;
233
+ font-weight: 600;
234
+ margin: 0;
235
+ line-height: 1.3;
236
+ }
237
+
238
+ &__feature-desc {
239
+ color: rgba(255, 255, 255, 0.6);
240
+ font-size: 0.72rem;
241
+ margin: 0;
242
+ line-height: 1.3;
243
+ }
244
+
245
+ // ── Responsive ────────────────────────────────────────────────────────────
246
+ @media (max-width: 768px) {
247
+ &__hero-content {
248
+ flex-direction: column;
249
+ padding: 32px 20px 28px;
250
+ }
251
+
252
+ &__sidebar {
253
+ width: 100%;
254
+ }
255
+
256
+ &__features {
257
+ flex-direction: column;
258
+ gap: 16px;
259
+ padding: 20px;
260
+ }
261
+ }
262
+ }
@@ -21,8 +21,8 @@ import { EnvironmentTypes, getAppConfig, getSiteConfig } from '@openedx/frontend
21
21
  import { appId } from '../constants';
22
22
  /** Hardcoded edX JWT used only for local/dev checkout against stage Viax. */
23
23
  const DEV_SESSION_BODY = {
24
- payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIiwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MDc2Njk2MCwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkwNzYzMzYwLCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJOU18yNjA5X1dLMzEiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiI4MjE3ZWI5MTk0YTM1MGJiODVmYmUxMGUzNWFhYWNkZiIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1MzcyLCJlbWFpbCI6Im5zb3Jva2FAMnUuY29tIiwibmFtZSI6Ik5pY29sYXMgU29yb2thIiwiZmFtaWx5X25hbWUiOiIiLCJnaXZlbl9uYW1lIjoiIiwiYWRtaW5pc3RyYXRvciI6ZmFsc2UsInN1cGVydXNlciI6ZmFsc2V9',
25
- signature: 'UOeR4ZPyFsfMZe8vSzhA4jY_6jBd0cU-PVNNHbf5z4OdIw5D0itG5uCU_Dn_ffJRpouw6mcWnCR1XMnfUShxeLKmU0Fncobx_Ei1G3_ntscMhAe3xXicSoQp3R2O6hq199egiMFBA6XoSIRJpIr5Wc9uukNKJn_9cS5Flmacn8arHTlptpcGpjBl4LdgTI6U2yAlQQkHjidd5xM0QF0QiwKTtUCTa-iAfJDtWn72DWayfaHWX10BEneE3tdjU2q1DVOmG3RON7heRTo45jxrYyYV0k6Kx2sxoEtx6KWxAKlAgAGSvAOsoajfu0gpDy7xdIduE-0L9kvJqJVAEdpSPA'
24
+ payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIiwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MDc4NzIwNCwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkwNzgzNjA0LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJBRF8yNjA5X0dQQ0EiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiIxZDMyZDQwN2YxMTJkZjNiMmRhNWRjNjkxNjM1NWIzYiIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1ODg5LCJlbWFpbCI6ImdpZ2FsYTgzODVAYnVsbGJhYnkuY29tIiwibmFtZSI6ImFrYXNoIGRldmdhbiIsImZhbWlseV9uYW1lIjoiIiwiZ2l2ZW5fbmFtZSI6IiIsImFkbWluaXN0cmF0b3IiOmZhbHNlLCJzdXBlcnVzZXIiOmZhbHNlfQ',
25
+ signature: 'YfXk1hhknddOevZPbosTmuptBYftNO8k-4UDnR2x_HjoVIwnl92-ROXju5aKXz7fOvOamJCCldrtPg0hBtVF1J0Ah7AkBwiOyj3ScQ943vXAQ8vpqowleB0Eh2Zwi-Un5OW5kcZXzi3Axrz4z9OCGdrHapLXzClvXTTme_D6AhURBhmLQQJaQrw7yTFtssPzLudRNFauP-Fu8AMSuXqYz_bv8hWrrgGB-pqvLKYtbddNx8553bjp7jmxekieZuVxwnFT5m13wHhll13Nu0Vv5McpLIm0zGQeWJAo9zlgoUGLYybsCNuX5lZyzinSHVh5uquM-n6RNvujIs125Ordbw',
26
26
  };
27
27
  const EDX_JWT_PAYLOAD_COOKIE_NAMES = [
28
28
  'stage-edx-jwt-cookie-header-payload',
@@ -1 +1 @@
1
- {"version":3,"file":"provision.js","sourceRoot":"","sources":["../../src/utils/provision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;;;;;;;;;;AAEH,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AA+BrC,6EAA6E;AAC7E,MAAM,gBAAgB,GAAG;IACvB,OAAO,EAAE,kuBAAkuB;IAC3uB,SAAS,EAAE,wVAAwV;CACpW,CAAC;AAEF,MAAM,4BAA4B,GAAG;IACnC,qCAAqC;IACrC,+BAA+B;CAChC,CAAC;AAEF,MAAM,8BAA8B,GAAG;IACrC,gCAAgC;IAChC,0BAA0B;CAC3B,CAAC;AAEF,SAAS,cAAc,CAAC,IAAY;IAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM;SAC3B,KAAK,CAAC,IAAI,CAAC;SACX,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;IAE3D,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,OAAO,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAe;IAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,mBAAmB;IAC1B,MAAM,OAAO,GAAG,mBAAmB,CAAC,4BAA4B,CAAC,CAAC;IAClE,MAAM,SAAS,GAAG,mBAAmB,CAAC,8BAA8B,CAAC,CAAC;IAEtE,OAAO,OAAO,IAAI,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9D,CAAC;AAED,SAAS,oBAAoB,CAAC,IAA6B;;IACzD,MAAM,SAAS,GAAG,CAChB,MAAA,MAAA,MAAA,IAAI,CAAC,SAAS,mCAAI,IAAI,CAAC,KAAK,mCAAI,IAAI,CAAC,YAAY,mCAAI,IAAI,CAAC,WAAW,CAChD,CAAC;IACxB,MAAM,WAAW,GAAG,CAAC,MAAA,IAAI,CAAC,WAAW,mCAAI,IAAI,CAAC,YAAY,CAAuB,CAAC;IAElF,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACxE,IAAI,CAAC,WAAW;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAE3E,OAAO;QACL,SAAS;QACT,WAAW;QACX,QAAQ,EAAE,IAAI,CAAC,QAA8B;QAC7C,SAAS,EAAE,IAAI,CAAC,SAA+B;QAC/C,QAAQ,EAAE,IAAI,CAAC,QAA8B;QAC7C,KAAK,EAAE,IAAI,CAAC,KAA2B;KACxC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAgB,gBAAgB,CAAC,UAAkB,EAAE,QAAgB;;QACzE,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,UAAU,yBAAyB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,EAAE;gBAC5F,WAAW,EAAE,SAAS;aACvB,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YACzB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA6B,CAAC;YACzD,OAAO,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QAChF,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CAAA;AAED,oEAAoE;AACpE,MAAM,UAAgB,aAAa;;QACjC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,mCAAmC,EAAE;YAC3D,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;QAC9D,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAsB,CAAC;QAClD,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QAED,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;CAAA;AAED,2DAA2D;AAC3D,MAAM,UAAgB,kBAAkB,CACtC,cAAsB,EACtB,SAAiB;;QAEjB,MAAM,UAAU,GAAG,mBAAmB,EAAE,CAAC;QACzC,MAAM,WAAW,GAAG,UAAU;YAC5B,CAAC,iBAAG,UAAU,EAAE,SAAS,IAAK,UAAU,EACxC,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;QAE9B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,cAAc,EAAE;YACtC,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,SAAS;YACtB,OAAO,EAAE;gBACP,MAAM,EAAE,kBAAkB;gBAC1B,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;QAChE,CAAC;QAED,OAAO,GAAG,CAAC,IAAI,EAAgC,CAAC;IAClD,CAAC;CAAA;AAED,SAAS,aAAa;IACpB,OAAO,aAAa,EAAE,CAAC,WAAW,KAAK,gBAAgB,CAAC,WAAW,CAAC;AACtE,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAgB,eAAe,CAAC,UAAkB;;QACtD,2EAA2E;QAC3E,wEAAwE;QACxE,gEAAgE;QAChE,IAAI,aAAa,EAAE,EAAE,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;gBACtC,IAAI,SAAS,CAAC,gBAAgB,EAAE,CAAC;oBAC/B,MAAM,IAAI,GAAG,SAAS,CAAC,yBAAoD,CAAC;oBAC5E,IAAI,IAAI,EAAE,CAAC;wBACT,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC;oBACpC,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,WAAM,CAAC;gBACP,iFAAiF;YACnF,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAA2B,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;QAEvE,IAAI,GAAa,CAAC;QAClB,IAAI,aAAa,EAAE,EAAE,CAAC;YACpB,GAAG,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE;gBAC5B,MAAM,EAAE,MAAM;gBACd,WAAW,EAAE,SAAS;gBACtB,OAAO,kCACF,OAAO,KACV,cAAc,EAAE,kBAAkB,GACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC;aACvC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,GAAG,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE;gBAC5B,MAAM,EAAE,MAAM;gBACd,WAAW,EAAE,SAAS;gBACtB,OAAO;aACR,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,yCAAyC,GAAG,CAAC,MAAM,uCAAuC,CAC3F,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAkD,CAAC;QAC9E,IAAI,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAEpD,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;CAAA","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, getAppConfig, getSiteConfig } from '@openedx/frontend-base';\nimport { appId } from '../constants';\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.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MDc2Njk2MCwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkwNzYzMzYwLCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJOU18yNjA5X1dLMzEiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiI4MjE3ZWI5MTk0YTM1MGJiODVmYmUxMGUzNWFhYWNkZiIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1MzcyLCJlbWFpbCI6Im5zb3Jva2FAMnUuY29tIiwibmFtZSI6Ik5pY29sYXMgU29yb2thIiwiZmFtaWx5X25hbWUiOiIiLCJnaXZlbl9uYW1lIjoiIiwiYWRtaW5pc3RyYXRvciI6ZmFsc2UsInN1cGVydXNlciI6ZmFsc2V9',\n signature: 'UOeR4ZPyFsfMZe8vSzhA4jY_6jBd0cU-PVNNHbf5z4OdIw5D0itG5uCU_Dn_ffJRpouw6mcWnCR1XMnfUShxeLKmU0Fncobx_Ei1G3_ntscMhAe3xXicSoQp3R2O6hq199egiMFBA6XoSIRJpIr5Wc9uukNKJn_9cS5Flmacn8arHTlptpcGpjBl4LdgTI6U2yAlQQkHjidd5xM0QF0QiwKTtUCTa-iAfJDtWn72DWayfaHWX10BEneE3tdjU2q1DVOmG3RON7heRTo45jxrYyYV0k6Kx2sxoEtx6KWxAKlAgAGSvAOsoajfu0gpDy7xdIduE-0L9kvJqJVAEdpSPA'\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 // When DEV_MOCK_SESSION is true, skip the network call entirely and return\n // the hardcoded response from site config. This avoids CORS errors when\n // testing over HTTPS locally (e.g. for Apple Pay / Google Pay).\n if (isDevelopment()) {\n try {\n const appConfig = getAppConfig(appId);\n if (appConfig.DEV_MOCK_SESSION) {\n const mock = appConfig.DEV_MOCK_SESSION_RESPONSE as Record<string, unknown>;\n if (mock) {\n return parseSessionResponse(mock);\n }\n }\n } catch {\n // getAppConfig may throw if called before init — fall through to the real fetch.\n }\n }\n\n const headers: Record<string, string> = { Accept: 'application/json' };\n\n let res: Response;\n if (isDevelopment()) {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers: {\n ...headers,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(DEV_SESSION_BODY),\n });\n } else {\n res = await fetch(sessionUrl, {\n method: 'POST',\n credentials: 'include',\n headers,\n });\n }\n\n if (!res.ok) {\n throw new Error(\n `Failed to establish checkout session (${res.status}). Please log into edX and try again.`,\n );\n }\n\n const data = await res.json() as Record<string, unknown> & { error?: string };\n if (data.error) throw new Error(String(data.error));\n\n return parseSessionResponse(data);\n}\n"]}
1
+ {"version":3,"file":"provision.js","sourceRoot":"","sources":["../../src/utils/provision.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;;;;;;;;;;AAEH,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvF,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AA+BrC,6EAA6E;AAC7E,MAAM,gBAAgB,GAAG;IACvB,OAAO,EAAE,4uBAA4uB;IACrvB,SAAS,EAAE,wVAAwV;CACpW,CAAC;AAEF,MAAM,4BAA4B,GAAG;IACnC,qCAAqC;IACrC,+BAA+B;CAChC,CAAC;AAEF,MAAM,8BAA8B,GAAG;IACrC,gCAAgC;IAChC,0BAA0B;CAC3B,CAAC;AAEF,SAAS,cAAc,CAAC,IAAY;IAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM;SAC3B,KAAK,CAAC,IAAI,CAAC;SACX,IAAI,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;IAE3D,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,OAAO,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAe;IAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,mBAAmB;IAC1B,MAAM,OAAO,GAAG,mBAAmB,CAAC,4BAA4B,CAAC,CAAC;IAClE,MAAM,SAAS,GAAG,mBAAmB,CAAC,8BAA8B,CAAC,CAAC;IAEtE,OAAO,OAAO,IAAI,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9D,CAAC;AAED,SAAS,oBAAoB,CAAC,IAA6B;;IACzD,MAAM,SAAS,GAAG,CAChB,MAAA,MAAA,MAAA,IAAI,CAAC,SAAS,mCAAI,IAAI,CAAC,KAAK,mCAAI,IAAI,CAAC,YAAY,mCAAI,IAAI,CAAC,WAAW,CAChD,CAAC;IACxB,MAAM,WAAW,GAAG,CAAC,MAAA,IAAI,CAAC,WAAW,mCAAI,IAAI,CAAC,YAAY,CAAuB,CAAC;IAElF,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACxE,IAAI,CAAC,WAAW;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAE3E,OAAO;QACL,SAAS;QACT,WAAW;QACX,QAAQ,EAAE,IAAI,CAAC,QAA8B;QAC7C,SAAS,EAAE,IAAI,CAAC,SAA+B;QAC/C,QAAQ,EAAE,IAAI,CAAC,QAA8B;QAC7C,KAAK,EAAE,IAAI,CAAC,KAA2B;KACxC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAgB,gBAAgB,CAAC,UAAkB,EAAE,QAAgB;;QACzE,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,UAAU,yBAAyB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,EAAE;gBAC5F,WAAW,EAAE,SAAS;aACvB,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YACzB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA6B,CAAC;YACzD,OAAO,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;QAChF,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CAAA;AAED,oEAAoE;AACpE,MAAM,UAAgB,aAAa;;QACjC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,mCAAmC,EAAE;YAC3D,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;SACxC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;QAC9D,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAsB,CAAC;QAClD,IAAI,OAAO,IAAI,CAAC,EAAE,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QAED,OAAO,IAAI,CAAC,EAAE,CAAC;IACjB,CAAC;CAAA;AAED,2DAA2D;AAC3D,MAAM,UAAgB,kBAAkB,CACtC,cAAsB,EACtB,SAAiB;;QAEjB,MAAM,UAAU,GAAG,mBAAmB,EAAE,CAAC;QACzC,MAAM,WAAW,GAAG,UAAU;YAC5B,CAAC,iBAAG,UAAU,EAAE,SAAS,IAAK,UAAU,EACxC,CAAC,CAAC,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;QAE9B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,cAAc,EAAE;YACtC,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,SAAS;YACtB,OAAO,EAAE;gBACP,MAAM,EAAE,kBAAkB;gBAC1B,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;SAClC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;QAChE,CAAC;QAED,OAAO,GAAG,CAAC,IAAI,EAAgC,CAAC;IAClD,CAAC;CAAA;AAED,SAAS,aAAa;IACpB,OAAO,aAAa,EAAE,CAAC,WAAW,KAAK,gBAAgB,CAAC,WAAW,CAAC;AACtE,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAgB,eAAe,CAAC,UAAkB;;QACtD,2EAA2E;QAC3E,wEAAwE;QACxE,gEAAgE;QAChE,IAAI,aAAa,EAAE,EAAE,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;gBACtC,IAAI,SAAS,CAAC,gBAAgB,EAAE,CAAC;oBAC/B,MAAM,IAAI,GAAG,SAAS,CAAC,yBAAoD,CAAC;oBAC5E,IAAI,IAAI,EAAE,CAAC;wBACT,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC;oBACpC,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,WAAM,CAAC;gBACP,iFAAiF;YACnF,CAAC;QACH,CAAC;QAED,MAAM,OAAO,GAA2B,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;QAEvE,IAAI,GAAa,CAAC;QAClB,IAAI,aAAa,EAAE,EAAE,CAAC;YACpB,GAAG,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE;gBAC5B,MAAM,EAAE,MAAM;gBACd,WAAW,EAAE,SAAS;gBACtB,OAAO,kCACF,OAAO,KACV,cAAc,EAAE,kBAAkB,GACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC;aACvC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,GAAG,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE;gBAC5B,MAAM,EAAE,MAAM;gBACd,WAAW,EAAE,SAAS;gBACtB,OAAO;aACR,CAAC,CAAC;QACL,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,yCAAyC,GAAG,CAAC,MAAM,uCAAuC,CAC3F,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAkD,CAAC;QAC9E,IAAI,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAEpD,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC;IACpC,CAAC;CAAA","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, getAppConfig, getSiteConfig } from '@openedx/frontend-base';\nimport { appId } from '../constants';\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.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MDc4NzIwNCwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkwNzgzNjA0LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJBRF8yNjA5X0dQQ0EiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiIxZDMyZDQwN2YxMTJkZjNiMmRhNWRjNjkxNjM1NWIzYiIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1ODg5LCJlbWFpbCI6ImdpZ2FsYTgzODVAYnVsbGJhYnkuY29tIiwibmFtZSI6ImFrYXNoIGRldmdhbiIsImZhbWlseV9uYW1lIjoiIiwiZ2l2ZW5fbmFtZSI6IiIsImFkbWluaXN0cmF0b3IiOmZhbHNlLCJzdXBlcnVzZXIiOmZhbHNlfQ',\n signature: 'YfXk1hhknddOevZPbosTmuptBYftNO8k-4UDnR2x_HjoVIwnl92-ROXju5aKXz7fOvOamJCCldrtPg0hBtVF1J0Ah7AkBwiOyj3ScQ943vXAQ8vpqowleB0Eh2Zwi-Un5OW5kcZXzi3Axrz4z9OCGdrHapLXzClvXTTme_D6AhURBhmLQQJaQrw7yTFtssPzLudRNFauP-Fu8AMSuXqYz_bv8hWrrgGB-pqvLKYtbddNx8553bjp7jmxekieZuVxwnFT5m13wHhll13Nu0Vv5McpLIm0zGQeWJAo9zlgoUGLYybsCNuX5lZyzinSHVh5uquM-n6RNvujIs125Ordbw',\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 // When DEV_MOCK_SESSION is true, skip the network call entirely and return\n // the hardcoded response from site config. This avoids CORS errors when\n // testing over HTTPS locally (e.g. for Apple Pay / Google Pay).\n if (isDevelopment()) {\n try {\n const appConfig = getAppConfig(appId);\n if (appConfig.DEV_MOCK_SESSION) {\n const mock = appConfig.DEV_MOCK_SESSION_RESPONSE as Record<string, unknown>;\n if (mock) {\n return parseSessionResponse(mock);\n }\n }\n } catch {\n // getAppConfig may throw if called before init — fall through to the real fetch.\n }\n }\n\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edx/frontend-app-alacarte-checkout",
3
- "version": "1.10.0",
3
+ "version": "1.11.0",
4
4
  "description": "alacarte Checkout Frontend application",
5
5
  "repository": {
6
6
  "type": "git",