@edx/frontend-app-alacarte-checkout 1.17.0 → 1.18.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.
@@ -19,6 +19,7 @@ const CheckoutPage = () => {
19
19
  const isDevelopment = siteConfig.environment === 'development';
20
20
  const edxSessionUrl = `${isDevelopment ? 'https://courses.stage.edx.org' : LMS_BASE_URL}/api/edx-session`;
21
21
  const geolocationUrl = `${isDevelopment ? 'https://courses.stage.edx.org' : LMS_BASE_URL}/api/geolocation/lookup`;
22
+ const complianceCheckUrl = `${isDevelopment ? 'https://courses.stage.edx.org' : LMS_BASE_URL}/api/compliance-check`;
22
23
  // When the demo banner is enabled, start on the "PDP" view; clicking the
23
24
  // CTA flips showCheckout to true and the real checkout renders.
24
25
  const [showCheckout, setShowCheckout] = useState(!SHOW_DEMO_PDP);
@@ -28,7 +29,7 @@ const CheckoutPage = () => {
28
29
  if (!showCheckout) {
29
30
  return (_jsx(DemoPdpBanner, { apiUrl: apiUrl, edxSessionUrl: edxSessionUrl, onEnroll: () => setShowCheckout(true) }));
30
31
  }
31
- return (_jsx(PageWrapper, { children: _jsx(CheckoutGate, { stripePublishableKey: stripePublishableKey, apiUrl: apiUrl, edxSessionUrl: edxSessionUrl, geolocationUrl: geolocationUrl, learnerDashboardUrl: `${LMS_BASE_URL}/dashboard`, homeUrl: LMS_BASE_URL || '/', onComplete: (_subscriptionBiId) => {
32
+ return (_jsx(PageWrapper, { children: _jsx(CheckoutGate, { stripePublishableKey: stripePublishableKey, apiUrl: apiUrl, edxSessionUrl: edxSessionUrl, geolocationUrl: geolocationUrl, complianceCheckUrl: complianceCheckUrl, learnerDashboardUrl: `${LMS_BASE_URL}/dashboard`, homeUrl: LMS_BASE_URL || '/', onComplete: (_subscriptionBiId) => {
32
33
  console.log(_subscriptionBiId);
33
34
  // window.location.href = `/checkout/confirmation?sub=${_subscriptionBiId ?? ''}`;
34
35
  // Success screen.
@@ -1 +1 @@
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,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,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,CACL,KAAC,aAAa,IACZ,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,GACrC,CACH,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 (!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 if (!showCheckout) {\n return (\n <DemoPdpBanner\n apiUrl={apiUrl}\n edxSessionUrl={edxSessionUrl}\n onEnroll={() => setShowCheckout(true)}\n />\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;IAClH,MAAM,kBAAkB,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,YAAY,uBAAuB,CAAC;IAEpH,yEAAyE;IACzE,gEAAgE;IAChE,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAC,CAAC,aAAa,CAAC,CAAC;IAEjE,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,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,CACL,KAAC,aAAa,IACZ,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,aAAa,EAC5B,QAAQ,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,GACrC,CACH,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,kBAAkB,EAAE,kBAAkB,EACtC,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 const complianceCheckUrl = `${isDevelopment ? 'https://courses.stage.edx.org' : LMS_BASE_URL}/api/compliance-check`;\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 (!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 if (!showCheckout) {\n return (\n <DemoPdpBanner\n apiUrl={apiUrl}\n edxSessionUrl={edxSessionUrl}\n onEnroll={() => setShowCheckout(true)}\n />\n );\n }\n\n return (\n <PageWrapper>\n <CheckoutGate\n stripePublishableKey={stripePublishableKey}\n apiUrl={apiUrl}\n edxSessionUrl={edxSessionUrl}\n geolocationUrl={geolocationUrl}\n complianceCheckUrl={complianceCheckUrl}\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"]}
@@ -4,6 +4,8 @@ export interface CheckoutGateProps extends CheckoutProps {
4
4
  learnerDashboardUrl?: string;
5
5
  /** Homepage URL for the country-unavailable modal CTA. */
6
6
  homeUrl?: string;
7
+ /** ALC-307 compliance-check endpoint — decides whether the learner is embargoed. */
8
+ complianceCheckUrl?: string;
7
9
  }
8
- declare const CheckoutGate: ({ learnerDashboardUrl, homeUrl, ...checkoutProps }: CheckoutGateProps) => import("react/jsx-runtime").JSX.Element;
10
+ declare const CheckoutGate: ({ learnerDashboardUrl, homeUrl, complianceCheckUrl, ...checkoutProps }: CheckoutGateProps) => import("react/jsx-runtime").JSX.Element;
9
11
  export default CheckoutGate;
@@ -18,11 +18,13 @@ import Checkout from '../Checkout/Checkout';
18
18
  import CountryUnavailableModal from '../CountryUnavailableModal/CountryUnavailableModal';
19
19
  import messages from './Messages';
20
20
  const CheckoutGate = (_a) => {
21
- var { learnerDashboardUrl, homeUrl = '/' } = _a, checkoutProps = __rest(_a, ["learnerDashboardUrl", "homeUrl"]);
21
+ var { learnerDashboardUrl, homeUrl = '/', complianceCheckUrl } = _a, checkoutProps = __rest(_a, ["learnerDashboardUrl", "homeUrl", "complianceCheckUrl"]);
22
22
  const { formatMessage } = useIntl();
23
23
  const { gateStatus, userCountry, gateMessage } = useCheckoutGate({
24
24
  apiUrl: checkoutProps.apiUrl,
25
25
  edxSessionUrl: checkoutProps.edxSessionUrl,
26
+ geolocationUrl: checkoutProps.geolocationUrl,
27
+ complianceCheckUrl,
26
28
  learnerDashboardUrl,
27
29
  });
28
30
  if (gateStatus === 'loading') {
@@ -1 +1 @@
1
- {"version":3,"file":"CheckoutGate.js","sourceRoot":"","sources":["../../../src/components/CheckoutGate/CheckoutGate.tsx"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,QAAQ,MAAM,sBAAsB,CAAC;AAC5C,OAAO,uBAAuB,MAAM,oDAAoD,CAAC;AAEzF,OAAO,QAAQ,MAAM,YAAY,CAAC;AASlC,MAAM,YAAY,GAAG,CAAC,EAIF,EAAE,EAAE;QAJF,EACpB,mBAAmB,EACnB,OAAO,GAAG,GAAG,OAEK,EADf,aAAa,cAHI,kCAIrB,CADiB;IAEhB,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,CAAC;IACpC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,eAAe,CAAC;QAC/D,MAAM,EAAE,aAAa,CAAC,MAAM;QAC5B,aAAa,EAAE,aAAa,CAAC,aAAa;QAC1C,mBAAmB;KACpB,CAAC,CAAC;IAEH,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,CACL,cAAK,SAAS,EAAC,mBAAmB,EAAC,IAAI,EAAC,QAAQ,eAAW,QAAQ,YACjE,KAAC,OAAO,IAAC,SAAS,EAAC,QAAQ,EAAC,gBAAgB,EAAC,6BAAwB,GAAG,GACpE,CACP,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;QAC3B,OAAO,CACL,MAAC,KAAK,IAAC,OAAO,EAAC,QAAQ,EAAC,IAAI,EAAE,SAAS,aACpC,WAAW,aAAX,WAAW,cAAX,WAAW,GACT,aAAa,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,GAAG,EAClD,mBAAmB,IAAI,CACtB,YAAG,IAAI,EAAE,mBAAmB,EAAE,SAAS,EAAC,YAAY,6CAEhD,CACL,IACK,CACT,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,CACL,4BACE,KAAC,uBAAuB,IAAC,MAAM,QAAC,OAAO,EAAE,OAAO,GAAI,GACnD,CACJ,CAAC;IACJ,CAAC;IAED,OAAO,CACL,KAAC,QAAQ,oBACH,aAAa,IACjB,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,mBAAmB,IACpC,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,YAAY,CAAC","sourcesContent":["import { Alert, Spinner } from '@openedx/paragon';\nimport { Error as ErrorIcon } from '@openedx/paragon/icons';\nimport { useIntl } from '@openedx/frontend-base';\nimport { useCheckoutGate } from '../../data/hooks/useCheckoutGate';\nimport Checkout from '../Checkout/Checkout';\nimport CountryUnavailableModal from '../CountryUnavailableModal/CountryUnavailableModal';\nimport type { CheckoutProps } from '../Checkout/Checkout';\nimport messages from './Messages';\n\nexport interface CheckoutGateProps extends CheckoutProps {\n /** URL linked in session-error recovery messaging. */\n learnerDashboardUrl?: string,\n /** Homepage URL for the country-unavailable modal CTA. */\n homeUrl?: string,\n}\n\nconst CheckoutGate = ({\n learnerDashboardUrl,\n homeUrl = '/',\n ...checkoutProps\n}: CheckoutGateProps) => {\n const { formatMessage } = useIntl();\n const { gateStatus, userCountry, gateMessage } = useCheckoutGate({\n apiUrl: checkoutProps.apiUrl,\n edxSessionUrl: checkoutProps.edxSessionUrl,\n learnerDashboardUrl,\n });\n\n if (gateStatus === 'loading') {\n return (\n <div className=\"processing-screen\" role=\"status\" aria-live=\"polite\">\n <Spinner animation=\"border\" screenReaderText=\"Checking your account…\" />\n </div>\n );\n }\n\n if (gateStatus === 'error') {\n return (\n <Alert variant=\"danger\" icon={ErrorIcon}>\n {gateMessage\n ?? formatMessage(messages.defaultErrorMessage)}{' '}\n {learnerDashboardUrl && (\n <a href={learnerDashboardUrl} className=\"alert-link\">\n Go to your learner dashboard\n </a>\n )}\n </Alert>\n );\n }\n\n if (gateStatus === 'blocked') {\n return (\n <>\n <CountryUnavailableModal isOpen homeUrl={homeUrl} />\n </>\n );\n }\n\n return (\n <Checkout\n {...checkoutProps}\n userCountry={userCountry}\n homeUrl={homeUrl}\n orderHistoryUrl={learnerDashboardUrl}\n />\n );\n};\n\nexport default CheckoutGate;\n"]}
1
+ {"version":3,"file":"CheckoutGate.js","sourceRoot":"","sources":["../../../src/components/CheckoutGate/CheckoutGate.tsx"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,QAAQ,MAAM,sBAAsB,CAAC;AAC5C,OAAO,uBAAuB,MAAM,oDAAoD,CAAC;AAEzF,OAAO,QAAQ,MAAM,YAAY,CAAC;AAWlC,MAAM,YAAY,GAAG,CAAC,EAKF,EAAE,EAAE;QALF,EACpB,mBAAmB,EACnB,OAAO,GAAG,GAAG,EACb,kBAAkB,OAEA,EADf,aAAa,cAJI,wDAKrB,CADiB;IAEhB,MAAM,EAAE,aAAa,EAAE,GAAG,OAAO,EAAE,CAAC;IACpC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,GAAG,eAAe,CAAC;QAC/D,MAAM,EAAE,aAAa,CAAC,MAAM;QAC5B,aAAa,EAAE,aAAa,CAAC,aAAa;QAC1C,cAAc,EAAE,aAAa,CAAC,cAAc;QAC5C,kBAAkB;QAClB,mBAAmB;KACpB,CAAC,CAAC;IAEH,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,CACL,cAAK,SAAS,EAAC,mBAAmB,EAAC,IAAI,EAAC,QAAQ,eAAW,QAAQ,YACjE,KAAC,OAAO,IAAC,SAAS,EAAC,QAAQ,EAAC,gBAAgB,EAAC,6BAAwB,GAAG,GACpE,CACP,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC;QAC3B,OAAO,CACL,MAAC,KAAK,IAAC,OAAO,EAAC,QAAQ,EAAC,IAAI,EAAE,SAAS,aACpC,WAAW,aAAX,WAAW,cAAX,WAAW,GACT,aAAa,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,GAAG,EAClD,mBAAmB,IAAI,CACtB,YAAG,IAAI,EAAE,mBAAmB,EAAE,SAAS,EAAC,YAAY,6CAEhD,CACL,IACK,CACT,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,CACL,4BACE,KAAC,uBAAuB,IAAC,MAAM,QAAC,OAAO,EAAE,OAAO,GAAI,GACnD,CACJ,CAAC;IACJ,CAAC;IAED,OAAO,CACL,KAAC,QAAQ,oBACH,aAAa,IACjB,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,OAAO,EAChB,eAAe,EAAE,mBAAmB,IACpC,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,YAAY,CAAC","sourcesContent":["import { Alert, Spinner } from '@openedx/paragon';\nimport { Error as ErrorIcon } from '@openedx/paragon/icons';\nimport { useIntl } from '@openedx/frontend-base';\nimport { useCheckoutGate } from '../../data/hooks/useCheckoutGate';\nimport Checkout from '../Checkout/Checkout';\nimport CountryUnavailableModal from '../CountryUnavailableModal/CountryUnavailableModal';\nimport type { CheckoutProps } from '../Checkout/Checkout';\nimport messages from './Messages';\n\nexport interface CheckoutGateProps extends CheckoutProps {\n /** URL linked in session-error recovery messaging. */\n learnerDashboardUrl?: string,\n /** Homepage URL for the country-unavailable modal CTA. */\n homeUrl?: string,\n /** ALC-307 compliance-check endpoint — decides whether the learner is embargoed. */\n complianceCheckUrl?: string,\n}\n\nconst CheckoutGate = ({\n learnerDashboardUrl,\n homeUrl = '/',\n complianceCheckUrl,\n ...checkoutProps\n}: CheckoutGateProps) => {\n const { formatMessage } = useIntl();\n const { gateStatus, userCountry, gateMessage } = useCheckoutGate({\n apiUrl: checkoutProps.apiUrl,\n edxSessionUrl: checkoutProps.edxSessionUrl,\n geolocationUrl: checkoutProps.geolocationUrl,\n complianceCheckUrl,\n learnerDashboardUrl,\n });\n\n if (gateStatus === 'loading') {\n return (\n <div className=\"processing-screen\" role=\"status\" aria-live=\"polite\">\n <Spinner animation=\"border\" screenReaderText=\"Checking your account…\" />\n </div>\n );\n }\n\n if (gateStatus === 'error') {\n return (\n <Alert variant=\"danger\" icon={ErrorIcon}>\n {gateMessage\n ?? formatMessage(messages.defaultErrorMessage)}{' '}\n {learnerDashboardUrl && (\n <a href={learnerDashboardUrl} className=\"alert-link\">\n Go to your learner dashboard\n </a>\n )}\n </Alert>\n );\n }\n\n if (gateStatus === 'blocked') {\n return (\n <>\n <CountryUnavailableModal isOpen homeUrl={homeUrl} />\n </>\n );\n }\n\n return (\n <Checkout\n {...checkoutProps}\n userCountry={userCountry}\n homeUrl={homeUrl}\n orderHistoryUrl={learnerDashboardUrl}\n />\n );\n};\n\nexport default CheckoutGate;\n"]}
@@ -52,6 +52,7 @@ export declare const STRIPE_ELEMENT_OPTS: {
52
52
  fontFamily: string;
53
53
  fontSize: string;
54
54
  fontWeight: string;
55
+ lineHeight: string;
55
56
  color: string;
56
57
  '::placeholder': {
57
58
  color: string;
@@ -67,6 +68,7 @@ export declare const STRIPE_CARD_NUMBER_OPTS: {
67
68
  fontFamily: string;
68
69
  fontSize: string;
69
70
  fontWeight: string;
71
+ lineHeight: string;
70
72
  color: string;
71
73
  '::placeholder': {
72
74
  color: string;
package/dist/constants.js CHANGED
@@ -288,8 +288,10 @@ export const STRIPE_ELEMENT_OPTS = {
288
288
  fontFamily: 'Inter, system-ui, sans-serif',
289
289
  fontSize: '16px',
290
290
  fontWeight: '400',
291
+ // Stripe's `lineHeight` requires a fixed unit (not a percentage) — 145% of 16px.
292
+ lineHeight: '23px',
291
293
  color: '#1a1a1a',
292
- '::placeholder': { color: '#8f8f8f' },
294
+ '::placeholder': { color: '#6e6e6e' },
293
295
  },
294
296
  },
295
297
  };
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CSSProperties } from 'react';\n\nexport const appId = 'org.openedx.frontend.app.aca.checkout';\nexport const acaCheckoutRole = 'org.openedx.frontend.role.aca.checkout';\n\n// --- Cart BI state machine ---------------------------------------------------\nexport const STATUS = {\n IN_CART: 'IN_CART',\n CONFIRM: 'CONFIRM',\n SANCTION_STARTED: 'SANCTION_STARTED',\n SANCTION_PASSED: 'SANCTION_PASSED',\n SANCTION_BLOCK: 'SANCTION_BLOCK',\n STRIPE_STARTED: 'STRIPE_STARTED',\n STRIPE_SUBSCRIBED: 'STRIPE_SUBSCRIBED',\n STRIPE_CONFIRMATION_REQUIRED: 'STRIPE_CONFIRMATION_REQUIRED',\n STRIPE_FAIL: 'STRIPE_FAIL',\n SUBSCRIPTION_CREATED: 'SUBSCRIPTION_CREATED',\n} as const;\n\nexport type Status = typeof STATUS[keyof typeof STATUS];\n\nexport type Phase = 'init' | 'ready' | 'processing' | 'threeds' | 'success' | 'blocked' | 'failed';\n\nexport const TERMINAL_STATES: Status[] = [\n STATUS.SUBSCRIPTION_CREATED,\n STATUS.SANCTION_BLOCK,\n STATUS.STRIPE_FAIL,\n];\n\nexport const STATUS_LABELS: Record<string, string> = {\n IN_CART: 'Cart created',\n CONFIRM: 'Order confirmed',\n SANCTION_STARTED: 'Checking sanctions…',\n SANCTION_PASSED: 'Sanctions passed — setting up your account…',\n SANCTION_BLOCK: 'Order blocked',\n STRIPE_STARTED: 'Setting up your subscription…',\n STRIPE_CONFIRMATION_REQUIRED: 'Card verification required',\n STRIPE_SUBSCRIBED: 'Card verified — finalizing…',\n STRIPE_FAIL: 'Payment failed',\n SUBSCRIPTION_CREATED: 'Subscription created',\n};\n\nexport const isTerminalStatus = (status: Status | null | undefined): boolean =>\n TERMINAL_STATES.includes(status as Status);\n\n/** UI phase derived from the raw bpStatus code + cart readiness. */\nexport function phaseFor(status: Status | null, ready: boolean): Phase {\n if (status === STATUS.SUBSCRIPTION_CREATED) return 'success';\n if (status === STATUS.SANCTION_BLOCK) return 'blocked';\n if (status === STATUS.STRIPE_FAIL) return 'failed';\n if (status && status !== STATUS.IN_CART) return 'processing';\n return ready ? 'ready' : 'init';\n}\n\n// --- Dev defaults (match the local test harness) -----------------------------\nexport const DEFAULTS = {\n apiUrl: '/api/graphql',\n confirmIntentUrl: '/api/stripe/confirm-pi',\n customerUid: '019d2bde-f769-743d-88f9-8bf0d1a7a002',\n salableUid: '019d2ce7-05bc-7dd6-b141-e32df9ac86c1',\n // Monthly Subscription template uid (not Annual)\n billingPlanUid: '019d2ce7-05bf-733c-ba56-0fcafafeb69b',\n};\n\nexport const LS_KEYS = { biId: 'twou.cartBiId', uid: 'twou.cartUid' };\n\n// ─── Checkout gate feature flags (dev controls) ───────────────────────────────\n// Set to false during development to bypass the country gate check.\n// Should be true in production. Subscriber access is enforced outside this app.\n\n/** When false, the country check is skipped — all countries can access checkout. */\nexport const GATE_CHECK_COUNTRY = true;\n\n/**\n * ISO 3166-1 alpha-2 country codes that are permitted to complete checkout.\n * Add or remove codes here to open or restrict access by country.\n */\nexport const ALLOWED_CHECKOUT_COUNTRIES: readonly string[] = ['GB', 'AU', 'CA', 'US'];\n\n/**\n * ISO 3166-1 alpha-2 country codes that require the extra billing-consent checkbox.\n * Add or remove codes here to control which selected billing countries show it.\n */\nexport const COUNTRIES_REQUIRING_EXTRA_CONFIRM: readonly string[] = ['GB', 'CA'];\n\n// export const COUNTRIES: readonly (readonly [string, string])[] = [\n// ['AF', 'Afghanistan'],\n// ['AL', 'Albania'],\n// ['DZ', 'Algeria'],\n// ['AD', 'Andorra'],\n// ['AO', 'Angola'],\n// ['AG', 'Antigua and Barbuda'],\n// ['AR', 'Argentina'],\n// ['AM', 'Armenia'],\n// ['AU', 'Australia'],\n// ['AT', 'Austria'],\n// ['AZ', 'Azerbaijan'],\n// ['BS', 'Bahamas'],\n// ['BH', 'Bahrain'],\n// ['BD', 'Bangladesh'],\n// ['BB', 'Barbados'],\n// ['BY', 'Belarus'],\n// ['BE', 'Belgium'],\n// ['BZ', 'Belize'],\n// ['BJ', 'Benin'],\n// ['BT', 'Bhutan'],\n// ['BO', 'Bolivia'],\n// ['BA', 'Bosnia and Herzegovina'],\n// ['BW', 'Botswana'],\n// ['BR', 'Brazil'],\n// ['BN', 'Brunei'],\n// ['BG', 'Bulgaria'],\n// ['BF', 'Burkina Faso'],\n// ['BI', 'Burundi'],\n// ['CV', 'Cabo Verde'],\n// ['KH', 'Cambodia'],\n// ['CM', 'Cameroon'],\n// ['CA', 'Canada'],\n// ['CF', 'Central African Republic'],\n// ['TD', 'Chad'],\n// ['CL', 'Chile'],\n// ['CN', 'China'],\n// ['CO', 'Colombia'],\n// ['KM', 'Comoros'],\n// ['CG', 'Congo (Congo-Brazzaville)'],\n// ['CR', 'Costa Rica'],\n// ['HR', 'Croatia'],\n// ['CU', 'Cuba'],\n// ['CY', 'Cyprus'],\n// ['CZ', 'Czechia (Czech Republic)'],\n// ['CD', 'Democratic Republic of the Congo'],\n// ['DK', 'Denmark'],\n// ['DJ', 'Djibouti'],\n// ['DM', 'Dominica'],\n// ['DO', 'Dominican Republic'],\n// ['EC', 'Ecuador'],\n// ['EG', 'Egypt'],\n// ['SV', 'El Salvador'],\n// ['GQ', 'Equatorial Guinea'],\n// ['ER', 'Eritrea'],\n// ['EE', 'Estonia'],\n// ['SZ', 'Eswatini'],\n// ['ET', 'Ethiopia'],\n// ['FJ', 'Fiji'],\n// ['FI', 'Finland'],\n// ['FR', 'France'],\n// ['GA', 'Gabon'],\n// ['GM', 'Gambia'],\n// ['GE', 'Georgia'],\n// ['DE', 'Germany'],\n// ['GH', 'Ghana'],\n// ['GR', 'Greece'],\n// ['GD', 'Grenada'],\n// ['GT', 'Guatemala'],\n// ['GN', 'Guinea'],\n// ['GW', 'Guinea-Bissau'],\n// ['GY', 'Guyana'],\n// ['HT', 'Haiti'],\n// ['VA', 'Holy See (Vatican City)'],\n// ['HN', 'Honduras'],\n// ['HU', 'Hungary'],\n// ['IS', 'Iceland'],\n// ['ID', 'Indonesia'],\n// ['IR', 'Iran'],\n// ['IQ', 'Iraq'],\n// ['IE', 'Ireland'],\n// ['IL', 'Israel'],\n// ['IT', 'Italy'],\n// ['CI', \"Ivory Coast (Côte d'Ivoire)\"],\n// ['JM', 'Jamaica'],\n// ['JP', 'Japan'],\n// ['JO', 'Jordan'],\n// ['KZ', 'Kazakhstan'],\n// ['KE', 'Kenya'],\n// ['KI', 'Kiribati'],\n// ['KW', 'Kuwait'],\n// ['KG', 'Kyrgyzstan'],\n// ['LA', 'Laos'],\n// ['LV', 'Latvia'],\n// ['LB', 'Lebanon'],\n// ['LS', 'Lesotho'],\n// ['LR', 'Liberia'],\n// ['LY', 'Libya'],\n// ['LI', 'Liechtenstein'],\n// ['LT', 'Lithuania'],\n// ['LU', 'Luxembourg'],\n// ['MG', 'Madagascar'],\n// ['MW', 'Malawi'],\n// ['MY', 'Malaysia'],\n// ['MV', 'Maldives'],\n// ['ML', 'Mali'],\n// ['MT', 'Malta'],\n// ['MH', 'Marshall Islands'],\n// ['MR', 'Mauritania'],\n// ['MU', 'Mauritius'],\n// ['MX', 'Mexico'],\n// ['FM', 'Micronesia'],\n// ['MD', 'Moldova'],\n// ['MC', 'Monaco'],\n// ['MN', 'Mongolia'],\n// ['ME', 'Montenegro'],\n// ['MA', 'Morocco'],\n// ['MZ', 'Mozambique'],\n// ['MM', 'Myanmar (Burma)'],\n// ['NA', 'Namibia'],\n// ['NR', 'Nauru'],\n// ['NP', 'Nepal'],\n// ['NL', 'Netherlands'],\n// ['NZ', 'New Zealand'],\n// ['NI', 'Nicaragua'],\n// ['NE', 'Niger'],\n// ['NG', 'Nigeria'],\n// ['KP', 'North Korea'],\n// ['MK', 'North Macedonia'],\n// ['NO', 'Norway'],\n// ['OM', 'Oman'],\n// ['PK', 'Pakistan'],\n// ['PW', 'Palau'],\n// ['PS', 'Palestine State'],\n// ['PA', 'Panama'],\n// ['PG', 'Papua New Guinea'],\n// ['PY', 'Paraguay'],\n// ['PE', 'Peru'],\n// ['PH', 'Philippines'],\n// ['PL', 'Poland'],\n// ['PT', 'Portugal'],\n// ['QA', 'Qatar'],\n// ['RO', 'Romania'],\n// ['RU', 'Russia'],\n// ['RW', 'Rwanda'],\n// ['KN', 'Saint Kitts and Nevis'],\n// ['LC', 'Saint Lucia'],\n// ['VC', 'Saint Vincent and the Grenadines'],\n// ['WS', 'Samoa'],\n// ['SM', 'San Marino'],\n// ['ST', 'Sao Tome and Principe'],\n// ['SA', 'Saudi Arabia'],\n// ['SN', 'Senegal'],\n// ['RS', 'Serbia'],\n// ['SC', 'Seychelles'],\n// ['SL', 'Sierra Leone'],\n// ['SG', 'Singapore'],\n// ['SK', 'Slovakia'],\n// ['SI', 'Slovenia'],\n// ['SB', 'Solomon Islands'],\n// ['SO', 'Somalia'],\n// ['ZA', 'South Africa'],\n// ['KR', 'South Korea'],\n// ['SS', 'South Sudan'],\n// ['ES', 'Spain'],\n// ['LK', 'Sri Lanka'],\n// ['SD', 'Sudan'],\n// ['SR', 'Suriname'],\n// ['SE', 'Sweden'],\n// ['CH', 'Switzerland'],\n// ['SY', 'Syria'],\n// ['TJ', 'Tajikistan'],\n// ['TZ', 'Tanzania'],\n// ['TH', 'Thailand'],\n// ['TL', 'Timor-Leste'],\n// ['TG', 'Togo'],\n// ['TO', 'Tonga'],\n// ['TT', 'Trinidad and Tobago'],\n// ['TN', 'Tunisia'],\n// ['TR', 'Turkey'],\n// ['TM', 'Turkmenistan'],\n// ['TV', 'Tuvalu'],\n// ['UG', 'Uganda'],\n// ['UA', 'Ukraine'],\n// ['AE', 'United Arab Emirates'],\n// ['GB', 'United Kingdom'],\n// ['US', 'United States'],\n// ['UY', 'Uruguay'],\n// ['UZ', 'Uzbekistan'],\n// ['VU', 'Vanuatu'],\n// ['VE', 'Venezuela'],\n// ['VN', 'Vietnam'],\n// ['YE', 'Yemen'],\n// ['ZM', 'Zambia'],\n// ['ZW', 'Zimbabwe'],\n// ];\n\n/** Billing country dropdown options for the supported countries in this checkout flow. */\nexport const BILLING_COUNTRIES: readonly (readonly [string, string])[] = [\n ['GB', 'United Kingdom'],\n ['AU', 'Australia'],\n ['CA', 'Canada'],\n ['US', 'United States'],\n];\n\n// --- Stripe Element presentation ---------------------------------------------\n// A div that hosts a Stripe Element but reads like a Paragon input.\n// Vertical padding replaces Element `lineHeight` (discouraged by Stripe.js).\nexport const STRIPE_FIELD_STYLE: CSSProperties = {\n height: 'auto',\n minHeight: '2.875rem',\n display: 'flex',\n alignItems: 'center',\n paddingTop: '0.5rem',\n paddingBottom: '0.5rem',\n};\nexport const STRIPE_ELEMENT_OPTS = {\n style: {\n base: {\n fontFamily: 'Inter, system-ui, sans-serif',\n fontSize: '16px',\n fontWeight: '400',\n color: '#1a1a1a',\n '::placeholder': { color: '#8f8f8f' },\n },\n },\n};\n/** cardNumber-only: disableLink is invalid on cardExpiry / cardCvc. */\nexport const STRIPE_CARD_NUMBER_OPTS = {\n ...STRIPE_ELEMENT_OPTS,\n disableLink: true,\n};\n"]}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CSSProperties } from 'react';\n\nexport const appId = 'org.openedx.frontend.app.aca.checkout';\nexport const acaCheckoutRole = 'org.openedx.frontend.role.aca.checkout';\n\n// --- Cart BI state machine ---------------------------------------------------\nexport const STATUS = {\n IN_CART: 'IN_CART',\n CONFIRM: 'CONFIRM',\n SANCTION_STARTED: 'SANCTION_STARTED',\n SANCTION_PASSED: 'SANCTION_PASSED',\n SANCTION_BLOCK: 'SANCTION_BLOCK',\n STRIPE_STARTED: 'STRIPE_STARTED',\n STRIPE_SUBSCRIBED: 'STRIPE_SUBSCRIBED',\n STRIPE_CONFIRMATION_REQUIRED: 'STRIPE_CONFIRMATION_REQUIRED',\n STRIPE_FAIL: 'STRIPE_FAIL',\n SUBSCRIPTION_CREATED: 'SUBSCRIPTION_CREATED',\n} as const;\n\nexport type Status = typeof STATUS[keyof typeof STATUS];\n\nexport type Phase = 'init' | 'ready' | 'processing' | 'threeds' | 'success' | 'blocked' | 'failed';\n\nexport const TERMINAL_STATES: Status[] = [\n STATUS.SUBSCRIPTION_CREATED,\n STATUS.SANCTION_BLOCK,\n STATUS.STRIPE_FAIL,\n];\n\nexport const STATUS_LABELS: Record<string, string> = {\n IN_CART: 'Cart created',\n CONFIRM: 'Order confirmed',\n SANCTION_STARTED: 'Checking sanctions…',\n SANCTION_PASSED: 'Sanctions passed — setting up your account…',\n SANCTION_BLOCK: 'Order blocked',\n STRIPE_STARTED: 'Setting up your subscription…',\n STRIPE_CONFIRMATION_REQUIRED: 'Card verification required',\n STRIPE_SUBSCRIBED: 'Card verified — finalizing…',\n STRIPE_FAIL: 'Payment failed',\n SUBSCRIPTION_CREATED: 'Subscription created',\n};\n\nexport const isTerminalStatus = (status: Status | null | undefined): boolean =>\n TERMINAL_STATES.includes(status as Status);\n\n/** UI phase derived from the raw bpStatus code + cart readiness. */\nexport function phaseFor(status: Status | null, ready: boolean): Phase {\n if (status === STATUS.SUBSCRIPTION_CREATED) return 'success';\n if (status === STATUS.SANCTION_BLOCK) return 'blocked';\n if (status === STATUS.STRIPE_FAIL) return 'failed';\n if (status && status !== STATUS.IN_CART) return 'processing';\n return ready ? 'ready' : 'init';\n}\n\n// --- Dev defaults (match the local test harness) -----------------------------\nexport const DEFAULTS = {\n apiUrl: '/api/graphql',\n confirmIntentUrl: '/api/stripe/confirm-pi',\n customerUid: '019d2bde-f769-743d-88f9-8bf0d1a7a002',\n salableUid: '019d2ce7-05bc-7dd6-b141-e32df9ac86c1',\n // Monthly Subscription template uid (not Annual)\n billingPlanUid: '019d2ce7-05bf-733c-ba56-0fcafafeb69b',\n};\n\nexport const LS_KEYS = { biId: 'twou.cartBiId', uid: 'twou.cartUid' };\n\n// ─── Checkout gate feature flags (dev controls) ───────────────────────────────\n// Set to false during development to bypass the country gate check.\n// Should be true in production. Subscriber access is enforced outside this app.\n\n/** When false, the country check is skipped — all countries can access checkout. */\nexport const GATE_CHECK_COUNTRY = true;\n\n/**\n * ISO 3166-1 alpha-2 country codes that are permitted to complete checkout.\n * Add or remove codes here to open or restrict access by country.\n */\nexport const ALLOWED_CHECKOUT_COUNTRIES: readonly string[] = ['GB', 'AU', 'CA', 'US'];\n\n/**\n * ISO 3166-1 alpha-2 country codes that require the extra billing-consent checkbox.\n * Add or remove codes here to control which selected billing countries show it.\n */\nexport const COUNTRIES_REQUIRING_EXTRA_CONFIRM: readonly string[] = ['GB', 'CA'];\n\n// export const COUNTRIES: readonly (readonly [string, string])[] = [\n// ['AF', 'Afghanistan'],\n// ['AL', 'Albania'],\n// ['DZ', 'Algeria'],\n// ['AD', 'Andorra'],\n// ['AO', 'Angola'],\n// ['AG', 'Antigua and Barbuda'],\n// ['AR', 'Argentina'],\n// ['AM', 'Armenia'],\n// ['AU', 'Australia'],\n// ['AT', 'Austria'],\n// ['AZ', 'Azerbaijan'],\n// ['BS', 'Bahamas'],\n// ['BH', 'Bahrain'],\n// ['BD', 'Bangladesh'],\n// ['BB', 'Barbados'],\n// ['BY', 'Belarus'],\n// ['BE', 'Belgium'],\n// ['BZ', 'Belize'],\n// ['BJ', 'Benin'],\n// ['BT', 'Bhutan'],\n// ['BO', 'Bolivia'],\n// ['BA', 'Bosnia and Herzegovina'],\n// ['BW', 'Botswana'],\n// ['BR', 'Brazil'],\n// ['BN', 'Brunei'],\n// ['BG', 'Bulgaria'],\n// ['BF', 'Burkina Faso'],\n// ['BI', 'Burundi'],\n// ['CV', 'Cabo Verde'],\n// ['KH', 'Cambodia'],\n// ['CM', 'Cameroon'],\n// ['CA', 'Canada'],\n// ['CF', 'Central African Republic'],\n// ['TD', 'Chad'],\n// ['CL', 'Chile'],\n// ['CN', 'China'],\n// ['CO', 'Colombia'],\n// ['KM', 'Comoros'],\n// ['CG', 'Congo (Congo-Brazzaville)'],\n// ['CR', 'Costa Rica'],\n// ['HR', 'Croatia'],\n// ['CU', 'Cuba'],\n// ['CY', 'Cyprus'],\n// ['CZ', 'Czechia (Czech Republic)'],\n// ['CD', 'Democratic Republic of the Congo'],\n// ['DK', 'Denmark'],\n// ['DJ', 'Djibouti'],\n// ['DM', 'Dominica'],\n// ['DO', 'Dominican Republic'],\n// ['EC', 'Ecuador'],\n// ['EG', 'Egypt'],\n// ['SV', 'El Salvador'],\n// ['GQ', 'Equatorial Guinea'],\n// ['ER', 'Eritrea'],\n// ['EE', 'Estonia'],\n// ['SZ', 'Eswatini'],\n// ['ET', 'Ethiopia'],\n// ['FJ', 'Fiji'],\n// ['FI', 'Finland'],\n// ['FR', 'France'],\n// ['GA', 'Gabon'],\n// ['GM', 'Gambia'],\n// ['GE', 'Georgia'],\n// ['DE', 'Germany'],\n// ['GH', 'Ghana'],\n// ['GR', 'Greece'],\n// ['GD', 'Grenada'],\n// ['GT', 'Guatemala'],\n// ['GN', 'Guinea'],\n// ['GW', 'Guinea-Bissau'],\n// ['GY', 'Guyana'],\n// ['HT', 'Haiti'],\n// ['VA', 'Holy See (Vatican City)'],\n// ['HN', 'Honduras'],\n// ['HU', 'Hungary'],\n// ['IS', 'Iceland'],\n// ['ID', 'Indonesia'],\n// ['IR', 'Iran'],\n// ['IQ', 'Iraq'],\n// ['IE', 'Ireland'],\n// ['IL', 'Israel'],\n// ['IT', 'Italy'],\n// ['CI', \"Ivory Coast (Côte d'Ivoire)\"],\n// ['JM', 'Jamaica'],\n// ['JP', 'Japan'],\n// ['JO', 'Jordan'],\n// ['KZ', 'Kazakhstan'],\n// ['KE', 'Kenya'],\n// ['KI', 'Kiribati'],\n// ['KW', 'Kuwait'],\n// ['KG', 'Kyrgyzstan'],\n// ['LA', 'Laos'],\n// ['LV', 'Latvia'],\n// ['LB', 'Lebanon'],\n// ['LS', 'Lesotho'],\n// ['LR', 'Liberia'],\n// ['LY', 'Libya'],\n// ['LI', 'Liechtenstein'],\n// ['LT', 'Lithuania'],\n// ['LU', 'Luxembourg'],\n// ['MG', 'Madagascar'],\n// ['MW', 'Malawi'],\n// ['MY', 'Malaysia'],\n// ['MV', 'Maldives'],\n// ['ML', 'Mali'],\n// ['MT', 'Malta'],\n// ['MH', 'Marshall Islands'],\n// ['MR', 'Mauritania'],\n// ['MU', 'Mauritius'],\n// ['MX', 'Mexico'],\n// ['FM', 'Micronesia'],\n// ['MD', 'Moldova'],\n// ['MC', 'Monaco'],\n// ['MN', 'Mongolia'],\n// ['ME', 'Montenegro'],\n// ['MA', 'Morocco'],\n// ['MZ', 'Mozambique'],\n// ['MM', 'Myanmar (Burma)'],\n// ['NA', 'Namibia'],\n// ['NR', 'Nauru'],\n// ['NP', 'Nepal'],\n// ['NL', 'Netherlands'],\n// ['NZ', 'New Zealand'],\n// ['NI', 'Nicaragua'],\n// ['NE', 'Niger'],\n// ['NG', 'Nigeria'],\n// ['KP', 'North Korea'],\n// ['MK', 'North Macedonia'],\n// ['NO', 'Norway'],\n// ['OM', 'Oman'],\n// ['PK', 'Pakistan'],\n// ['PW', 'Palau'],\n// ['PS', 'Palestine State'],\n// ['PA', 'Panama'],\n// ['PG', 'Papua New Guinea'],\n// ['PY', 'Paraguay'],\n// ['PE', 'Peru'],\n// ['PH', 'Philippines'],\n// ['PL', 'Poland'],\n// ['PT', 'Portugal'],\n// ['QA', 'Qatar'],\n// ['RO', 'Romania'],\n// ['RU', 'Russia'],\n// ['RW', 'Rwanda'],\n// ['KN', 'Saint Kitts and Nevis'],\n// ['LC', 'Saint Lucia'],\n// ['VC', 'Saint Vincent and the Grenadines'],\n// ['WS', 'Samoa'],\n// ['SM', 'San Marino'],\n// ['ST', 'Sao Tome and Principe'],\n// ['SA', 'Saudi Arabia'],\n// ['SN', 'Senegal'],\n// ['RS', 'Serbia'],\n// ['SC', 'Seychelles'],\n// ['SL', 'Sierra Leone'],\n// ['SG', 'Singapore'],\n// ['SK', 'Slovakia'],\n// ['SI', 'Slovenia'],\n// ['SB', 'Solomon Islands'],\n// ['SO', 'Somalia'],\n// ['ZA', 'South Africa'],\n// ['KR', 'South Korea'],\n// ['SS', 'South Sudan'],\n// ['ES', 'Spain'],\n// ['LK', 'Sri Lanka'],\n// ['SD', 'Sudan'],\n// ['SR', 'Suriname'],\n// ['SE', 'Sweden'],\n// ['CH', 'Switzerland'],\n// ['SY', 'Syria'],\n// ['TJ', 'Tajikistan'],\n// ['TZ', 'Tanzania'],\n// ['TH', 'Thailand'],\n// ['TL', 'Timor-Leste'],\n// ['TG', 'Togo'],\n// ['TO', 'Tonga'],\n// ['TT', 'Trinidad and Tobago'],\n// ['TN', 'Tunisia'],\n// ['TR', 'Turkey'],\n// ['TM', 'Turkmenistan'],\n// ['TV', 'Tuvalu'],\n// ['UG', 'Uganda'],\n// ['UA', 'Ukraine'],\n// ['AE', 'United Arab Emirates'],\n// ['GB', 'United Kingdom'],\n// ['US', 'United States'],\n// ['UY', 'Uruguay'],\n// ['UZ', 'Uzbekistan'],\n// ['VU', 'Vanuatu'],\n// ['VE', 'Venezuela'],\n// ['VN', 'Vietnam'],\n// ['YE', 'Yemen'],\n// ['ZM', 'Zambia'],\n// ['ZW', 'Zimbabwe'],\n// ];\n\n/** Billing country dropdown options for the supported countries in this checkout flow. */\nexport const BILLING_COUNTRIES: readonly (readonly [string, string])[] = [\n ['GB', 'United Kingdom'],\n ['AU', 'Australia'],\n ['CA', 'Canada'],\n ['US', 'United States'],\n];\n\n// --- Stripe Element presentation ---------------------------------------------\n// A div that hosts a Stripe Element but reads like a Paragon input.\n// Vertical padding replaces Element `lineHeight` (discouraged by Stripe.js).\nexport const STRIPE_FIELD_STYLE: CSSProperties = {\n height: 'auto',\n minHeight: '2.875rem',\n display: 'flex',\n alignItems: 'center',\n paddingTop: '0.5rem',\n paddingBottom: '0.5rem',\n};\nexport const STRIPE_ELEMENT_OPTS = {\n style: {\n base: {\n fontFamily: 'Inter, system-ui, sans-serif',\n fontSize: '16px',\n fontWeight: '400',\n // Stripe's `lineHeight` requires a fixed unit (not a percentage) — 145% of 16px.\n lineHeight: '23px',\n color: '#1a1a1a',\n '::placeholder': { color: '#6e6e6e' },\n },\n },\n};\n/** cardNumber-only: disableLink is invalid on cardExpiry / cardCvc. */\nexport const STRIPE_CARD_NUMBER_OPTS = {\n ...STRIPE_ELEMENT_OPTS,\n disableLink: true,\n};\n"]}
@@ -11,29 +11,11 @@ import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
11
11
  import { createCheckoutClient } from '../../utils/checkout';
12
12
  import { getPlanFromUrl, writePlanQueryParam } from '../../utils/planQuery';
13
13
  import { getReturnedCartBiIdFromUrl, loadStripe } from '../../utils/stripe';
14
+ import { getCourseRunKeyFromUrl } from '../../utils/url';
14
15
  import { STATUS, STATUS_LABELS, STRIPE_ELEMENT_OPTS, STRIPE_CARD_NUMBER_OPTS, phaseFor, DEFAULTS, BILLING_COUNTRIES, } from '../../constants';
15
16
  import { useCartStatusQuery, useSubscriptionIdQuery, useViaxSessionQuery, useCourseDetailQuery, } from './useCheckoutQueries';
16
17
  import { useCheckoutMutations } from './useCheckoutMutations';
17
18
  import { createCardPaymentMethod as tokenizeCard, confirmCardPayment as confirmStripeCardPayment, } from '../../utils/stripeCard';
18
- /**
19
- * Read ?course_run_key= from the current URL.
20
- * Avoid URLSearchParams — it treats bare `+` as a space, which would turn
21
- * HarvardX+CS50+X into "HarvardX CS50 X". decodeURIComponent keeps `+` intact.
22
- */
23
- function getCourseRunKeyFromUrl() {
24
- if (typeof window === 'undefined')
25
- return null;
26
- const match = /[?&]course_run_key=([^&]*)/.exec(window.location.search);
27
- if (!match)
28
- return null;
29
- try {
30
- // Preserve literal `+` (treat as %2B) then decode %XX sequences.
31
- return decodeURIComponent(match[1].replace(/\+/g, '%2B')).trim() || null;
32
- }
33
- catch (_a) {
34
- return match[1].trim() || null;
35
- }
36
- }
37
19
  const redirectBrowser = (url) => {
38
20
  window.location.assign(url);
39
21
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useCheckout.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckout.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EACL,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,GAClD,MAAM,OAAO,CAAC;AAGf,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,0BAA0B,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,MAAM,EACN,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,QAAQ,EACR,QAAQ,EACR,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AAKzB,OAAO,EACL,kBAAkB,EAAE,sBAAsB,EAC1C,mBAAmB,EAAE,oBAAoB,GAC1C,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EACL,uBAAuB,IAAI,YAAY,EACvC,kBAAkB,IAAI,wBAAwB,GAC/C,MAAM,wBAAwB,CAAC;AAEhC;;;;GAIG;AACH,SAAS,sBAAsB;IAC7B,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAC/C,MAAM,KAAK,GAAG,4BAA4B,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,CAAC;QACH,iEAAiE;QACjE,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;IAC3E,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;IACjC,CAAC;AACH,CAAC;AAED,MAAM,eAAe,GAAG,CAAC,GAAW,EAAQ,EAAE;IAC5C,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC9B,CAAC,CAAC;AAEF,MAAM,wBAAwB,GAAG,CAAC,WAA0B,EAAU,EAAE;IACtE,MAAM,UAAU,GAAG,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,IAAI,GAAG,WAAW,EAAE,CAAC;IACrD,IAAI,UAAU,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,CAAC,WAAW,KAAK,UAAU,CAAC,EAAE,CAAC;QACxF,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC;AAoEF,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,UAA8B,EAAE;;IAClE,MAAM,EACJ,MAAM,GAAG,QAAQ,CAAC,MAAM,EACxB,aAAa,EACb,oBAAoB,EACpB,UAAU,EACV,WAAW,GAAG,IAAI,EAClB,YAAY,EAAE,gBAAgB,EAC9B,aAAa,GAAG,eAAe,GAChC,GAAG,OAAO,CAAC;IAEZ,MAAM,YAAY,GAAG,OAAO,CAC1B,GAAG,EAAE,CAAC,gBAAgB,aAAhB,gBAAgB,cAAhB,gBAAgB,GAAI,sBAAsB,EAAE,EAClD,CAAC,gBAAgB,CAAC,CACnB,CAAC;IAEF,MAAM,MAAM,GAAG,OAAO,CAAwB,GAAG,EAAE;QACjD,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACzB,OAAO,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1C,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,MAAM,aAAa,GAAG,CAAC,MAAM,IAAI,CAAC,oBAAoB,IAAI,CAAC,aAAa,CAAC;IAEzE,oDAAoD;IACpD,MAAM,YAAY,GAAG,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAc,CAAC,CAAC;IAChF,MAAM,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC;IAEzC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,YAAY,CAAC,IAAI,IAAI,MAAM,EAAE,CAAC;YAChC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAEhC,+DAA+D;IAC/D,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,MAAM,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;IAEnF,gBAAgB;IAChB,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAc,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAoB,IAAI,CAAC,CAAC;IAC9D,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAC/C,MAAM,CAAC,eAAe,EAAE,kBAAkB,CAAC,GAAG,QAAQ,CAAkB;QACtE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;QACnE,MAAM,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;QACnE,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;KACjE,CAAC,CAAC;IACH,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAc;QAClD,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,wBAAwB,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE;KACrH,CAAC,CAAC;IACH,MAAM,CAAC,aAAa,EAAE,gBAAgB,CAAC,GAAG,QAAQ,CAAe,cAAc,CAAC,CAAC;IACjF,mFAAmF;IACnF,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAgB,0BAA0B,CAAC,CAAC;IACpG,MAAM,CAAC,sBAAsB,EAAE,yBAAyB,CAAC,GAAG,QAAQ,CAClE,GAAG,EAAE,CAAC,0BAA0B,EAAE,KAAK,IAAI,CAC5C,CAAC;IAEF,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,KAAmB,EAAE,EAAE;QACnD,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACxB,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC7B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,YAAY,CAAC,KAAK,EAAE,CAAC;YACvB,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACvC,CAAC;IACH,CAAC,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IAEzB,MAAM,QAAQ,GAAG,WAAW,CAC1B,CAAC,KAAwB,EAAE,KAAa,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAc,EAAE,EAAE,CAAC,iCAAM,CAAC,KAAE,CAAC,KAAK,CAAC,EAAE,KAAK,IAAG,CAAC,EACvG,EAAE,CACH,CAAC;IAEF,eAAe;IACf,6DAA6D;IAC7D,MAAM,iBAAiB,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAC1D,MAAM,oBAAoB,GAAG,MAAM,CAA2B,IAAI,CAAC,CAAC;IACpE,MAAM,oBAAoB,GAAG,MAAM,CAA2B,IAAI,CAAC,CAAC;IACpE,MAAM,iBAAiB,GAAG,MAAM,CAA2B,IAAI,CAAC,CAAC;IACjE,MAAM,WAAW,GAAG,MAAM,CAA4B,IAAI,CAAC,CAAC;IAC5D,+EAA+E;IAC/E,MAAM,kBAAkB,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAC;IAC/D,MAAM,kBAAkB,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAC;IAC/D,MAAM,eAAe,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAExD,MAAM,cAAc,GAAG,WAAW,CAAC,CACjC,KAAyC,EACzC,IAA2B,EAC3B,EAAE;;QACF,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;QACrB,IACE,kBAAkB,CAAC,OAAO;eACvB,kBAAkB,CAAC,OAAO;eAC1B,eAAe,CAAC,OAAO,EAC1B,CAAC;YACD,iBAAiB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAChC,OAAO;QACT,CAAC;QACD,2EAA2E;QAC3E,MAAA,MAAA,oBAAoB,CAAC,OAAO,0CAAE,OAAO,kDAAI,CAAC;QAC1C,MAAA,MAAA,oBAAoB,CAAC,OAAO,0CAAE,OAAO,kDAAI,CAAC;QAC1C,MAAA,MAAA,iBAAiB,CAAC,OAAO,0CAAE,OAAO,kDAAI,CAAC;IACzC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,IAA2B,EAAE,EAAE;QACjE,cAAc,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IACrB,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,IAA2B,EAAE,EAAE;QACjE,cAAc,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IACrB,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,IAA2B,EAAE,EAAE;QAC9D,cAAc,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;IACxC,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC,CAAC;IACrB,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,gBAAgB,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IACrD,MAAM,UAAU,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAC;IAEzD,SAAS,CAAC,GAAG,EAAE;;QACb,UAAU,CAAC,OAAO,GAAG,MAAA,YAAY,CAAC,IAAI,mCAAI,IAAI,CAAC;IACjD,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IAExB,sFAAsF;IACtF,MAAM,OAAO,GAAG,MAAM,CAAc,IAAI,CAAC,CAAC;IAC1C,MAAM,UAAU,GAAG,MAAM,CAAc,OAAO,CAAC,CAAC;IAChD,MAAM,gBAAgB,GAAG,MAAM,CAAe,aAAa,CAAC,CAAC;IAE7D,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IACzB,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACX,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAC/B,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACd,SAAS,CAAC,GAAG,EAAE;QACb,gBAAgB,CAAC,OAAO,GAAG,aAAa,CAAC;IAC3C,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IACpB,SAAS,CAAC,GAAG,EAAE;QACb,iBAAiB,CAAC,OAAO,GAAG,MAAM,CAAC;IACrC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,uBAAuB;IACvB,yEAAyE;IACzE,2EAA2E;IAC3E,yBAAyB;IACzB,MAAM,QAAQ,GAAG,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,IAAI,mCAAI,CAAC,YAAY,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACxE,MAAM,WAAW,GAAG,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzD,MAAM,MAAM,GAAkB,MAAA,MAAA,WAAW,CAAC,IAAI,0CAAE,MAAM,mCAAI,IAAI,CAAC;IAC/D,MAAM,WAAW,GAAG,MAAA,MAAA,WAAW,CAAC,IAAI,0CAAE,WAAW,mCAAI,EAAE,CAAC;IAExD,iEAAiE;IACjE,MAAM,KAAK,GAAU,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IACtF,MAAM,mBAAmB,GAAG,sBAAsB,CAChD,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC,oBAAoB,CACzD,CAAC;IAEF,oBAAoB;IACpB,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,GAAG,oBAAoB,CAAC;QACzD,MAAM;QACN,UAAU;QACV,SAAS,EAAE,iBAAiB;QAC5B,eAAe,EAAE,oBAAoB;QACrC,UAAU;QACV,gBAAgB;QAChB,QAAQ,EAAE,CAAC,GAAW,EAAE,EAAE;YACxB,yBAAyB,CAAC,KAAK,CAAC,CAAC;YACjC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;QACD,YAAY,EAAE,CAAC,GAAW,EAAE,EAAE;YAC5B,yBAAyB,CAAC,KAAK,CAAC,CAAC;YACjC,YAAY,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QACD,aAAa,EAAE,CAAC,OAAa,EAAE,EAAE;YAC/B,iEAAiE;YACjE,8CAA8C;YAC9C,OAAO,CAAC,OAAO,GAAG,OAAO,CAAC;YAC1B,IAAI,UAAU,CAAC,OAAO;gBAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC3C,CAAC;KACF,CAAC,CAAC;IAEH,kCAAkC;IAClC,SAAS,CAAC,GAAG,EAAE;QACb,UAAU,CAAC,OAAO,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,oBAAoB;YAAE,OAAO,SAAS,CAAC;QAE5C,CAAC,GAAS,EAAE;YACV,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,oBAAoB,CAAC,CAAC;gBACxD,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;oBACvB,SAAS,CAAC,QAAQ,CAAC,CAAC;oBACpB,cAAc,CAAC,IAAI,CAAC,CAAC;gBACvB,CAAC;YACH,CAAC;YAAC,OAAO,CAAU,EAAE,CAAC;gBACpB,IAAI,UAAU,CAAC,OAAO;oBAAE,QAAQ,CAAE,CAAW,CAAC,OAAO,CAAC,CAAC;YACzD,CAAC;QACH,CAAC,CAAA,CAAC,EAAE,CAAC;QAEL,OAAO,GAAG,EAAE;YACV,UAAU,CAAC,OAAO,GAAG,KAAK,CAAC;QAC7B,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAE3B,+EAA+E;IAC/E,SAAS,CAAC,GAAG,EAAE;;QACb,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,UAAU,GAAG,kBAAkB,CAAC,OAAO,CAAC;QAC9C,MAAM,UAAU,GAAG,kBAAkB,CAAC,OAAO,CAAC;QAC9C,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC;QACxC,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,IAAI,CAAC,OAAO;YAAE,OAAO;QAEnD,IAAI,oBAAoB,CAAC,OAAO,IAAI,oBAAoB,CAAC,OAAO,IAAI,iBAAiB,CAAC,OAAO,EAAE,CAAC;YAC9F,IAAI,CAAC;gBACH,MAAA,MAAA,oBAAoB,CAAC,OAAO,EAAC,OAAO,kDAAI,CAAC;gBACzC,MAAA,MAAA,oBAAoB,CAAC,OAAO,EAAC,OAAO,kDAAI,CAAC;gBACzC,MAAA,MAAA,iBAAiB,CAAC,OAAO,EAAC,OAAO,kDAAI,CAAC;gBACtC,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC/C,oBAAoB,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAC/C,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBACzC,OAAO;YACT,CAAC;YAAC,WAAM,CAAC;gBACP,wEAAwE;gBACxE,oBAAoB,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpC,oBAAoB,CAAC,OAAO,GAAG,IAAI,CAAC;gBACpC,iBAAiB,CAAC,OAAO,GAAG,IAAI,CAAC;gBACjC,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;YAC7B,CAAC;QACH,CAAC;QAED,YAAY,CAAC,KAAK,CAAC,CAAC;QAEpB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;QACnC,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAC;QAE/B,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,uBAAuB,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,mBAAmB,CAAC,CAAC;QAClE,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,mBAAmB,CAAC,CAAC;QAE5D,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACzB,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACzB,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAEnB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACtB,IAAI,UAAU,CAAC,OAAO;gBAAE,YAAY,CAAC,IAAI,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;;YACxB,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,6EAA6E;gBAC7E,YAAY,CAAC,CAAA,MAAA,CAAC,CAAC,KAAK,0CAAE,OAAO,KAAI,EAAE,CAAC,CAAC;gBACrC,kBAAkB,CAAC,CAAC,IAAI,EAAE,EAAE;;oBAAC,OAAA,iCACxB,IAAI,KACP,MAAM,kCACD,IAAI,CAAC,MAAM,KACd,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,EAChB,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EACtB,KAAK,EAAE,CAAA,MAAA,CAAC,CAAC,KAAK,0CAAE,OAAO,KAAI,EAAE,OAE/B,CAAA;iBAAA,CAAC,CAAC;YACN,CAAC;QACH,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACrB,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,kBAAkB,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iCACxB,IAAI,KACP,MAAM,kCAAO,IAAI,CAAC,MAAM,KAAE,OAAO,EAAE,IAAI,OACvC,CAAC,CAAC;YACN,CAAC;QACH,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;YACxB,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,kBAAkB,CAAC,CAAC,IAAI,EAAE,EAAE;;oBAAC,OAAA,iCACxB,IAAI,KACP,MAAM,kCACD,IAAI,CAAC,MAAM,KACd,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,EAChB,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EACtB,KAAK,EAAE,CAAA,MAAA,CAAC,CAAC,KAAK,0CAAE,OAAO,KAAI,EAAE,OAE/B,CAAA;iBAAA,CAAC,CAAC;YACN,CAAC;QACH,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACrB,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,kBAAkB,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iCACxB,IAAI,KACP,MAAM,kCAAO,IAAI,CAAC,MAAM,KAAE,OAAO,EAAE,IAAI,OACvC,CAAC,CAAC;YACN,CAAC;QACH,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;YACrB,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,kBAAkB,CAAC,CAAC,IAAI,EAAE,EAAE;;oBAAC,OAAA,iCACxB,IAAI,KACP,GAAG,kCACE,IAAI,CAAC,GAAG,KACX,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,EAChB,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,EACtB,KAAK,EAAE,CAAA,MAAA,CAAC,CAAC,KAAK,0CAAE,OAAO,KAAI,EAAE,OAE/B,CAAA;iBAAA,CAAC,CAAC;YACN,CAAC;QACH,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YAClB,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;gBACvB,kBAAkB,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iCACxB,IAAI,KACP,GAAG,kCAAO,IAAI,CAAC,GAAG,KAAE,OAAO,EAAE,IAAI,OACjC,CAAC,CAAC;YACN,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,oBAAoB,CAAC,OAAO,GAAG,MAAM,CAAC;QACtC,oBAAoB,CAAC,OAAO,GAAG,MAAM,CAAC;QACtC,iBAAiB,CAAC,OAAO,GAAG,GAAG,CAAC;IAClC,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;IAE7B,qBAAqB;IACrB,SAAS,CAAC,GAAG,EAAE;;QACb,IAAI,MAAM,KAAK,MAAM,CAAC,oBAAoB,IAAI,YAAY,CAAC,OAAO;YAAE,OAAO;QAC3E,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC;QAC5B,IAAI,UAAU;YAAE,UAAU,CAAC,MAAA,MAAA,mBAAmB,CAAC,IAAI,0CAAE,IAAI,mCAAI,IAAI,CAAC,CAAC;IACrE,CAAC,EAAE,CAAC,MAAM,EAAE,mBAAmB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;IAEnD,wEAAwE;IACxE,iEAAiE;IACjE,mDAAmD;IACnD,SAAS,CAAC,GAAG,EAAE;;QACb,MAAM,WAAW,GAAG,CAAA,MAAA,WAAW,CAAC,IAAI,0CAAE,6BAA6B;gBAC9D,MAAA,WAAW,CAAC,IAAI,0CAAE,iBAAiB,CAAA,CAAC;QACzC,IACE,MAAM,KAAK,MAAM,CAAC,4BAA4B;eAC3C,CAAC,WAAW;eACZ,gBAAgB,CAAC,OAAO,KAAK,WAAW;YAC3C,OAAO;QAET,gBAAgB,CAAC,OAAO,GAAG,WAAW,CAAC;QACvC,aAAa,CAAC,WAAW,CAAC,CAAC;IAC7B,CAAC,EAAE;QACD,MAAM;QACN,MAAA,WAAW,CAAC,IAAI,0CAAE,6BAA6B;QAC/C,MAAA,WAAW,CAAC,IAAI,0CAAE,iBAAiB;QACnC,aAAa;KACd,CAAC,CAAC;IAEH,kBAAkB;IAClB,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,OAAwB,EAAE,gBAAwC,MAAM,EAAE,EAAE;QAC1G,QAAQ,CAAC,EAAE,CAAC,CAAC;QACb,YAAY,CAAC,EAAE,CAAC,CAAC;QACjB,IAAI,KAAK,KAAK,QAAQ;YAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACtC,4EAA4E;QAC5E,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAC1B,yBAAyB,CAAC,IAAI,CAAC,CAAC;QAChC,aAAa,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;IACnD,CAAC,EAAE,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC;IAE3B,MAAM,uBAAuB,GAAG,WAAW,CAAC,GAAS,EAAE;QACrD,YAAY,CAAC,EAAE,CAAC,CAAC;QACjB,MAAM,cAAc,GAAG,iBAAiB,CAAC,OAAO,CAAC;QACjD,MAAM,IAAI,GAAG,oBAAoB,CAAC,OAAO,CAAC;QAC1C,IAAI,CAAC,cAAc,IAAI,CAAC,IAAI,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;QACzF,CAAC;QACD,IAAI,CAAC;YACH,OAAO,MAAM,YAAY,CAAC;gBACxB,MAAM,EAAE,cAAc;gBACtB,IAAI;gBACJ,OAAO,EAAE,UAAU,CAAC,OAAO;aAC5B,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,qCAAqC,CAAC;YAC3F,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,CAAA,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,kBAAkB,GAAG,WAAW,CAAC,CAAO,YAAoB,EAAE,EAAE;QACpE,YAAY,CAAC,EAAE,CAAC,CAAC;QACjB,MAAM,cAAc,GAAG,iBAAiB,CAAC,OAAO,CAAC;QACjD,MAAM,IAAI,GAAG,oBAAoB,CAAC,OAAO,CAAC;QAC1C,IAAI,CAAC,cAAc,IAAI,CAAC,IAAI,EAAE,CAAC;YAC7B,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;QACzF,CAAC;QACD,IAAI,CAAC;YACH,MAAM,wBAAwB,CAAC;gBAC7B,MAAM,EAAE,cAAc;gBACtB,IAAI;gBACJ,OAAO,EAAE,UAAU,CAAC,OAAO;gBAC3B,YAAY;aACb,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,qCAAqC,CAAC;YAC3F,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,CAAA,EAAE,EAAE,CAAC,CAAC;IAEP,mEAAmE;IACnE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,gBAAgB,GAAa;YACjC,MAAM,CAAC,oBAAoB;YAC3B,MAAM,CAAC,WAAW;YAClB,MAAM,CAAC,cAAc;SACtB,CAAC;QACF,IAAI,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACtC,yBAAyB,CAAC,KAAK,CAAC,CAAC;QACnC,CAAC;IACH,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAEb,OAAO;QACL,KAAK;QACL,MAAM;QACN,WAAW,EAAE,CAAC,MAAM,IAAI,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,wBAAwB;QAC1E,WAAW;QACX,SAAS;QACT,aAAa;QACb,UAAU;QACV,aAAa,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC;QACzC,KAAK;QACL,UAAU,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC9B,SAAS;QACT,WAAW;QACX,mBAAmB,EAAE,MAAA,mBAAmB,CAAC,IAAI,mCAAI,IAAI;QACrD,OAAO;QACP,QAAQ;QACR,cAAc;QACd,cAAc;QACd,WAAW;QACX,UAAU,EAAE,aAAa,CAAC,SAAS;QACnC,sBAAsB;QACtB,mBAAmB,EAAE,gBAAgB,KAAK,IAAI;QAC9C,UAAU;QACV,uBAAuB;QACvB,kBAAkB;QAClB,aAAa;QACb,QAAQ;QACR,YAAY;QACZ,WAAW;QACX,MAAM,EAAE,MAAA,iBAAiB,CAAC,IAAI,mCAAI,IAAI;QACtC,MAAM;QACN,eAAe;KAChB,CAAC;AACJ,CAAC","sourcesContent":["import {\n useCallback, useEffect, useMemo, useRef, useState,\n} from 'react';\nimport type { Ref } from 'react';\nimport type { BillingCycle } from '../../types';\nimport { createCheckoutClient } from '../../utils/checkout';\nimport { getPlanFromUrl, writePlanQueryParam } from '../../utils/planQuery';\nimport { getReturnedCartBiIdFromUrl, loadStripe } from '../../utils/stripe';\nimport {\n STATUS,\n STATUS_LABELS,\n STRIPE_ELEMENT_OPTS,\n STRIPE_CARD_NUMBER_OPTS,\n phaseFor,\n DEFAULTS,\n BILLING_COUNTRIES,\n} from '../../constants';\nimport type {\n Status, Phase, Cart, BillingInfo, CheckoutClient, StripeLike, StripeElementLike,\n StripeElementsLike, CheckoutConsent, SupportedPaymentMethod, CardFieldStates,\n} from '../../types';\nimport {\n useCartStatusQuery, useSubscriptionIdQuery,\n useViaxSessionQuery, useCourseDetailQuery,\n} from './useCheckoutQueries';\nimport type { EdxSessionResult } from '../../utils/provision';\nimport { useCheckoutMutations } from './useCheckoutMutations';\nimport {\n createCardPaymentMethod as tokenizeCard,\n confirmCardPayment as confirmStripeCardPayment,\n} from '../../utils/stripeCard';\n\n/**\n * Read ?course_run_key= from the current URL.\n * Avoid URLSearchParams — it treats bare `+` as a space, which would turn\n * HarvardX+CS50+X into \"HarvardX CS50 X\". decodeURIComponent keeps `+` intact.\n */\nfunction getCourseRunKeyFromUrl(): string | null {\n if (typeof window === 'undefined') return null;\n const match = /[?&]course_run_key=([^&]*)/.exec(window.location.search);\n if (!match) return null;\n try {\n // Preserve literal `+` (treat as %2B) then decode %XX sequences.\n return decodeURIComponent(match[1].replace(/\\+/g, '%2B')).trim() || null;\n } catch {\n return match[1].trim() || null;\n }\n}\n\nconst redirectBrowser = (url: string): void => {\n window.location.assign(url);\n};\n\nconst getInitialBillingCountry = (userCountry: string | null): string => {\n const normalized = userCountry?.trim().toUpperCase();\n if (normalized && BILLING_COUNTRIES.some(([countryCode]) => countryCode === normalized)) {\n return normalized;\n }\n return '';\n};\n\nexport interface UseCheckoutOptions {\n apiUrl?: string,\n edxSessionUrl?: string,\n stripePublishableKey?: string,\n onComplete?: (subscriptionBiId: string | null) => void,\n /** Country code resolved by CheckoutGate — used to show/hide the UK/CA extra checkbox. */\n userCountry?: string | null,\n /**\n * Course run key from the URL (e.g. course-v1:HarvardX+CS50+X).\n * When omitted, read from ?course_run_key=.\n */\n courseRunKey?: string | null,\n /** Override only for tests; production performs a full-page browser redirect. */\n redirectToUrl?: (url: string) => void,\n}\n\nexport interface UseCheckoutResult {\n phase: Phase,\n status: Status | null,\n statusLabel: string,\n stripeReady: boolean,\n /** True once the Stripe card Element iframe is fully initialised and interactive. */\n cardReady: boolean,\n notConfigured: boolean,\n showBanner: boolean,\n dismissBanner: () => void,\n error: string,\n clearError: () => void,\n cardError: string,\n lastComment: string,\n /** Full subscription details including card last 4 — available after SUBSCRIPTION_CREATED. */\n subscriptionDetails: import('../../types').SubscriptionDetails | null,\n billing: BillingInfo,\n setField: (field: keyof BillingInfo, value: string) => void,\n numberMountRef: Ref<HTMLDivElement>,\n expiryMountRef: Ref<HTMLDivElement>,\n cvcMountRef: Ref<HTMLDivElement>,\n submitting: boolean,\n /** True from Place order click until a terminal status is reached. */\n processingSubscription: boolean,\n /** True when Stripe sent the learner back with ?cart_bi_id=. */\n returningFromStripe: boolean,\n placeOrder: (consent: CheckoutConsent, paymentMethod?: SupportedPaymentMethod) => void,\n /**\n * Tokenizes the mounted Stripe card Element into a PaymentMethod id.\n * Used by à la carte checkout (no createTwouCart).\n */\n createCardPaymentMethod: () => Promise<string>,\n /** Confirms the PaymentIntent from tCheckoutPlaceOrder with the mounted card. */\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n /** Currently selected billing cycle. */\n selectedCycle: BillingCycle,\n /** Switch between monthly and annual billing. */\n setCycle: (cycle: BillingCycle) => void,\n /** True once GET /edx-session has returned and the viax token is set. */\n sessionReady: boolean,\n /** ISO 3166-1 alpha-2 country code — passed in from CheckoutGate for the UK/CA checkbox. */\n userCountry: string | null,\n /** Course details when arrived via ?course_run_key=; null for standard checkout. */\n course: import('../../types').CourseDetail | null,\n /** The Stripe.js instance — needed by ExpressCheckout for Apple Pay / Google Pay. */\n stripe: StripeLike | null,\n /** Tracks empty/complete state of each Stripe card element for validation. */\n cardFieldStates: CardFieldStates,\n}\n\nexport default function useCheckout(options: UseCheckoutOptions = {}): UseCheckoutResult {\n const {\n apiUrl = DEFAULTS.apiUrl,\n edxSessionUrl,\n stripePublishableKey,\n onComplete,\n userCountry = null,\n courseRunKey: courseRunKeyProp,\n redirectToUrl = redirectBrowser,\n } = options;\n\n const courseRunKey = useMemo(\n () => courseRunKeyProp ?? getCourseRunKeyFromUrl(),\n [courseRunKeyProp],\n );\n\n const client = useMemo<CheckoutClient | null>(() => {\n if (!apiUrl) return null;\n return createCheckoutClient({ apiUrl });\n }, [apiUrl]);\n\n const notConfigured = !client || !stripePublishableKey || !edxSessionUrl;\n\n // Exchange edX session for viax token on page load.\n const sessionQuery = useViaxSessionQuery(notConfigured ? null : edxSessionUrl!);\n const sessionReady = !!sessionQuery.data;\n\n useEffect(() => {\n if (sessionQuery.data && client) {\n client.setToken(sessionQuery.data.viaxToken);\n }\n }, [sessionQuery.data, client]);\n\n // Fetch optional course detail once the viax session is ready.\n const courseDetailQuery = useCourseDetailQuery(client, sessionReady, courseRunKey);\n\n // --- state ---\n const [showBanner, setShowBanner] = useState(true);\n const [cart, setCart] = useState<Cart | null>(null);\n const [stripe, setStripe] = useState<StripeLike | null>(null);\n const [stripeReady, setStripeReady] = useState(false);\n const [cardReady, setCardReady] = useState(false);\n const [error, setError] = useState('');\n const [cardError, setCardError] = useState('');\n const [cardFieldStates, setCardFieldStates] = useState<CardFieldStates>({\n number: { empty: true, complete: false, error: '', blurred: false },\n expiry: { empty: true, complete: false, error: '', blurred: false },\n cvc: { empty: true, complete: false, error: '', blurred: false },\n });\n const [billing, setBilling] = useState<BillingInfo>({\n firstName: '', lastName: '', city: '', zip: '', country: getInitialBillingCountry(userCountry), line1: '', state: '',\n });\n const [selectedCycle, setSelectedCycle] = useState<BillingCycle>(getPlanFromUrl);\n // Set when Stripe returns from a 3DS challenge; cleared once a new attempt starts.\n const [returnedCartBiId, setReturnedCartBiId] = useState<string | null>(getReturnedCartBiIdFromUrl);\n const [processingSubscription, setProcessingSubscription] = useState(\n () => getReturnedCartBiIdFromUrl() !== null,\n );\n\n const setCycle = useCallback((cycle: BillingCycle) => {\n setSelectedCycle(cycle);\n writePlanQueryParam(cycle);\n }, []);\n\n useEffect(() => {\n if (sessionQuery.error) {\n setError(sessionQuery.error.message);\n }\n }, [sessionQuery.error]);\n\n const setField = useCallback(\n (field: keyof BillingInfo, value: string) => setBilling((p: BillingInfo) => ({ ...p, [field]: value })),\n [],\n );\n\n // --- refs ---\n // Stripe instances — written inside useEffect and mutations.\n const stripeInstanceRef = useRef<StripeLike | null>(null);\n const cardNumberElementRef = useRef<StripeElementLike | null>(null);\n const cardExpiryElementRef = useRef<StripeElementLike | null>(null);\n const cardCvcElementRef = useRef<StripeElementLike | null>(null);\n const elementsRef = useRef<StripeElementsLike | null>(null);\n // DOM mount targets — callback refs so we remount when hosts leave and return.\n const numberMountNodeRef = useRef<HTMLDivElement | null>(null);\n const expiryMountNodeRef = useRef<HTMLDivElement | null>(null);\n const cvcMountNodeRef = useRef<HTMLDivElement | null>(null);\n const [cardHostsEpoch, setCardHostsEpoch] = useState(0);\n\n const assignCardHost = useCallback((\n store: { current: HTMLDivElement | null },\n node: HTMLDivElement | null,\n ) => {\n store.current = node;\n if (\n numberMountNodeRef.current\n && expiryMountNodeRef.current\n && cvcMountNodeRef.current\n ) {\n setCardHostsEpoch((n) => n + 1);\n return;\n }\n // Hosts are leaving the DOM — detach so Stripe is not bound to dead nodes.\n cardNumberElementRef.current?.unmount?.();\n cardExpiryElementRef.current?.unmount?.();\n cardCvcElementRef.current?.unmount?.();\n }, []);\n\n const numberMountRef = useCallback((node: HTMLDivElement | null) => {\n assignCardHost(numberMountNodeRef, node);\n }, [assignCardHost]);\n const expiryMountRef = useCallback((node: HTMLDivElement | null) => {\n assignCardHost(expiryMountNodeRef, node);\n }, [assignCardHost]);\n const cvcMountRef = useCallback((node: HTMLDivElement | null) => {\n assignCardHost(cvcMountNodeRef, node);\n }, [assignCardHost]);\n const mountedRef = useRef(true);\n const completedRef = useRef(false);\n const redirectedUrlRef = useRef<string | null>(null);\n const sessionRef = useRef<EdxSessionResult | null>(null);\n\n useEffect(() => {\n sessionRef.current = sessionQuery.data ?? null;\n }, [sessionQuery.data]);\n\n // Mirror state into refs so mutations can read current values without stale closures.\n const cartRef = useRef<Cart | null>(null);\n const billingRef = useRef<BillingInfo>(billing);\n const selectedCycleRef = useRef<BillingCycle>(selectedCycle);\n\n useEffect(() => {\n cartRef.current = cart;\n }, [cart]);\n useEffect(() => {\n billingRef.current = billing;\n }, [billing]);\n useEffect(() => {\n selectedCycleRef.current = selectedCycle;\n }, [selectedCycle]);\n useEffect(() => {\n stripeInstanceRef.current = stripe;\n }, [stripe]);\n\n // --- server state ---\n // Poll the cart created in this session, or the one Stripe just returned\n // from. The returned cart waits for the viax session so the status request\n // carries a valid token.\n const cartBiId = cart?.biId ?? (sessionReady ? returnedCartBiId : null);\n const statusQuery = useCartStatusQuery(client, cartBiId);\n const status: Status | null = statusQuery.data?.status ?? null;\n const lastComment = statusQuery.data?.lastComment ?? '';\n\n // Phase is 'ready' until a cart exists, then driven by bpStatus.\n const phase: Phase = cartBiId ? phaseFor(status, status === STATUS.IN_CART) : 'ready';\n const subscriptionIdQuery = useSubscriptionIdQuery(\n client, cartBiId, status === STATUS.SUBSCRIPTION_CREATED,\n );\n\n // --- mutations ---\n const { placeOrder: placeMutation } = useCheckoutMutations({\n client,\n sessionRef,\n stripeRef: stripeInstanceRef,\n cardNumberElRef: cardNumberElementRef,\n billingRef,\n selectedCycleRef,\n setError: (msg: string) => {\n setProcessingSubscription(false);\n setError(msg);\n },\n setCardError: (msg: string) => {\n setProcessingSubscription(false);\n setCardError(msg);\n },\n onCartCreated: (created: Cart) => {\n // Sync the ref immediately so any same-tick readers see the cart\n // before the setCart → useEffect mirror runs.\n cartRef.current = created;\n if (mountedRef.current) setCart(created);\n },\n });\n\n // --- load Stripe.js on mount ---\n useEffect(() => {\n mountedRef.current = true;\n if (!stripePublishableKey) return undefined;\n\n (async () => {\n try {\n const instance = await loadStripe(stripePublishableKey);\n if (mountedRef.current) {\n setStripe(instance);\n setStripeReady(true);\n }\n } catch (e: unknown) {\n if (mountedRef.current) setError((e as Error).message);\n }\n })();\n\n return () => {\n mountedRef.current = false;\n };\n }, [stripePublishableKey]);\n\n // --- mount Stripe Elements once Stripe is loaded; remount if hosts return ---\n useEffect(() => {\n if (!stripe) return;\n const numberNode = numberMountNodeRef.current;\n const expiryNode = expiryMountNodeRef.current;\n const cvcNode = cvcMountNodeRef.current;\n if (!numberNode || !expiryNode || !cvcNode) return;\n\n if (cardNumberElementRef.current && cardExpiryElementRef.current && cardCvcElementRef.current) {\n try {\n cardNumberElementRef.current.unmount?.();\n cardExpiryElementRef.current.unmount?.();\n cardCvcElementRef.current.unmount?.();\n cardNumberElementRef.current.mount(numberNode);\n cardExpiryElementRef.current.mount(expiryNode);\n cardCvcElementRef.current.mount(cvcNode);\n return;\n } catch {\n // Element is bound to a destroyed host — create a fresh Elements group.\n cardNumberElementRef.current = null;\n cardExpiryElementRef.current = null;\n cardCvcElementRef.current = null;\n elementsRef.current = null;\n }\n }\n\n setCardReady(false);\n\n const elements = stripe.elements();\n elementsRef.current = elements;\n\n const number = elements.create('cardNumber', STRIPE_CARD_NUMBER_OPTS);\n const expiry = elements.create('cardExpiry', STRIPE_ELEMENT_OPTS);\n const cvc = elements.create('cardCvc', STRIPE_ELEMENT_OPTS);\n\n number.mount(numberNode);\n expiry.mount(expiryNode);\n cvc.mount(cvcNode);\n\n number.on('ready', () => {\n if (mountedRef.current) setCardReady(true);\n });\n number.on('change', (e) => {\n if (mountedRef.current) {\n // Keep Stripe's own copy on the input; custom messages are for banners only.\n setCardError(e.error?.message || '');\n setCardFieldStates((prev) => ({\n ...prev,\n number: {\n ...prev.number,\n empty: !!e.empty,\n complete: !!e.complete,\n error: e.error?.message || '',\n },\n }));\n }\n });\n number.on('blur', () => {\n if (mountedRef.current) {\n setCardFieldStates((prev) => ({\n ...prev,\n number: { ...prev.number, blurred: true },\n }));\n }\n });\n expiry.on('change', (e) => {\n if (mountedRef.current) {\n setCardFieldStates((prev) => ({\n ...prev,\n expiry: {\n ...prev.expiry,\n empty: !!e.empty,\n complete: !!e.complete,\n error: e.error?.message || '',\n },\n }));\n }\n });\n expiry.on('blur', () => {\n if (mountedRef.current) {\n setCardFieldStates((prev) => ({\n ...prev,\n expiry: { ...prev.expiry, blurred: true },\n }));\n }\n });\n cvc.on('change', (e) => {\n if (mountedRef.current) {\n setCardFieldStates((prev) => ({\n ...prev,\n cvc: {\n ...prev.cvc,\n empty: !!e.empty,\n complete: !!e.complete,\n error: e.error?.message || '',\n },\n }));\n }\n });\n cvc.on('blur', () => {\n if (mountedRef.current) {\n setCardFieldStates((prev) => ({\n ...prev,\n cvc: { ...prev.cvc, blurred: true },\n }));\n }\n });\n\n cardNumberElementRef.current = number;\n cardExpiryElementRef.current = expiry;\n cardCvcElementRef.current = cvc;\n }, [stripe, cardHostsEpoch]);\n\n // --- on success ---\n useEffect(() => {\n if (status !== STATUS.SUBSCRIPTION_CREATED || completedRef.current) return;\n completedRef.current = true;\n if (onComplete) onComplete(subscriptionIdQuery.data?.biId ?? null);\n }, [status, subscriptionIdQuery.data, onComplete]);\n\n // Stripe and PayPal both authenticate the buyer on a hosted page before\n // returning the browser to checkout, so this must be a full-page\n // navigation (not an API request) for either flow.\n useEffect(() => {\n const redirectUrl = statusQuery.data?.stripeConfirmationRedirectUrl\n || statusQuery.data?.paypalApprovalUrl;\n if (\n status !== STATUS.STRIPE_CONFIRMATION_REQUIRED\n || !redirectUrl\n || redirectedUrlRef.current === redirectUrl\n ) return;\n\n redirectedUrlRef.current = redirectUrl;\n redirectToUrl(redirectUrl);\n }, [\n status,\n statusQuery.data?.stripeConfirmationRedirectUrl,\n statusQuery.data?.paypalApprovalUrl,\n redirectToUrl,\n ]);\n\n // --- actions ---\n const placeOrder = useCallback((consent: CheckoutConsent, paymentMethod: SupportedPaymentMethod = 'card') => {\n setError('');\n setCardError('');\n if (phase === 'failed') setCart(null);\n // Each attempt creates its own cart, so stop reporting on the returned one.\n setReturnedCartBiId(null);\n setProcessingSubscription(true);\n placeMutation.mutate({ consent, paymentMethod });\n }, [placeMutation, phase]);\n\n const createCardPaymentMethod = useCallback(async () => {\n setCardError('');\n const stripeInstance = stripeInstanceRef.current;\n const card = cardNumberElementRef.current;\n if (!stripeInstance || !card) {\n throw new Error('Checkout is not ready yet. Please wait for the card fields to load.');\n }\n try {\n return await tokenizeCard({\n stripe: stripeInstance,\n card,\n billing: billingRef.current,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : 'There was a problem with your card.';\n setCardError(message);\n throw err;\n }\n }, []);\n\n const confirmCardPayment = useCallback(async (clientSecret: string) => {\n setCardError('');\n const stripeInstance = stripeInstanceRef.current;\n const card = cardNumberElementRef.current;\n if (!stripeInstance || !card) {\n throw new Error('Checkout is not ready yet. Please wait for the card fields to load.');\n }\n try {\n await confirmStripeCardPayment({\n stripe: stripeInstance,\n card,\n billing: billingRef.current,\n clientSecret,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : 'There was a problem with your card.';\n setCardError(message);\n throw err;\n }\n }, []);\n\n // Turn off processingSubscription once we reach a terminal status.\n useEffect(() => {\n if (!status) return;\n const terminalStatuses: Status[] = [\n STATUS.SUBSCRIPTION_CREATED,\n STATUS.STRIPE_FAIL,\n STATUS.SANCTION_BLOCK,\n ];\n if (terminalStatuses.includes(status)) {\n setProcessingSubscription(false);\n }\n }, [status]);\n\n return {\n phase,\n status,\n statusLabel: (status && STATUS_LABELS[status]) || 'Processing your order…',\n stripeReady,\n cardReady,\n notConfigured,\n showBanner,\n dismissBanner: () => setShowBanner(false),\n error,\n clearError: () => setError(''),\n cardError,\n lastComment,\n subscriptionDetails: subscriptionIdQuery.data ?? null,\n billing,\n setField,\n numberMountRef,\n expiryMountRef,\n cvcMountRef,\n submitting: placeMutation.isPending,\n processingSubscription,\n returningFromStripe: returnedCartBiId !== null,\n placeOrder,\n createCardPaymentMethod,\n confirmCardPayment,\n selectedCycle,\n setCycle,\n sessionReady,\n userCountry,\n course: courseDetailQuery.data ?? null,\n stripe,\n cardFieldStates,\n };\n}\n"]}
1
+ {"version":3,"file":"useCheckout.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckout.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n useCallback, useEffect, useMemo, useRef, useState,\n} from 'react';\nimport type { Ref } from 'react';\nimport type { BillingCycle } from '../../types';\nimport { createCheckoutClient } from '../../utils/checkout';\nimport { getPlanFromUrl, writePlanQueryParam } from '../../utils/planQuery';\nimport { getReturnedCartBiIdFromUrl, loadStripe } from '../../utils/stripe';\nimport { getCourseRunKeyFromUrl } from '../../utils/url';\nimport {\n STATUS,\n STATUS_LABELS,\n STRIPE_ELEMENT_OPTS,\n STRIPE_CARD_NUMBER_OPTS,\n phaseFor,\n DEFAULTS,\n BILLING_COUNTRIES,\n} from '../../constants';\nimport type {\n Status, Phase, Cart, BillingInfo, CheckoutClient, StripeLike, StripeElementLike,\n StripeElementsLike, CheckoutConsent, SupportedPaymentMethod, CardFieldStates,\n} from '../../types';\nimport {\n useCartStatusQuery, useSubscriptionIdQuery,\n useViaxSessionQuery, useCourseDetailQuery,\n} from './useCheckoutQueries';\nimport type { EdxSessionResult } from '../../utils/provision';\nimport { useCheckoutMutations } from './useCheckoutMutations';\nimport {\n createCardPaymentMethod as tokenizeCard,\n confirmCardPayment as confirmStripeCardPayment,\n} from '../../utils/stripeCard';\n\nconst redirectBrowser = (url: string): void => {\n window.location.assign(url);\n};\n\nconst getInitialBillingCountry = (userCountry: string | null): string => {\n const normalized = userCountry?.trim().toUpperCase();\n if (normalized && BILLING_COUNTRIES.some(([countryCode]) => countryCode === normalized)) {\n return normalized;\n }\n return '';\n};\n\nexport interface UseCheckoutOptions {\n apiUrl?: string,\n edxSessionUrl?: string,\n stripePublishableKey?: string,\n onComplete?: (subscriptionBiId: string | null) => void,\n /** Country code resolved by CheckoutGate — used to show/hide the UK/CA extra checkbox. */\n userCountry?: string | null,\n /**\n * Course run key from the URL (e.g. course-v1:HarvardX+CS50+X).\n * When omitted, read from ?course_run_key=.\n */\n courseRunKey?: string | null,\n /** Override only for tests; production performs a full-page browser redirect. */\n redirectToUrl?: (url: string) => void,\n}\n\nexport interface UseCheckoutResult {\n phase: Phase,\n status: Status | null,\n statusLabel: string,\n stripeReady: boolean,\n /** True once the Stripe card Element iframe is fully initialised and interactive. */\n cardReady: boolean,\n notConfigured: boolean,\n showBanner: boolean,\n dismissBanner: () => void,\n error: string,\n clearError: () => void,\n cardError: string,\n lastComment: string,\n /** Full subscription details including card last 4 — available after SUBSCRIPTION_CREATED. */\n subscriptionDetails: import('../../types').SubscriptionDetails | null,\n billing: BillingInfo,\n setField: (field: keyof BillingInfo, value: string) => void,\n numberMountRef: Ref<HTMLDivElement>,\n expiryMountRef: Ref<HTMLDivElement>,\n cvcMountRef: Ref<HTMLDivElement>,\n submitting: boolean,\n /** True from Place order click until a terminal status is reached. */\n processingSubscription: boolean,\n /** True when Stripe sent the learner back with ?cart_bi_id=. */\n returningFromStripe: boolean,\n placeOrder: (consent: CheckoutConsent, paymentMethod?: SupportedPaymentMethod) => void,\n /**\n * Tokenizes the mounted Stripe card Element into a PaymentMethod id.\n * Used by à la carte checkout (no createTwouCart).\n */\n createCardPaymentMethod: () => Promise<string>,\n /** Confirms the PaymentIntent from tCheckoutPlaceOrder with the mounted card. */\n confirmCardPayment: (clientSecret: string) => Promise<void>,\n /** Currently selected billing cycle. */\n selectedCycle: BillingCycle,\n /** Switch between monthly and annual billing. */\n setCycle: (cycle: BillingCycle) => void,\n /** True once GET /edx-session has returned and the viax token is set. */\n sessionReady: boolean,\n /** ISO 3166-1 alpha-2 country code — passed in from CheckoutGate for the UK/CA checkbox. */\n userCountry: string | null,\n /** Course details when arrived via ?course_run_key=; null for standard checkout. */\n course: import('../../types').CourseDetail | null,\n /** The Stripe.js instance — needed by ExpressCheckout for Apple Pay / Google Pay. */\n stripe: StripeLike | null,\n /** Tracks empty/complete state of each Stripe card element for validation. */\n cardFieldStates: CardFieldStates,\n}\n\nexport default function useCheckout(options: UseCheckoutOptions = {}): UseCheckoutResult {\n const {\n apiUrl = DEFAULTS.apiUrl,\n edxSessionUrl,\n stripePublishableKey,\n onComplete,\n userCountry = null,\n courseRunKey: courseRunKeyProp,\n redirectToUrl = redirectBrowser,\n } = options;\n\n const courseRunKey = useMemo(\n () => courseRunKeyProp ?? getCourseRunKeyFromUrl(),\n [courseRunKeyProp],\n );\n\n const client = useMemo<CheckoutClient | null>(() => {\n if (!apiUrl) return null;\n return createCheckoutClient({ apiUrl });\n }, [apiUrl]);\n\n const notConfigured = !client || !stripePublishableKey || !edxSessionUrl;\n\n // Exchange edX session for viax token on page load.\n const sessionQuery = useViaxSessionQuery(notConfigured ? null : edxSessionUrl!);\n const sessionReady = !!sessionQuery.data;\n\n useEffect(() => {\n if (sessionQuery.data && client) {\n client.setToken(sessionQuery.data.viaxToken);\n }\n }, [sessionQuery.data, client]);\n\n // Fetch optional course detail once the viax session is ready.\n const courseDetailQuery = useCourseDetailQuery(client, sessionReady, courseRunKey);\n\n // --- state ---\n const [showBanner, setShowBanner] = useState(true);\n const [cart, setCart] = useState<Cart | null>(null);\n const [stripe, setStripe] = useState<StripeLike | null>(null);\n const [stripeReady, setStripeReady] = useState(false);\n const [cardReady, setCardReady] = useState(false);\n const [error, setError] = useState('');\n const [cardError, setCardError] = useState('');\n const [cardFieldStates, setCardFieldStates] = useState<CardFieldStates>({\n number: { empty: true, complete: false, error: '', blurred: false },\n expiry: { empty: true, complete: false, error: '', blurred: false },\n cvc: { empty: true, complete: false, error: '', blurred: false },\n });\n const [billing, setBilling] = useState<BillingInfo>({\n firstName: '', lastName: '', city: '', zip: '', country: getInitialBillingCountry(userCountry), line1: '', state: '',\n });\n const [selectedCycle, setSelectedCycle] = useState<BillingCycle>(getPlanFromUrl);\n // Set when Stripe returns from a 3DS challenge; cleared once a new attempt starts.\n const [returnedCartBiId, setReturnedCartBiId] = useState<string | null>(getReturnedCartBiIdFromUrl);\n const [processingSubscription, setProcessingSubscription] = useState(\n () => getReturnedCartBiIdFromUrl() !== null,\n );\n\n const setCycle = useCallback((cycle: BillingCycle) => {\n setSelectedCycle(cycle);\n writePlanQueryParam(cycle);\n }, []);\n\n useEffect(() => {\n if (sessionQuery.error) {\n setError(sessionQuery.error.message);\n }\n }, [sessionQuery.error]);\n\n const setField = useCallback(\n (field: keyof BillingInfo, value: string) => setBilling((p: BillingInfo) => ({ ...p, [field]: value })),\n [],\n );\n\n // --- refs ---\n // Stripe instances — written inside useEffect and mutations.\n const stripeInstanceRef = useRef<StripeLike | null>(null);\n const cardNumberElementRef = useRef<StripeElementLike | null>(null);\n const cardExpiryElementRef = useRef<StripeElementLike | null>(null);\n const cardCvcElementRef = useRef<StripeElementLike | null>(null);\n const elementsRef = useRef<StripeElementsLike | null>(null);\n // DOM mount targets — callback refs so we remount when hosts leave and return.\n const numberMountNodeRef = useRef<HTMLDivElement | null>(null);\n const expiryMountNodeRef = useRef<HTMLDivElement | null>(null);\n const cvcMountNodeRef = useRef<HTMLDivElement | null>(null);\n const [cardHostsEpoch, setCardHostsEpoch] = useState(0);\n\n const assignCardHost = useCallback((\n store: { current: HTMLDivElement | null },\n node: HTMLDivElement | null,\n ) => {\n store.current = node;\n if (\n numberMountNodeRef.current\n && expiryMountNodeRef.current\n && cvcMountNodeRef.current\n ) {\n setCardHostsEpoch((n) => n + 1);\n return;\n }\n // Hosts are leaving the DOM — detach so Stripe is not bound to dead nodes.\n cardNumberElementRef.current?.unmount?.();\n cardExpiryElementRef.current?.unmount?.();\n cardCvcElementRef.current?.unmount?.();\n }, []);\n\n const numberMountRef = useCallback((node: HTMLDivElement | null) => {\n assignCardHost(numberMountNodeRef, node);\n }, [assignCardHost]);\n const expiryMountRef = useCallback((node: HTMLDivElement | null) => {\n assignCardHost(expiryMountNodeRef, node);\n }, [assignCardHost]);\n const cvcMountRef = useCallback((node: HTMLDivElement | null) => {\n assignCardHost(cvcMountNodeRef, node);\n }, [assignCardHost]);\n const mountedRef = useRef(true);\n const completedRef = useRef(false);\n const redirectedUrlRef = useRef<string | null>(null);\n const sessionRef = useRef<EdxSessionResult | null>(null);\n\n useEffect(() => {\n sessionRef.current = sessionQuery.data ?? null;\n }, [sessionQuery.data]);\n\n // Mirror state into refs so mutations can read current values without stale closures.\n const cartRef = useRef<Cart | null>(null);\n const billingRef = useRef<BillingInfo>(billing);\n const selectedCycleRef = useRef<BillingCycle>(selectedCycle);\n\n useEffect(() => {\n cartRef.current = cart;\n }, [cart]);\n useEffect(() => {\n billingRef.current = billing;\n }, [billing]);\n useEffect(() => {\n selectedCycleRef.current = selectedCycle;\n }, [selectedCycle]);\n useEffect(() => {\n stripeInstanceRef.current = stripe;\n }, [stripe]);\n\n // --- server state ---\n // Poll the cart created in this session, or the one Stripe just returned\n // from. The returned cart waits for the viax session so the status request\n // carries a valid token.\n const cartBiId = cart?.biId ?? (sessionReady ? returnedCartBiId : null);\n const statusQuery = useCartStatusQuery(client, cartBiId);\n const status: Status | null = statusQuery.data?.status ?? null;\n const lastComment = statusQuery.data?.lastComment ?? '';\n\n // Phase is 'ready' until a cart exists, then driven by bpStatus.\n const phase: Phase = cartBiId ? phaseFor(status, status === STATUS.IN_CART) : 'ready';\n const subscriptionIdQuery = useSubscriptionIdQuery(\n client, cartBiId, status === STATUS.SUBSCRIPTION_CREATED,\n );\n\n // --- mutations ---\n const { placeOrder: placeMutation } = useCheckoutMutations({\n client,\n sessionRef,\n stripeRef: stripeInstanceRef,\n cardNumberElRef: cardNumberElementRef,\n billingRef,\n selectedCycleRef,\n setError: (msg: string) => {\n setProcessingSubscription(false);\n setError(msg);\n },\n setCardError: (msg: string) => {\n setProcessingSubscription(false);\n setCardError(msg);\n },\n onCartCreated: (created: Cart) => {\n // Sync the ref immediately so any same-tick readers see the cart\n // before the setCart → useEffect mirror runs.\n cartRef.current = created;\n if (mountedRef.current) setCart(created);\n },\n });\n\n // --- load Stripe.js on mount ---\n useEffect(() => {\n mountedRef.current = true;\n if (!stripePublishableKey) return undefined;\n\n (async () => {\n try {\n const instance = await loadStripe(stripePublishableKey);\n if (mountedRef.current) {\n setStripe(instance);\n setStripeReady(true);\n }\n } catch (e: unknown) {\n if (mountedRef.current) setError((e as Error).message);\n }\n })();\n\n return () => {\n mountedRef.current = false;\n };\n }, [stripePublishableKey]);\n\n // --- mount Stripe Elements once Stripe is loaded; remount if hosts return ---\n useEffect(() => {\n if (!stripe) return;\n const numberNode = numberMountNodeRef.current;\n const expiryNode = expiryMountNodeRef.current;\n const cvcNode = cvcMountNodeRef.current;\n if (!numberNode || !expiryNode || !cvcNode) return;\n\n if (cardNumberElementRef.current && cardExpiryElementRef.current && cardCvcElementRef.current) {\n try {\n cardNumberElementRef.current.unmount?.();\n cardExpiryElementRef.current.unmount?.();\n cardCvcElementRef.current.unmount?.();\n cardNumberElementRef.current.mount(numberNode);\n cardExpiryElementRef.current.mount(expiryNode);\n cardCvcElementRef.current.mount(cvcNode);\n return;\n } catch {\n // Element is bound to a destroyed host — create a fresh Elements group.\n cardNumberElementRef.current = null;\n cardExpiryElementRef.current = null;\n cardCvcElementRef.current = null;\n elementsRef.current = null;\n }\n }\n\n setCardReady(false);\n\n const elements = stripe.elements();\n elementsRef.current = elements;\n\n const number = elements.create('cardNumber', STRIPE_CARD_NUMBER_OPTS);\n const expiry = elements.create('cardExpiry', STRIPE_ELEMENT_OPTS);\n const cvc = elements.create('cardCvc', STRIPE_ELEMENT_OPTS);\n\n number.mount(numberNode);\n expiry.mount(expiryNode);\n cvc.mount(cvcNode);\n\n number.on('ready', () => {\n if (mountedRef.current) setCardReady(true);\n });\n number.on('change', (e) => {\n if (mountedRef.current) {\n // Keep Stripe's own copy on the input; custom messages are for banners only.\n setCardError(e.error?.message || '');\n setCardFieldStates((prev) => ({\n ...prev,\n number: {\n ...prev.number,\n empty: !!e.empty,\n complete: !!e.complete,\n error: e.error?.message || '',\n },\n }));\n }\n });\n number.on('blur', () => {\n if (mountedRef.current) {\n setCardFieldStates((prev) => ({\n ...prev,\n number: { ...prev.number, blurred: true },\n }));\n }\n });\n expiry.on('change', (e) => {\n if (mountedRef.current) {\n setCardFieldStates((prev) => ({\n ...prev,\n expiry: {\n ...prev.expiry,\n empty: !!e.empty,\n complete: !!e.complete,\n error: e.error?.message || '',\n },\n }));\n }\n });\n expiry.on('blur', () => {\n if (mountedRef.current) {\n setCardFieldStates((prev) => ({\n ...prev,\n expiry: { ...prev.expiry, blurred: true },\n }));\n }\n });\n cvc.on('change', (e) => {\n if (mountedRef.current) {\n setCardFieldStates((prev) => ({\n ...prev,\n cvc: {\n ...prev.cvc,\n empty: !!e.empty,\n complete: !!e.complete,\n error: e.error?.message || '',\n },\n }));\n }\n });\n cvc.on('blur', () => {\n if (mountedRef.current) {\n setCardFieldStates((prev) => ({\n ...prev,\n cvc: { ...prev.cvc, blurred: true },\n }));\n }\n });\n\n cardNumberElementRef.current = number;\n cardExpiryElementRef.current = expiry;\n cardCvcElementRef.current = cvc;\n }, [stripe, cardHostsEpoch]);\n\n // --- on success ---\n useEffect(() => {\n if (status !== STATUS.SUBSCRIPTION_CREATED || completedRef.current) return;\n completedRef.current = true;\n if (onComplete) onComplete(subscriptionIdQuery.data?.biId ?? null);\n }, [status, subscriptionIdQuery.data, onComplete]);\n\n // Stripe and PayPal both authenticate the buyer on a hosted page before\n // returning the browser to checkout, so this must be a full-page\n // navigation (not an API request) for either flow.\n useEffect(() => {\n const redirectUrl = statusQuery.data?.stripeConfirmationRedirectUrl\n || statusQuery.data?.paypalApprovalUrl;\n if (\n status !== STATUS.STRIPE_CONFIRMATION_REQUIRED\n || !redirectUrl\n || redirectedUrlRef.current === redirectUrl\n ) return;\n\n redirectedUrlRef.current = redirectUrl;\n redirectToUrl(redirectUrl);\n }, [\n status,\n statusQuery.data?.stripeConfirmationRedirectUrl,\n statusQuery.data?.paypalApprovalUrl,\n redirectToUrl,\n ]);\n\n // --- actions ---\n const placeOrder = useCallback((consent: CheckoutConsent, paymentMethod: SupportedPaymentMethod = 'card') => {\n setError('');\n setCardError('');\n if (phase === 'failed') setCart(null);\n // Each attempt creates its own cart, so stop reporting on the returned one.\n setReturnedCartBiId(null);\n setProcessingSubscription(true);\n placeMutation.mutate({ consent, paymentMethod });\n }, [placeMutation, phase]);\n\n const createCardPaymentMethod = useCallback(async () => {\n setCardError('');\n const stripeInstance = stripeInstanceRef.current;\n const card = cardNumberElementRef.current;\n if (!stripeInstance || !card) {\n throw new Error('Checkout is not ready yet. Please wait for the card fields to load.');\n }\n try {\n return await tokenizeCard({\n stripe: stripeInstance,\n card,\n billing: billingRef.current,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : 'There was a problem with your card.';\n setCardError(message);\n throw err;\n }\n }, []);\n\n const confirmCardPayment = useCallback(async (clientSecret: string) => {\n setCardError('');\n const stripeInstance = stripeInstanceRef.current;\n const card = cardNumberElementRef.current;\n if (!stripeInstance || !card) {\n throw new Error('Checkout is not ready yet. Please wait for the card fields to load.');\n }\n try {\n await confirmStripeCardPayment({\n stripe: stripeInstance,\n card,\n billing: billingRef.current,\n clientSecret,\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : 'There was a problem with your card.';\n setCardError(message);\n throw err;\n }\n }, []);\n\n // Turn off processingSubscription once we reach a terminal status.\n useEffect(() => {\n if (!status) return;\n const terminalStatuses: Status[] = [\n STATUS.SUBSCRIPTION_CREATED,\n STATUS.STRIPE_FAIL,\n STATUS.SANCTION_BLOCK,\n ];\n if (terminalStatuses.includes(status)) {\n setProcessingSubscription(false);\n }\n }, [status]);\n\n return {\n phase,\n status,\n statusLabel: (status && STATUS_LABELS[status]) || 'Processing your order…',\n stripeReady,\n cardReady,\n notConfigured,\n showBanner,\n dismissBanner: () => setShowBanner(false),\n error,\n clearError: () => setError(''),\n cardError,\n lastComment,\n subscriptionDetails: subscriptionIdQuery.data ?? null,\n billing,\n setField,\n numberMountRef,\n expiryMountRef,\n cvcMountRef,\n submitting: placeMutation.isPending,\n processingSubscription,\n returningFromStripe: returnedCartBiId !== null,\n placeOrder,\n createCardPaymentMethod,\n confirmCardPayment,\n selectedCycle,\n setCycle,\n sessionReady,\n userCountry,\n course: courseDetailQuery.data ?? null,\n stripe,\n cardFieldStates,\n };\n}\n"]}
@@ -2,11 +2,15 @@ export interface UseCheckoutGateOptions {
2
2
  /** Kept for call-site compatibility; no longer used (subscriber check removed). */
3
3
  apiUrl?: string;
4
4
  edxSessionUrl?: string;
5
+ /** Endpoint used to resolve city/etc. for the learner's public IP. */
6
+ geolocationUrl?: string;
7
+ /** ALC-307 compliance-check endpoint — decides whether the learner is embargoed. */
8
+ complianceCheckUrl?: string;
5
9
  learnerDashboardUrl?: string;
6
10
  }
7
11
  export type GateStatus = 'loading' | 'blocked' | 'allowed' | 'error';
8
12
  export interface UseCheckoutGateResult {
9
- /** 'loading' while session/checks are in flight, 'blocked' if country not allowed, 'allowed' if clear, 'error' if session failed. */
13
+ /** 'loading' while checks are in flight, 'blocked' if the compliance check rejects the learner, 'allowed' if clear, 'error' if session or compliance check failed. */
10
14
  gateStatus: GateStatus;
11
15
  /** ISO 3166-1 alpha-2 country code — passed down to Checkout for the UK/CA checkbox. */
12
16
  userCountry: string | null;
@@ -15,10 +19,11 @@ export interface UseCheckoutGateResult {
15
19
  gateMessage: string | null;
16
20
  }
17
21
  /**
18
- * Session + optional country gate for à la carte checkout.
22
+ * Session + embargo compliance gate for à la carte checkout.
19
23
  *
20
- * An edX Plus subscription does not give ownership of a course/program
21
- * (see alacarte-owned-products-api.md), so this gate no longer calls
22
- * tIsCurrentUserSubscriber or blocks existing subscribers.
24
+ * Calls the ALC-307 compliance-check endpoint with the learner's identity,
25
+ * account-profile country, and public IP/city. The compliance-check call
26
+ * fails closed (gateStatus = 'error') — unlike the profile/IP/geolocation
27
+ * lookups that merely source its request body and fail open.
23
28
  */
24
29
  export declare function useCheckoutGate(options?: UseCheckoutGateOptions): UseCheckoutGateResult;
@@ -1,46 +1,87 @@
1
- import { ALLOWED_CHECKOUT_COUNTRIES, GATE_CHECK_COUNTRY, } from '../../constants';
2
- import { useViaxSessionQuery, useUserCountryQuery, } from './useCheckoutQueries';
1
+ import { useProductSelector } from './useAlacarteCheckout';
2
+ import { getCourseRunKeyFromUrl } from '../../utils/url';
3
+ import { useViaxSessionQuery, useUserProfileQuery, usePublicIpQuery, useIpGeolocationQuery, useComplianceCheckQuery, } from './useCheckoutQueries';
3
4
  /**
4
- * Session + optional country gate for à la carte checkout.
5
+ * Session + embargo compliance gate for à la carte checkout.
5
6
  *
6
- * An edX Plus subscription does not give ownership of a course/program
7
- * (see alacarte-owned-products-api.md), so this gate no longer calls
8
- * tIsCurrentUserSubscriber or blocks existing subscribers.
7
+ * Calls the ALC-307 compliance-check endpoint with the learner's identity,
8
+ * account-profile country, and public IP/city. The compliance-check call
9
+ * fails closed (gateStatus = 'error') — unlike the profile/IP/geolocation
10
+ * lookups that merely source its request body and fail open.
9
11
  */
10
12
  export function useCheckoutGate(options = {}) {
11
- var _a, _b, _c;
12
- const { edxSessionUrl, learnerDashboardUrl, } = options;
13
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
14
+ const { edxSessionUrl, geolocationUrl, complianceCheckUrl, learnerDashboardUrl, } = options;
13
15
  const sessionQuery = useViaxSessionQuery(edxSessionUrl !== null && edxSessionUrl !== void 0 ? edxSessionUrl : null);
14
16
  const sessionReady = !!sessionQuery.data;
15
- const userCountryQuery = useUserCountryQuery(sessionReady && GATE_CHECK_COUNTRY ? (edxSessionUrl !== null && edxSessionUrl !== void 0 ? edxSessionUrl : null) : null, (_b = (_a = sessionQuery.data) === null || _a === void 0 ? void 0 : _a.username) !== null && _b !== void 0 ? _b : null);
16
- const userCountry = (_c = userCountryQuery.data) !== null && _c !== void 0 ? _c : null;
17
- // Use isLoading (isPending && isFetching), not isPending alone.
18
- // In TanStack Query v5, disabled queries with no data stay isPending forever —
19
- // e.g. country check when username is missing from the session response.
17
+ const profileQuery = useUserProfileQuery(sessionReady ? (edxSessionUrl !== null && edxSessionUrl !== void 0 ? edxSessionUrl : null) : null, (_b = (_a = sessionQuery.data) === null || _a === void 0 ? void 0 : _a.username) !== null && _b !== void 0 ? _b : null);
18
+ // fetchUserProfile fails open internally (never throws) — "settled" means
19
+ // isLoading is false, regardless of whether data came back null.
20
+ const profileSettled = !profileQuery.isLoading;
21
+ const ipQuery = usePublicIpQuery(sessionReady);
22
+ const geoQuery = useIpGeolocationQuery(sessionReady ? (geolocationUrl !== null && geolocationUrl !== void 0 ? geolocationUrl : null) : null, (_c = ipQuery.data) !== null && _c !== void 0 ? _c : null);
23
+ // IP/geo lookups are best-effort inputs to the request body, not gating
24
+ // signals themselves — treat any terminal state (success OR error) as
25
+ // settled so a flaky ipify/geolocation call never blocks the
26
+ // compliance-check call.
27
+ const ipGeoSettled = (!sessionReady)
28
+ || ipQuery.isError
29
+ || (!!ipQuery.data && !geoQuery.isLoading);
30
+ // Both course_uid + course_run_key (course) or program_uid (program) are
31
+ // valid product selectors in this app — use the same selector the rest of
32
+ // checkout uses (useAlacarteCheckout) to know *which* case we're in. But
33
+ // for the actual course_run_key value, don't use productSelector.runKey —
34
+ // nuqs decodes literal `+` as a space (standards-compliant, but course run
35
+ // keys like "course-v1:edX+DemoX+Demo_Course" rely on literal `+`), which
36
+ // corrupts the id sent to compliance-check. getCourseRunKeyFromUrl() reads
37
+ // the raw query string and preserves `+` correctly (same util useCheckout
38
+ // uses for the real course lookup).
39
+ const productSelector = useProductSelector();
40
+ const rawCourseRunKey = getCourseRunKeyFromUrl();
41
+ const courseIds = productSelector.kind === 'course'
42
+ ? [rawCourseRunKey !== null && rawCourseRunKey !== void 0 ? rawCourseRunKey : productSelector.runKey]
43
+ : productSelector.kind === 'program'
44
+ ? [productSelector.programUid]
45
+ : [];
46
+ const complianceCheckReady = sessionReady && profileSettled && ipGeoSettled;
47
+ const complianceCheckQuery = useComplianceCheckQuery(complianceCheckUrl !== null && complianceCheckUrl !== void 0 ? complianceCheckUrl : null, {
48
+ lmsUserId: (_e = (_d = sessionQuery.data) === null || _d === void 0 ? void 0 : _d.edxUserId) !== null && _e !== void 0 ? _e : null,
49
+ fullName: (_g = (_f = profileQuery.data) === null || _f === void 0 ? void 0 : _f.name) !== null && _g !== void 0 ? _g : null,
50
+ country: (_j = (_h = profileQuery.data) === null || _h === void 0 ? void 0 : _h.country) !== null && _j !== void 0 ? _j : null,
51
+ city: (_l = (_k = geoQuery.data) === null || _k === void 0 ? void 0 : _k.city) !== null && _l !== void 0 ? _l : null,
52
+ username: (_o = (_m = sessionQuery.data) === null || _m === void 0 ? void 0 : _m.username) !== null && _o !== void 0 ? _o : null,
53
+ ipAddress: (_p = ipQuery.data) !== null && _p !== void 0 ? _p : null,
54
+ courseIds,
55
+ }, complianceCheckReady);
56
+ const userCountry = (_r = (_q = profileQuery.data) === null || _q === void 0 ? void 0 : _q.country) !== null && _r !== void 0 ? _r : null;
20
57
  const loading = (!sessionReady && !sessionQuery.isError)
21
- || (GATE_CHECK_COUNTRY && userCountryQuery.isLoading);
22
- // GATE_CHECK_COUNTRY=false → treat all countries as allowed.
23
- const isCountryAllowed = !GATE_CHECK_COUNTRY
24
- || userCountry === null
25
- || ALLOWED_CHECKOUT_COUNTRIES.includes(userCountry);
58
+ || (sessionReady && !complianceCheckReady)
59
+ || (complianceCheckReady && complianceCheckQuery.isLoading);
26
60
  let gateStatus;
27
- if (sessionQuery.isError) {
61
+ if (sessionQuery.isError || (complianceCheckReady && complianceCheckQuery.isError)) {
28
62
  gateStatus = 'error';
29
63
  }
30
64
  else if (loading) {
31
65
  gateStatus = 'loading';
32
66
  }
33
- else if (!isCountryAllowed) {
67
+ else if ((_s = complianceCheckQuery.data) === null || _s === void 0 ? void 0 : _s.isBlocked) {
34
68
  gateStatus = 'blocked';
35
69
  }
36
70
  else {
37
71
  gateStatus = 'allowed';
38
72
  }
39
- const gateMessage = gateStatus === 'error'
40
- ? (sessionQuery.error instanceof Error
41
- ? sessionQuery.error.message
42
- : 'Failed to establish checkout session.')
43
- : null;
73
+ let gateMessage = null;
74
+ if (gateStatus === 'error') {
75
+ if (sessionQuery.error instanceof Error) {
76
+ gateMessage = sessionQuery.error.message;
77
+ }
78
+ else if (complianceCheckQuery.error instanceof Error) {
79
+ gateMessage = complianceCheckQuery.error.message;
80
+ }
81
+ else {
82
+ gateMessage = 'Failed to establish checkout session.';
83
+ }
84
+ }
44
85
  return {
45
86
  gateStatus,
46
87
  userCountry,
@@ -1 +1 @@
1
- {"version":3,"file":"useCheckoutGate.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutGate.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,sBAAsB,CAAC;AAqB9B;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,UAAkC,EAAE;;IAClE,MAAM,EACJ,aAAa,EACb,mBAAmB,GACpB,GAAG,OAAO,CAAC;IAEZ,MAAM,YAAY,GAAG,mBAAmB,CAAC,aAAa,aAAb,aAAa,cAAb,aAAa,GAAI,IAAI,CAAC,CAAC;IAChE,MAAM,YAAY,GAAG,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC;IAEzC,MAAM,gBAAgB,GAAG,mBAAmB,CAC1C,YAAY,IAAI,kBAAkB,CAAC,CAAC,CAAC,CAAC,aAAa,aAAb,aAAa,cAAb,aAAa,GAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EACnE,MAAA,MAAA,YAAY,CAAC,IAAI,0CAAE,QAAQ,mCAAI,IAAI,CACpC,CAAC;IAEF,MAAM,WAAW,GAAG,MAAA,gBAAgB,CAAC,IAAI,mCAAI,IAAI,CAAC;IAElD,gEAAgE;IAChE,+EAA+E;IAC/E,yEAAyE;IACzE,MAAM,OAAO,GAAG,CAAC,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC;WACnD,CAAC,kBAAkB,IAAI,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAExD,6DAA6D;IAC7D,MAAM,gBAAgB,GAAG,CAAC,kBAAkB;WACvC,WAAW,KAAK,IAAI;WACpB,0BAA0B,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAEtD,IAAI,UAAsB,CAAC;IAC3B,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC;QACzB,UAAU,GAAG,OAAO,CAAC;IACvB,CAAC;SAAM,IAAI,OAAO,EAAE,CAAC;QACnB,UAAU,GAAG,SAAS,CAAC;IACzB,CAAC;SAAM,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAC7B,UAAU,GAAG,SAAS,CAAC;IACzB,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,SAAS,CAAC;IACzB,CAAC;IAED,MAAM,WAAW,GAAG,UAAU,KAAK,OAAO;QACxC,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,YAAY,KAAK;YAClC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO;YAC5B,CAAC,CAAC,uCAAuC,CAAC;QAC9C,CAAC,CAAC,IAAI,CAAC;IAET,OAAO;QACL,UAAU;QACV,WAAW;QACX,mBAAmB;QACnB,WAAW;KACZ,CAAC;AACJ,CAAC","sourcesContent":["import {\n ALLOWED_CHECKOUT_COUNTRIES,\n GATE_CHECK_COUNTRY,\n} from '../../constants';\nimport {\n useViaxSessionQuery,\n useUserCountryQuery,\n} from './useCheckoutQueries';\n\nexport interface UseCheckoutGateOptions {\n /** Kept for call-site compatibility; no longer used (subscriber check removed). */\n apiUrl?: string,\n edxSessionUrl?: string,\n learnerDashboardUrl?: string,\n}\n\nexport type GateStatus = 'loading' | 'blocked' | 'allowed' | 'error';\n\nexport interface UseCheckoutGateResult {\n /** 'loading' while session/checks are in flight, 'blocked' if country not allowed, 'allowed' if clear, 'error' if session failed. */\n gateStatus: GateStatus,\n /** ISO 3166-1 alpha-2 country code — passed down to Checkout for the UK/CA checkbox. */\n userCountry: string | null,\n learnerDashboardUrl: string | undefined,\n /** Message shown when gateStatus is not 'allowed'. */\n gateMessage: string | null,\n}\n\n/**\n * Session + optional country gate for à la carte checkout.\n *\n * An edX Plus subscription does not give ownership of a course/program\n * (see alacarte-owned-products-api.md), so this gate no longer calls\n * tIsCurrentUserSubscriber or blocks existing subscribers.\n */\nexport function useCheckoutGate(options: UseCheckoutGateOptions = {}): UseCheckoutGateResult {\n const {\n edxSessionUrl,\n learnerDashboardUrl,\n } = options;\n\n const sessionQuery = useViaxSessionQuery(edxSessionUrl ?? null);\n const sessionReady = !!sessionQuery.data;\n\n const userCountryQuery = useUserCountryQuery(\n sessionReady && GATE_CHECK_COUNTRY ? (edxSessionUrl ?? null) : null,\n sessionQuery.data?.username ?? null,\n );\n\n const userCountry = userCountryQuery.data ?? null;\n\n // Use isLoading (isPending && isFetching), not isPending alone.\n // In TanStack Query v5, disabled queries with no data stay isPending forever —\n // e.g. country check when username is missing from the session response.\n const loading = (!sessionReady && !sessionQuery.isError)\n || (GATE_CHECK_COUNTRY && userCountryQuery.isLoading);\n\n // GATE_CHECK_COUNTRY=false → treat all countries as allowed.\n const isCountryAllowed = !GATE_CHECK_COUNTRY\n || userCountry === null\n || ALLOWED_CHECKOUT_COUNTRIES.includes(userCountry);\n\n let gateStatus: GateStatus;\n if (sessionQuery.isError) {\n gateStatus = 'error';\n } else if (loading) {\n gateStatus = 'loading';\n } else if (!isCountryAllowed) {\n gateStatus = 'blocked';\n } else {\n gateStatus = 'allowed';\n }\n\n const gateMessage = gateStatus === 'error'\n ? (sessionQuery.error instanceof Error\n ? sessionQuery.error.message\n : 'Failed to establish checkout session.')\n : null;\n\n return {\n gateStatus,\n userCountry,\n learnerDashboardUrl,\n gateMessage,\n };\n}\n"]}
1
+ {"version":3,"file":"useCheckoutGate.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutGate.ts"],"names":[],"mappings":"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","sourcesContent":["import { useProductSelector } from './useAlacarteCheckout';\nimport { getCourseRunKeyFromUrl } from '../../utils/url';\nimport {\n useViaxSessionQuery,\n useUserProfileQuery,\n usePublicIpQuery,\n useIpGeolocationQuery,\n useComplianceCheckQuery,\n} from './useCheckoutQueries';\n\nexport interface UseCheckoutGateOptions {\n /** Kept for call-site compatibility; no longer used (subscriber check removed). */\n apiUrl?: string,\n edxSessionUrl?: string,\n /** Endpoint used to resolve city/etc. for the learner's public IP. */\n geolocationUrl?: string,\n /** ALC-307 compliance-check endpoint — decides whether the learner is embargoed. */\n complianceCheckUrl?: string,\n learnerDashboardUrl?: string,\n}\n\nexport type GateStatus = 'loading' | 'blocked' | 'allowed' | 'error';\n\nexport interface UseCheckoutGateResult {\n /** 'loading' while checks are in flight, 'blocked' if the compliance check rejects the learner, 'allowed' if clear, 'error' if session or compliance check failed. */\n gateStatus: GateStatus,\n /** ISO 3166-1 alpha-2 country code — passed down to Checkout for the UK/CA checkbox. */\n userCountry: string | null,\n learnerDashboardUrl: string | undefined,\n /** Message shown when gateStatus is not 'allowed'. */\n gateMessage: string | null,\n}\n\n/**\n * Session + embargo compliance gate for à la carte checkout.\n *\n * Calls the ALC-307 compliance-check endpoint with the learner's identity,\n * account-profile country, and public IP/city. The compliance-check call\n * fails closed (gateStatus = 'error') — unlike the profile/IP/geolocation\n * lookups that merely source its request body and fail open.\n */\nexport function useCheckoutGate(options: UseCheckoutGateOptions = {}): UseCheckoutGateResult {\n const {\n edxSessionUrl,\n geolocationUrl,\n complianceCheckUrl,\n learnerDashboardUrl,\n } = options;\n\n const sessionQuery = useViaxSessionQuery(edxSessionUrl ?? null);\n const sessionReady = !!sessionQuery.data;\n\n const profileQuery = useUserProfileQuery(\n sessionReady ? (edxSessionUrl ?? null) : null,\n sessionQuery.data?.username ?? null,\n );\n // fetchUserProfile fails open internally (never throws) — \"settled\" means\n // isLoading is false, regardless of whether data came back null.\n const profileSettled = !profileQuery.isLoading;\n\n const ipQuery = usePublicIpQuery(sessionReady);\n const geoQuery = useIpGeolocationQuery(\n sessionReady ? (geolocationUrl ?? null) : null,\n ipQuery.data ?? null,\n );\n // IP/geo lookups are best-effort inputs to the request body, not gating\n // signals themselves — treat any terminal state (success OR error) as\n // settled so a flaky ipify/geolocation call never blocks the\n // compliance-check call.\n const ipGeoSettled = (!sessionReady)\n || ipQuery.isError\n || (!!ipQuery.data && !geoQuery.isLoading);\n\n // Both course_uid + course_run_key (course) or program_uid (program) are\n // valid product selectors in this app — use the same selector the rest of\n // checkout uses (useAlacarteCheckout) to know *which* case we're in. But\n // for the actual course_run_key value, don't use productSelector.runKey —\n // nuqs decodes literal `+` as a space (standards-compliant, but course run\n // keys like \"course-v1:edX+DemoX+Demo_Course\" rely on literal `+`), which\n // corrupts the id sent to compliance-check. getCourseRunKeyFromUrl() reads\n // the raw query string and preserves `+` correctly (same util useCheckout\n // uses for the real course lookup).\n const productSelector = useProductSelector();\n const rawCourseRunKey = getCourseRunKeyFromUrl();\n const courseIds = productSelector.kind === 'course'\n ? [rawCourseRunKey ?? productSelector.runKey]\n : productSelector.kind === 'program'\n ? [productSelector.programUid]\n : [];\n\n const complianceCheckReady = sessionReady && profileSettled && ipGeoSettled;\n\n const complianceCheckQuery = useComplianceCheckQuery(\n complianceCheckUrl ?? null,\n {\n lmsUserId: sessionQuery.data?.edxUserId ?? null,\n fullName: profileQuery.data?.name ?? null,\n country: profileQuery.data?.country ?? null,\n city: geoQuery.data?.city ?? null,\n username: sessionQuery.data?.username ?? null,\n ipAddress: ipQuery.data ?? null,\n courseIds,\n },\n complianceCheckReady,\n );\n\n const userCountry = profileQuery.data?.country ?? null;\n\n const loading = (!sessionReady && !sessionQuery.isError)\n || (sessionReady && !complianceCheckReady)\n || (complianceCheckReady && complianceCheckQuery.isLoading);\n\n let gateStatus: GateStatus;\n if (sessionQuery.isError || (complianceCheckReady && complianceCheckQuery.isError)) {\n gateStatus = 'error';\n } else if (loading) {\n gateStatus = 'loading';\n } else if (complianceCheckQuery.data?.isBlocked) {\n gateStatus = 'blocked';\n } else {\n gateStatus = 'allowed';\n }\n\n let gateMessage: string | null = null;\n if (gateStatus === 'error') {\n if (sessionQuery.error instanceof Error) {\n gateMessage = sessionQuery.error.message;\n } else if (complianceCheckQuery.error instanceof Error) {\n gateMessage = complianceCheckQuery.error.message;\n } else {\n gateMessage = 'Failed to establish checkout session.';\n }\n }\n\n return {\n gateStatus,\n userCountry,\n learnerDashboardUrl,\n gateMessage,\n };\n}\n"]}
@@ -4,11 +4,30 @@ export declare function useViaxSessionQuery(sessionUrl: string | null): import("
4
4
  /** Checks whether the authenticated learner already has an active subscription. */
5
5
  export declare function useCurrentUserSubscriberQuery(client: CheckoutClient | null, sessionReady: boolean): import("@tanstack/react-query").UseQueryResult<import("../../types").CurrentUserSubscriberResult | null, Error>;
6
6
  /**
7
- * Fetches the user's country from their edX account profile.
7
+ * Fetches the user's country and full name from their edX account profile.
8
8
  * Derives the LMS base URL from the session URL (same origin).
9
- * Returns null if the fetch fails or the field is absent — checkout stays open in that case.
9
+ * Returns nulls if the fetch fails or a field is absent — checkout stays open in that case.
10
10
  */
11
- export declare function useUserCountryQuery(sessionUrl: string | null, username: string | null): import("@tanstack/react-query").UseQueryResult<string | null, Error>;
11
+ export declare function useUserProfileQuery(sessionUrl: string | null, username: string | null): import("@tanstack/react-query").UseQueryResult<import("../../utils/provision").UserProfileResult, Error>;
12
+ /** Fetches the learner's current public IP once the viax session is ready. */
13
+ export declare function usePublicIpQuery(sessionReady: boolean): import("@tanstack/react-query").UseQueryResult<string, Error>;
14
+ /** Resolves city/etc. for the public IP via the LMS geolocation endpoint. */
15
+ export declare function useIpGeolocationQuery(geolocationUrl: string | null, ipAddress: string | null): import("@tanstack/react-query").UseQueryResult<import("../../utils/provision").GeolocationResult, Error>;
16
+ export interface ComplianceCheckInputs {
17
+ lmsUserId: string | null;
18
+ fullName: string | null;
19
+ country: string | null;
20
+ city: string | null;
21
+ username: string | null;
22
+ ipAddress: string | null;
23
+ courseIds: string[];
24
+ }
25
+ /**
26
+ * Calls the ALC-307 compliance-check endpoint once identity, country, and
27
+ * IP/city are resolved. Fails closed: a thrown error here should surface as
28
+ * gateStatus = 'error' via isError (see useCheckoutGate), never 'allowed'.
29
+ */
30
+ export declare function useComplianceCheckQuery(complianceCheckUrl: string | null, inputs: ComplianceCheckInputs, ready: boolean): import("@tanstack/react-query").UseQueryResult<import("../../utils/provision").ComplianceCheckResult, Error>;
12
31
  /** Polls the cart state machine every second; stops on terminal states. */
13
32
  export declare function useCartStatusQuery(client: CheckoutClient | null, cartBiId: string | null): import("@tanstack/react-query").UseQueryResult<CartStatusResult, Error>;
14
33
  /** Looks up the created subscription biId after success. */
@@ -7,7 +7,7 @@
7
7
  import { useQuery } from '@tanstack/react-query';
8
8
  import { isTerminalStatus } from '../../constants';
9
9
  import { checkoutKeys } from './keys';
10
- import { fetchEdxSession, fetchUserCountry } from '../../utils/provision';
10
+ import { fetchEdxSession, fetchUserProfile, fetchPublicIp, fetchIpGeolocation, fetchComplianceCheck, } from '../../utils/provision';
11
11
  /** Exchange edX session cookies for a viax token on page load. */
12
12
  export function useViaxSessionQuery(sessionUrl) {
13
13
  return useQuery({
@@ -29,17 +29,63 @@ export function useCurrentUserSubscriberQuery(client, sessionReady) {
29
29
  });
30
30
  }
31
31
  /**
32
- * Fetches the user's country from their edX account profile.
32
+ * Fetches the user's country and full name from their edX account profile.
33
33
  * Derives the LMS base URL from the session URL (same origin).
34
- * Returns null if the fetch fails or the field is absent — checkout stays open in that case.
34
+ * Returns nulls if the fetch fails or a field is absent — checkout stays open in that case.
35
35
  */
36
- export function useUserCountryQuery(sessionUrl, username) {
36
+ export function useUserProfileQuery(sessionUrl, username) {
37
37
  return useQuery({
38
- queryKey: ['checkout', 'userCountry', username],
38
+ queryKey: ['checkout', 'userProfile', username],
39
39
  enabled: !!sessionUrl && !!username,
40
40
  queryFn: () => {
41
41
  const lmsBaseUrl = new URL(sessionUrl).origin;
42
- return fetchUserCountry(lmsBaseUrl, username);
42
+ return fetchUserProfile(lmsBaseUrl, username);
43
+ },
44
+ staleTime: Infinity,
45
+ retry: 1,
46
+ });
47
+ }
48
+ /** Fetches the learner's current public IP once the viax session is ready. */
49
+ export function usePublicIpQuery(sessionReady) {
50
+ return useQuery({
51
+ queryKey: ['checkout', 'publicIp'],
52
+ enabled: sessionReady,
53
+ queryFn: () => fetchPublicIp(),
54
+ staleTime: Infinity,
55
+ retry: 1,
56
+ });
57
+ }
58
+ /** Resolves city/etc. for the public IP via the LMS geolocation endpoint. */
59
+ export function useIpGeolocationQuery(geolocationUrl, ipAddress) {
60
+ return useQuery({
61
+ queryKey: ['checkout', 'ipGeolocation', ipAddress !== null && ipAddress !== void 0 ? ipAddress : 'none'],
62
+ enabled: !!geolocationUrl && !!ipAddress,
63
+ queryFn: () => fetchIpGeolocation(geolocationUrl, ipAddress),
64
+ staleTime: Infinity,
65
+ retry: 1,
66
+ });
67
+ }
68
+ /**
69
+ * Calls the ALC-307 compliance-check endpoint once identity, country, and
70
+ * IP/city are resolved. Fails closed: a thrown error here should surface as
71
+ * gateStatus = 'error' via isError (see useCheckoutGate), never 'allowed'.
72
+ */
73
+ export function useComplianceCheckQuery(complianceCheckUrl, inputs, ready) {
74
+ const { lmsUserId, fullName, country, city, username, ipAddress, courseIds, } = inputs;
75
+ return useQuery({
76
+ queryKey: ['checkout', 'complianceCheck', username !== null && username !== void 0 ? username : 'none', ipAddress !== null && ipAddress !== void 0 ? ipAddress : 'none'],
77
+ enabled: !!complianceCheckUrl && ready && !!lmsUserId && !!username,
78
+ queryFn: () => {
79
+ const requestBody = {
80
+ lms_user_id: lmsUserId,
81
+ full_name: fullName !== null && fullName !== void 0 ? fullName : '',
82
+ country: country !== null && country !== void 0 ? country : '',
83
+ city: city !== null && city !== void 0 ? city : '',
84
+ user: username,
85
+ ip_address: ipAddress !== null && ipAddress !== void 0 ? ipAddress : '',
86
+ course_ids: courseIds,
87
+ };
88
+ return fetchComplianceCheck(complianceCheckUrl, requestBody);
43
89
  },
44
90
  staleTime: Infinity,
45
91
  retry: 1,
@@ -1 +1 @@
1
- {"version":3,"file":"useCheckoutQueries.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutQueries.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAK1E,kEAAkE;AAClE,MAAM,UAAU,mBAAmB,CAAC,UAAyB;IAC3D,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,YAAY,CAAC,WAAW,EAAE;QACpC,OAAO,EAAE,CAAC,CAAC,UAAU;QACrB,OAAO,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,UAAW,CAAC;QAC3C,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI;QACxB,KAAK,EAAE,CAAC;KACT,CAAC,CAAC;AACL,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,6BAA6B,CAAC,MAA6B,EAAE,YAAqB;IAChG,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,CAAC,UAAU,EAAE,uBAAuB,CAAC;QAC/C,OAAO,EAAE,CAAC,CAAC,MAAM,IAAI,YAAY;QACjC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAO,CAAC,uBAAuB,EAAE;QAChD,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI;QACxB,KAAK,EAAE,CAAC;KACT,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAAyB,EAAE,QAAuB;IACpF,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,CAAC,UAAU,EAAE,aAAa,EAAE,QAAQ,CAAC;QAC/C,OAAO,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,QAAQ;QACnC,OAAO,EAAE,GAAG,EAAE;YACZ,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,UAAW,CAAC,CAAC,MAAM,CAAC;YAC/C,OAAO,gBAAgB,CAAC,UAAU,EAAE,QAAS,CAAC,CAAC;QACjD,CAAC;QACD,SAAS,EAAE,QAAQ;QACnB,KAAK,EAAE,CAAC;KACT,CAAC,CAAC;AACL,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,kBAAkB,CAAC,MAA6B,EAAE,QAAuB;IACvF,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAC,QAAQ,aAAR,QAAQ,cAAR,QAAQ,GAAI,MAAM,CAAC;QACjD,OAAO,EAAE,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,QAAQ;QAC/B,OAAO,EAAE,GAA8B,EAAE,CAAC,MAAO,CAAC,aAAa,CAAC,QAAS,CAAC;QAC1E,eAAe,EAAE,CAAC,KAAK,EAAE,EAAE,eACzB,OAAA,gBAAgB,CAAC,MAAA,MAAA,KAAK,CAAC,KAAK,CAAC,IAAI,0CAAE,MAAM,mCAAI,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA,EAAA;QACnE,oBAAoB,EAAE,KAAK;QAC3B,MAAM,EAAE,CAAC;KACV,CAAC,CAAC;AACL,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,sBAAsB,CACpC,MAA6B,EAC7B,QAAuB,EACvB,OAAgB;IAEhB,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,YAAY,CAAC,cAAc,CAAC,QAAQ,aAAR,QAAQ,cAAR,QAAQ,GAAI,MAAM,CAAC;QACzD,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,QAAQ;QAC1C,OAAO,EAAE,GAAG,EAAE,CAAC,MAAO,CAAC,oBAAoB,CAAC,QAAS,CAAC;QACtD,SAAS,EAAE,QAAQ;KACpB,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,MAA6B,EAC7B,YAAqB,EACrB,YAA2B;IAE3B,OAAO,QAAQ,CAAC;QACd,QAAQ,EAAE,YAAY,CAAC,YAAY,CAAC,YAAY,aAAZ,YAAY,cAAZ,YAAY,GAAI,MAAM,CAAC;QAC3D,OAAO,EAAE,CAAC,CAAC,MAAM,IAAI,YAAY,IAAI,CAAC,CAAC,YAAY;QACnD,OAAO,EAAE,GAAiC,EAAE,CAAC,MAAO,CAAC,eAAe,CAAC,YAAa,CAAC;QACnF,SAAS,EAAE,QAAQ;QACnB,KAAK,EAAE,CAAC;KACT,CAAC,CAAC;AACL,CAAC","sourcesContent":["/**\n * query/useCheckoutQueries.ts — read-side TanStack Query hooks.\n *\n * useViaxSessionQuery runs on mount to exchange the edX session for a viax\n * token.\n */\nimport { useQuery } from '@tanstack/react-query';\nimport { isTerminalStatus } from '../../constants';\nimport { checkoutKeys } from './keys';\nimport { fetchEdxSession, fetchUserCountry } from '../../utils/provision';\nimport type {\n CheckoutClient, CartStatusResult, CourseDetail,\n} from '../../types';\n\n/** Exchange edX session cookies for a viax token on page load. */\nexport function useViaxSessionQuery(sessionUrl: string | null) {\n return useQuery({\n queryKey: checkoutKeys.viaxSession(),\n enabled: !!sessionUrl,\n queryFn: () => fetchEdxSession(sessionUrl!),\n staleTime: 5 * 60 * 1000,\n retry: 1,\n });\n}\n\n/** Checks whether the authenticated learner already has an active subscription. */\nexport function useCurrentUserSubscriberQuery(client: CheckoutClient | null, sessionReady: boolean) {\n return useQuery({\n queryKey: ['checkout', 'currentUserSubscriber'],\n enabled: !!client && sessionReady,\n queryFn: () => client!.isCurrentUserSubscriber(),\n staleTime: 5 * 60 * 1000,\n retry: 1,\n });\n}\n\n/**\n * Fetches the user's country from their edX account profile.\n * Derives the LMS base URL from the session URL (same origin).\n * Returns null if the fetch fails or the field is absent — checkout stays open in that case.\n */\nexport function useUserCountryQuery(sessionUrl: string | null, username: string | null) {\n return useQuery({\n queryKey: ['checkout', 'userCountry', username],\n enabled: !!sessionUrl && !!username,\n queryFn: () => {\n const lmsBaseUrl = new URL(sessionUrl!).origin;\n return fetchUserCountry(lmsBaseUrl, username!);\n },\n staleTime: Infinity,\n retry: 1,\n });\n}\n\n/** Polls the cart state machine every second; stops on terminal states. */\nexport function useCartStatusQuery(client: CheckoutClient | null, cartBiId: string | null) {\n return useQuery({\n queryKey: checkoutKeys.status(cartBiId ?? 'none'),\n enabled: !!client && !!cartBiId,\n queryFn: (): Promise<CartStatusResult> => client!.getCartStatus(cartBiId!),\n refetchInterval: (query) =>\n isTerminalStatus(query.state.data?.status ?? null) ? false : 1000,\n refetchOnWindowFocus: false,\n gcTime: 0,\n });\n}\n\n/** Looks up the created subscription biId after success. */\nexport function useSubscriptionIdQuery(\n client: CheckoutClient | null,\n cartBiId: string | null,\n enabled: boolean,\n) {\n return useQuery({\n queryKey: checkoutKeys.subscriptionId(cartBiId ?? 'none'),\n enabled: enabled && !!client && !!cartBiId,\n queryFn: () => client!.findSubscriptionBiId(cartBiId!),\n staleTime: Infinity,\n });\n}\n\n/**\n * Fetches course details when the user arrived via ?course_run_key=.\n * Skipped entirely when no course key is present (standard checkout).\n */\nexport function useCourseDetailQuery(\n client: CheckoutClient | null,\n sessionReady: boolean,\n courseRunKey: string | null,\n) {\n return useQuery({\n queryKey: checkoutKeys.courseDetail(courseRunKey ?? 'none'),\n enabled: !!client && sessionReady && !!courseRunKey,\n queryFn: (): Promise<CourseDetail | null> => client!.getCourseDetail(courseRunKey!),\n staleTime: Infinity,\n retry: 1,\n });\n}\n"]}
1
+ {"version":3,"file":"useCheckoutQueries.js","sourceRoot":"","sources":["../../../src/data/hooks/useCheckoutQueries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * query/useCheckoutQueries.ts — read-side TanStack Query hooks.\n *\n * useViaxSessionQuery runs on mount to exchange the edX session for a viax\n * token.\n */\nimport { useQuery } from '@tanstack/react-query';\nimport { isTerminalStatus } from '../../constants';\nimport { checkoutKeys } from './keys';\nimport {\n fetchEdxSession,\n fetchUserProfile,\n fetchPublicIp,\n fetchIpGeolocation,\n fetchComplianceCheck,\n} from '../../utils/provision';\nimport type { ComplianceCheckRequest } from '../../utils/provision';\nimport type {\n CheckoutClient, CartStatusResult, CourseDetail,\n} from '../../types';\n\n/** Exchange edX session cookies for a viax token on page load. */\nexport function useViaxSessionQuery(sessionUrl: string | null) {\n return useQuery({\n queryKey: checkoutKeys.viaxSession(),\n enabled: !!sessionUrl,\n queryFn: () => fetchEdxSession(sessionUrl!),\n staleTime: 5 * 60 * 1000,\n retry: 1,\n });\n}\n\n/** Checks whether the authenticated learner already has an active subscription. */\nexport function useCurrentUserSubscriberQuery(client: CheckoutClient | null, sessionReady: boolean) {\n return useQuery({\n queryKey: ['checkout', 'currentUserSubscriber'],\n enabled: !!client && sessionReady,\n queryFn: () => client!.isCurrentUserSubscriber(),\n staleTime: 5 * 60 * 1000,\n retry: 1,\n });\n}\n\n/**\n * Fetches the user's country and full name from their edX account profile.\n * Derives the LMS base URL from the session URL (same origin).\n * Returns nulls if the fetch fails or a field is absent — checkout stays open in that case.\n */\nexport function useUserProfileQuery(sessionUrl: string | null, username: string | null) {\n return useQuery({\n queryKey: ['checkout', 'userProfile', username],\n enabled: !!sessionUrl && !!username,\n queryFn: () => {\n const lmsBaseUrl = new URL(sessionUrl!).origin;\n return fetchUserProfile(lmsBaseUrl, username!);\n },\n staleTime: Infinity,\n retry: 1,\n });\n}\n\n/** Fetches the learner's current public IP once the viax session is ready. */\nexport function usePublicIpQuery(sessionReady: boolean) {\n return useQuery({\n queryKey: ['checkout', 'publicIp'],\n enabled: sessionReady,\n queryFn: () => fetchPublicIp(),\n staleTime: Infinity,\n retry: 1,\n });\n}\n\n/** Resolves city/etc. for the public IP via the LMS geolocation endpoint. */\nexport function useIpGeolocationQuery(geolocationUrl: string | null, ipAddress: string | null) {\n return useQuery({\n queryKey: ['checkout', 'ipGeolocation', ipAddress ?? 'none'],\n enabled: !!geolocationUrl && !!ipAddress,\n queryFn: () => fetchIpGeolocation(geolocationUrl!, ipAddress!),\n staleTime: Infinity,\n retry: 1,\n });\n}\n\nexport interface ComplianceCheckInputs {\n lmsUserId: string | null,\n fullName: string | null,\n country: string | null,\n city: string | null,\n username: string | null,\n ipAddress: string | null,\n courseIds: string[],\n}\n\n/**\n * Calls the ALC-307 compliance-check endpoint once identity, country, and\n * IP/city are resolved. Fails closed: a thrown error here should surface as\n * gateStatus = 'error' via isError (see useCheckoutGate), never 'allowed'.\n */\nexport function useComplianceCheckQuery(\n complianceCheckUrl: string | null,\n inputs: ComplianceCheckInputs,\n ready: boolean,\n) {\n const {\n lmsUserId, fullName, country, city, username, ipAddress, courseIds,\n } = inputs;\n\n return useQuery({\n queryKey: ['checkout', 'complianceCheck', username ?? 'none', ipAddress ?? 'none'],\n enabled: !!complianceCheckUrl && ready && !!lmsUserId && !!username,\n queryFn: () => {\n const requestBody: ComplianceCheckRequest = {\n lms_user_id: lmsUserId!,\n full_name: fullName ?? '',\n country: country ?? '',\n city: city ?? '',\n user: username!,\n ip_address: ipAddress ?? '',\n course_ids: courseIds,\n };\n return fetchComplianceCheck(complianceCheckUrl!, requestBody);\n },\n staleTime: Infinity,\n retry: 1,\n });\n}\n\n/** Polls the cart state machine every second; stops on terminal states. */\nexport function useCartStatusQuery(client: CheckoutClient | null, cartBiId: string | null) {\n return useQuery({\n queryKey: checkoutKeys.status(cartBiId ?? 'none'),\n enabled: !!client && !!cartBiId,\n queryFn: (): Promise<CartStatusResult> => client!.getCartStatus(cartBiId!),\n refetchInterval: (query) =>\n isTerminalStatus(query.state.data?.status ?? null) ? false : 1000,\n refetchOnWindowFocus: false,\n gcTime: 0,\n });\n}\n\n/** Looks up the created subscription biId after success. */\nexport function useSubscriptionIdQuery(\n client: CheckoutClient | null,\n cartBiId: string | null,\n enabled: boolean,\n) {\n return useQuery({\n queryKey: checkoutKeys.subscriptionId(cartBiId ?? 'none'),\n enabled: enabled && !!client && !!cartBiId,\n queryFn: () => client!.findSubscriptionBiId(cartBiId!),\n staleTime: Infinity,\n });\n}\n\n/**\n * Fetches course details when the user arrived via ?course_run_key=.\n * Skipped entirely when no course key is present (standard checkout).\n */\nexport function useCourseDetailQuery(\n client: CheckoutClient | null,\n sessionReady: boolean,\n courseRunKey: string | null,\n) {\n return useQuery({\n queryKey: checkoutKeys.courseDetail(courseRunKey ?? 'none'),\n enabled: !!client && sessionReady && !!courseRunKey,\n queryFn: (): Promise<CourseDetail | null> => client!.getCourseDetail(courseRunKey!),\n staleTime: Infinity,\n retry: 1,\n });\n}\n"]}
@@ -35,14 +35,47 @@ export interface GeolocationResult {
35
35
  looked_up_at: string | null;
36
36
  fallback_reason: string | null;
37
37
  }
38
+ export interface UserProfileResult {
39
+ country: string | null;
40
+ name: string | null;
41
+ }
38
42
  /**
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.
43
+ * Fetch the ISO 3166-1 alpha-2 country code and full name from the user's
44
+ * edX account profile. Returns nulls for any field that fails or is absent —
45
+ * callers decide fail-open/closed.
41
46
  */
42
- export declare function fetchUserCountry(lmsBaseUrl: string, username: string): Promise<string | null>;
47
+ export declare function fetchUserProfile(lmsBaseUrl: string, username: string): Promise<UserProfileResult>;
43
48
  /** Fetch the learner's current public IP for geolocation lookup. */
44
49
  export declare function fetchPublicIp(): Promise<string>;
45
50
  /** Look up geolocation data for an explicit IP address. */
46
51
  export declare function fetchIpGeolocation(geolocationUrl: string, ipAddress: string): Promise<GeolocationResult>;
47
52
  /** Exchange the learner's edX session (cookies) for a viax token. */
48
53
  export declare function fetchEdxSession(sessionUrl: string): Promise<EdxSessionResult>;
54
+ export interface ComplianceCheckRequest {
55
+ lms_user_id: string;
56
+ full_name: string;
57
+ country: string;
58
+ city: string;
59
+ user: string;
60
+ ip_address: string;
61
+ course_ids: string[];
62
+ }
63
+ export interface ComplianceCheckResult {
64
+ isBlocked: boolean;
65
+ }
66
+ /**
67
+ * POST the learner's identity/location to the compliance-check endpoint.
68
+ * Throws on any non-ok response or network failure — callers must treat
69
+ * this as fail-closed (gateStatus = 'error'), unlike fetchUserProfile.
70
+ *
71
+ * Requires the edX JWT as an Authorization header (per ALC-307 backend) —
72
+ * rebuilt from the same two-part JWT cookie pair fetchIpGeolocation reads
73
+ * (header.payload + signature). Those cookies are scoped to the LMS's own
74
+ * domain (Domain=.courses.<env>.edx.org) — readable via document.cookie
75
+ * only when this app is served from that same domain or a subdomain of it.
76
+ * In local dev (a different origin entirely) this will always come back
77
+ * null; that's an unavoidable limitation of testing against a remote
78
+ * backend from localhost, not a bug — verify against an actual stage/prod
79
+ * deploy instead.
80
+ */
81
+ export declare function fetchComplianceCheck(complianceCheckUrl: string, requestBody: ComplianceCheckRequest): Promise<ComplianceCheckResult>;
@@ -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.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MTgxNTc5MCwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkxODEyMTkwLCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJOU18yNjA5X1dLMzEiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiI4MjE3ZWI5MTk0YTM1MGJiODVmYmUxMGUzNWFhYWNkZiIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1MzcyLCJlbWFpbCI6Im5zb3Jva2FAMnUuY29tIiwibmFtZSI6Ik5pY29sYXMgU29yb2thIiwiZmFtaWx5X25hbWUiOiIiLCJnaXZlbl9uYW1lIjoiIiwiYWRtaW5pc3RyYXRvciI6ZmFsc2UsInN1cGVydXNlciI6ZmFsc2V9',
25
- signature: 'B56DHXeCo_00XE1gyBX3BAMx-Fp2ObkfYWPLcN4-j_rpMVca_0GtgPGA32wtKliUVZkgl2uOBYeSb4UFc8YogUuj33IA5cExz5xEU9rsUSF4vrqwSKWeeUZarXPX9Nr45QDf4AoPyLzFgIWJuwqnZDR3IuOdpiZ8YkvBNBqq43XvoXK6ISuad6fG-fpRmLvxtebAlNytHfyJqmY_ySfzvL4vUdO0uW5au5gVRKwUu1wkfxA-Djt2MjEJkHXIS3CwukumJN7q867T9Ri_dMcmQRi78QmwazSo391CHgB7ovGQkbZ1BeClYRxbYJtHZwSiBIZqV8HVf1b7_wJAHVHmfA',
24
+ payload: 'eyJhbGciOiJSUzUxMiIsImtpZCI6Imxtc3N0YWdlMDAyIiwidHlwIjoiSldUIn0.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MTQ3MzY2NiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkxNDcwMDY2LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJOU18yNjA5X1dLMzEiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiI4MjE3ZWI5MTk0YTM1MGJiODVmYmUxMGUzNWFhYWNkZiIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1MzcyLCJlbWFpbCI6Im5zb3Jva2FAMnUuY29tIiwibmFtZSI6Ik5pY29sYXMgU29yb2thIiwiZmFtaWx5X25hbWUiOiIiLCJnaXZlbl9uYW1lIjoiIiwiYWRtaW5pc3RyYXRvciI6ZmFsc2UsInN1cGVydXNlciI6ZmFsc2V9',
25
+ signature: 'FcAsj-bBGmwzOzcue246r0ZMLAdnv1G2QTLRI_ZZoKVnxfPNUtjB2JdlJT-_CilT9T98Hh6Aac12kcd4twFHfOVUqXHrs8q2YLPFoZtKaaqf52ARvBubuzFFc6_lCjklSR0tz_tPwFrYk4TlWOCIeEhD2Y4qiEL8kdW9vWWqDRaFJTQ-nIOGx7MPaDOPcOfRAFdLzKxBGFRkMszQ_KYUHhjQiRgC_LwmMhlAo_nAPNu3YQCi4cEvx_3i14G7L0ZLTqUtVwcrOyZdOE6iQfzSKX2zEMPxadh3pqavkeQfoojtYUFp1ahQbIeU2-2IdQqBmanGEga1MCD3YKklpLm5Ew'
26
26
  };
27
27
  const EDX_JWT_PAYLOAD_COOKIE_NAMES = [
28
28
  'stage-edx-jwt-cookie-header-payload',
@@ -71,22 +71,26 @@ function parseSessionResponse(data) {
71
71
  };
72
72
  }
73
73
  /**
74
- * Fetch the ISO 3166-1 alpha-2 country code from the user's edX account profile.
75
- * Returns null if the request fails or the field is absent.
74
+ * Fetch the ISO 3166-1 alpha-2 country code and full name from the user's
75
+ * edX account profile. Returns nulls for any field that fails or is absent —
76
+ * callers decide fail-open/closed.
76
77
  */
77
- export function fetchUserCountry(lmsBaseUrl, username) {
78
+ export function fetchUserProfile(lmsBaseUrl, username) {
78
79
  return __awaiter(this, void 0, void 0, function* () {
79
80
  try {
80
81
  const res = yield fetch(`${lmsBaseUrl}/api/user/v1/accounts/${encodeURIComponent(username)}`, {
81
82
  credentials: 'include',
82
83
  });
83
84
  if (!res.ok)
84
- return null;
85
+ return { country: null, name: null };
85
86
  const data = yield res.json();
86
- return typeof data.country === 'string' && data.country ? data.country : null;
87
+ return {
88
+ country: typeof data.country === 'string' && data.country ? data.country : null,
89
+ name: typeof data.name === 'string' && data.name ? data.name : null,
90
+ };
87
91
  }
88
92
  catch (_a) {
89
- return null;
93
+ return { country: null, name: null };
90
94
  }
91
95
  });
92
96
  }
@@ -176,4 +180,58 @@ export function fetchEdxSession(sessionUrl) {
176
180
  return parseSessionResponse(data);
177
181
  });
178
182
  }
183
+ /**
184
+ * Parse the compliance-check response into a normalized shape.
185
+ * The exact ALC-307 response contract isn't finalized here — this narrow
186
+ * parser is the single spot to adjust if the real shape differs. Treats
187
+ * is_blocked === true, status === 'blocked', or allowed === false as a
188
+ * block; defaults to allowed otherwise.
189
+ */
190
+ function parseComplianceCheckResponse(data) {
191
+ if (typeof data.is_blocked === 'boolean')
192
+ return { isBlocked: data.is_blocked };
193
+ if (typeof data.status === 'string')
194
+ return { isBlocked: data.status.toLowerCase() === 'blocked' };
195
+ if (typeof data.allowed === 'boolean')
196
+ return { isBlocked: !data.allowed };
197
+ return { isBlocked: false };
198
+ }
199
+ /**
200
+ * POST the learner's identity/location to the compliance-check endpoint.
201
+ * Throws on any non-ok response or network failure — callers must treat
202
+ * this as fail-closed (gateStatus = 'error'), unlike fetchUserProfile.
203
+ *
204
+ * Requires the edX JWT as an Authorization header (per ALC-307 backend) —
205
+ * rebuilt from the same two-part JWT cookie pair fetchIpGeolocation reads
206
+ * (header.payload + signature). Those cookies are scoped to the LMS's own
207
+ * domain (Domain=.courses.<env>.edx.org) — readable via document.cookie
208
+ * only when this app is served from that same domain or a subdomain of it.
209
+ * In local dev (a different origin entirely) this will always come back
210
+ * null; that's an unavoidable limitation of testing against a remote
211
+ * backend from localhost, not a bug — verify against an actual stage/prod
212
+ * deploy instead.
213
+ */
214
+ export function fetchComplianceCheck(complianceCheckUrl, requestBody) {
215
+ return __awaiter(this, void 0, void 0, function* () {
216
+ const edxJwtBody = getEdxJwtCookieBody();
217
+ const headers = {
218
+ Accept: 'application/json',
219
+ 'Content-Type': 'application/json',
220
+ };
221
+ if (edxJwtBody) {
222
+ headers.Authorization = `JWT ${edxJwtBody.payload}.${edxJwtBody.signature}`;
223
+ }
224
+ const res = yield fetch(complianceCheckUrl, {
225
+ method: 'POST',
226
+ credentials: 'include',
227
+ headers,
228
+ body: JSON.stringify(requestBody),
229
+ });
230
+ if (!res.ok) {
231
+ throw new Error(`Compliance check failed (${res.status}).`);
232
+ }
233
+ const data = yield res.json();
234
+ return parseComplianceCheckResponse(data);
235
+ });
236
+ }
179
237
  //# sourceMappingURL=provision.js.map
@@ -1 +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, 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.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MTgxNTc5MCwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkxODEyMTkwLCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJOU18yNjA5X1dLMzEiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiI4MjE3ZWI5MTk0YTM1MGJiODVmYmUxMGUzNWFhYWNkZiIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1MzcyLCJlbWFpbCI6Im5zb3Jva2FAMnUuY29tIiwibmFtZSI6Ik5pY29sYXMgU29yb2thIiwiZmFtaWx5X25hbWUiOiIiLCJnaXZlbl9uYW1lIjoiIiwiYWRtaW5pc3RyYXRvciI6ZmFsc2UsInN1cGVydXNlciI6ZmFsc2V9',\n signature: 'B56DHXeCo_00XE1gyBX3BAMx-Fp2ObkfYWPLcN4-j_rpMVca_0GtgPGA32wtKliUVZkgl2uOBYeSb4UFc8YogUuj33IA5cExz5xEU9rsUSF4vrqwSKWeeUZarXPX9Nr45QDf4AoPyLzFgIWJuwqnZDR3IuOdpiZ8YkvBNBqq43XvoXK6ISuad6fG-fpRmLvxtebAlNytHfyJqmY_ySfzvL4vUdO0uW5au5gVRKwUu1wkfxA-Djt2MjEJkHXIS3CwukumJN7q867T9Ri_dMcmQRi78QmwazSo391CHgB7ovGQkbZ1BeClYRxbYJtHZwSiBIZqV8HVf1b7_wJAHVHmfA',\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,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;AAOD;;;;GAIG;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,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YAClD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA6B,CAAC;YACzD,OAAO;gBACL,OAAO,EAAE,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;gBAC/E,IAAI,EAAE,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;aACpE,CAAC;QACJ,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACvC,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;AAgBD;;;;;;GAMG;AACH,SAAS,4BAA4B,CAAC,IAA6B;IACjE,IAAI,OAAO,IAAI,CAAC,UAAU,KAAK,SAAS;QAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;IAChF,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,SAAS,EAAE,CAAC;IACnG,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;IAC3E,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AAC9B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAgB,oBAAoB,CACxC,kBAA0B,EAC1B,WAAmC;;QAEnC,MAAM,UAAU,GAAG,mBAAmB,EAAE,CAAC;QACzC,MAAM,OAAO,GAA2B;YACtC,MAAM,EAAE,kBAAkB;YAC1B,cAAc,EAAE,kBAAkB;SACnC,CAAC;QACF,IAAI,UAAU,EAAE,CAAC;YACf,OAAO,CAAC,aAAa,GAAG,OAAO,UAAU,CAAC,OAAO,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;QAC9E,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,kBAAkB,EAAE;YAC1C,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,SAAS;YACtB,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;SAClC,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,EAA6B,CAAC;QACzD,OAAO,4BAA4B,CAAC,IAAI,CAAC,CAAC;IAC5C,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.eyJhdWQiOiJJbmtvY3VqTGlreXVjc0Vkd2lXYXRkZWJyRWFja21ldkxha0R1aWZLb29zaGtha1dvdyIsImV4cCI6MTc5MTQ3MzY2NiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwiaWF0IjoxNzkxNDcwMDY2LCJpc3MiOiJodHRwczovL2NvdXJzZXMuc3RhZ2UuZWR4Lm9yZy9vYXV0aDIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJOU18yNjA5X1dLMzEiLCJzY29wZXMiOlsidXNlcl9pZCIsImVtYWlsIiwicHJvZmlsZSJdLCJ2ZXJzaW9uIjoiMS4yLjAiLCJzdWIiOiI4MjE3ZWI5MTk0YTM1MGJiODVmYmUxMGUzNWFhYWNkZiIsImZpbHRlcnMiOlsidXNlcjptZSJdLCJpc19yZXN0cmljdGVkIjpmYWxzZSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJ1c2VyX2lkIjo1Mjg1MzcyLCJlbWFpbCI6Im5zb3Jva2FAMnUuY29tIiwibmFtZSI6Ik5pY29sYXMgU29yb2thIiwiZmFtaWx5X25hbWUiOiIiLCJnaXZlbl9uYW1lIjoiIiwiYWRtaW5pc3RyYXRvciI6ZmFsc2UsInN1cGVydXNlciI6ZmFsc2V9',\n signature: 'FcAsj-bBGmwzOzcue246r0ZMLAdnv1G2QTLRI_ZZoKVnxfPNUtjB2JdlJT-_CilT9T98Hh6Aac12kcd4twFHfOVUqXHrs8q2YLPFoZtKaaqf52ARvBubuzFFc6_lCjklSR0tz_tPwFrYk4TlWOCIeEhD2Y4qiEL8kdW9vWWqDRaFJTQ-nIOGx7MPaDOPcOfRAFdLzKxBGFRkMszQ_KYUHhjQiRgC_LwmMhlAo_nAPNu3YQCi4cEvx_3i14G7L0ZLTqUtVwcrOyZdOE6iQfzSKX2zEMPxadh3pqavkeQfoojtYUFp1ahQbIeU2-2IdQqBmanGEga1MCD3YKklpLm5Ew'\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\nexport interface UserProfileResult {\n country: string | null,\n name: string | null,\n}\n\n/**\n * Fetch the ISO 3166-1 alpha-2 country code and full name from the user's\n * edX account profile. Returns nulls for any field that fails or is absent —\n * callers decide fail-open/closed.\n */\nexport async function fetchUserProfile(lmsBaseUrl: string, username: string): Promise<UserProfileResult> {\n try {\n const res = await fetch(`${lmsBaseUrl}/api/user/v1/accounts/${encodeURIComponent(username)}`, {\n credentials: 'include',\n });\n if (!res.ok) return { country: null, name: null };\n const data = await res.json() as Record<string, unknown>;\n return {\n country: typeof data.country === 'string' && data.country ? data.country : null,\n name: typeof data.name === 'string' && data.name ? data.name : null,\n };\n } catch {\n return { country: null, name: 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\nexport interface ComplianceCheckRequest {\n lms_user_id: string,\n full_name: string,\n country: string,\n city: string,\n user: string,\n ip_address: string,\n course_ids: string[],\n}\n\nexport interface ComplianceCheckResult {\n isBlocked: boolean,\n}\n\n/**\n * Parse the compliance-check response into a normalized shape.\n * The exact ALC-307 response contract isn't finalized here — this narrow\n * parser is the single spot to adjust if the real shape differs. Treats\n * is_blocked === true, status === 'blocked', or allowed === false as a\n * block; defaults to allowed otherwise.\n */\nfunction parseComplianceCheckResponse(data: Record<string, unknown>): ComplianceCheckResult {\n if (typeof data.is_blocked === 'boolean') return { isBlocked: data.is_blocked };\n if (typeof data.status === 'string') return { isBlocked: data.status.toLowerCase() === 'blocked' };\n if (typeof data.allowed === 'boolean') return { isBlocked: !data.allowed };\n return { isBlocked: false };\n}\n\n/**\n * POST the learner's identity/location to the compliance-check endpoint.\n * Throws on any non-ok response or network failure — callers must treat\n * this as fail-closed (gateStatus = 'error'), unlike fetchUserProfile.\n *\n * Requires the edX JWT as an Authorization header (per ALC-307 backend) —\n * rebuilt from the same two-part JWT cookie pair fetchIpGeolocation reads\n * (header.payload + signature). Those cookies are scoped to the LMS's own\n * domain (Domain=.courses.<env>.edx.org) — readable via document.cookie\n * only when this app is served from that same domain or a subdomain of it.\n * In local dev (a different origin entirely) this will always come back\n * null; that's an unavoidable limitation of testing against a remote\n * backend from localhost, not a bug — verify against an actual stage/prod\n * deploy instead.\n */\nexport async function fetchComplianceCheck(\n complianceCheckUrl: string,\n requestBody: ComplianceCheckRequest,\n): Promise<ComplianceCheckResult> {\n const edxJwtBody = getEdxJwtCookieBody();\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n };\n if (edxJwtBody) {\n headers.Authorization = `JWT ${edxJwtBody.payload}.${edxJwtBody.signature}`;\n }\n\n const res = await fetch(complianceCheckUrl, {\n method: 'POST',\n credentials: 'include',\n headers,\n body: JSON.stringify(requestBody),\n });\n\n if (!res.ok) {\n throw new Error(`Compliance check failed (${res.status}).`);\n }\n\n const data = await res.json() as Record<string, unknown>;\n return parseComplianceCheckResponse(data);\n}\n"]}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Read ?course_run_key= from the current URL.
3
+ * Avoid URLSearchParams — it treats bare `+` as a space, which would turn
4
+ * HarvardX+CS50+X into "HarvardX CS50 X". decodeURIComponent keeps `+` intact.
5
+ */
6
+ export declare function getCourseRunKeyFromUrl(): string | null;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Read ?course_run_key= from the current URL.
3
+ * Avoid URLSearchParams — it treats bare `+` as a space, which would turn
4
+ * HarvardX+CS50+X into "HarvardX CS50 X". decodeURIComponent keeps `+` intact.
5
+ */
6
+ export function getCourseRunKeyFromUrl() {
7
+ if (typeof window === 'undefined')
8
+ return null;
9
+ const match = /[?&]course_run_key=([^&]*)/.exec(window.location.search);
10
+ if (!match)
11
+ return null;
12
+ try {
13
+ // Preserve literal `+` (treat as %2B) then decode %XX sequences.
14
+ return decodeURIComponent(match[1].replace(/\+/g, '%2B')).trim() || null;
15
+ }
16
+ catch (_a) {
17
+ return match[1].trim() || null;
18
+ }
19
+ }
20
+ //# sourceMappingURL=url.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url.js","sourceRoot":"","sources":["../../src/utils/url.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,sBAAsB;IACpC,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAC/C,MAAM,KAAK,GAAG,4BAA4B,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxE,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,CAAC;QACH,iEAAiE;QACjE,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;IAC3E,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;IACjC,CAAC;AACH,CAAC","sourcesContent":["/**\n * Read ?course_run_key= from the current URL.\n * Avoid URLSearchParams — it treats bare `+` as a space, which would turn\n * HarvardX+CS50+X into \"HarvardX CS50 X\". decodeURIComponent keeps `+` intact.\n */\nexport function getCourseRunKeyFromUrl(): string | null {\n if (typeof window === 'undefined') return null;\n const match = /[?&]course_run_key=([^&]*)/.exec(window.location.search);\n if (!match) return null;\n try {\n // Preserve literal `+` (treat as %2B) then decode %XX sequences.\n return decodeURIComponent(match[1].replace(/\\+/g, '%2B')).trim() || null;\n } catch {\n return match[1].trim() || null;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edx/frontend-app-alacarte-checkout",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "description": "alacarte Checkout Frontend application",
5
5
  "repository": {
6
6
  "type": "git",