@edx/frontend-app-subscription-checkout 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/LICENSE +661 -0
  2. package/README.rst +235 -0
  3. package/dist/Main.d.ts +3 -0
  4. package/dist/Main.js +15 -0
  5. package/dist/Main.js.map +1 -0
  6. package/dist/app.d.ts +3 -0
  7. package/dist/app.js +13 -0
  8. package/dist/app.js.map +1 -0
  9. package/dist/checkoutPage/CheckoutPage.d.ts +3 -0
  10. package/dist/checkoutPage/CheckoutPage.js +30 -0
  11. package/dist/checkoutPage/CheckoutPage.js.map +1 -0
  12. package/dist/components/BrandMarks/BrandMarks.d.ts +3 -0
  13. package/dist/components/BrandMarks/BrandMarks.js +9 -0
  14. package/dist/components/BrandMarks/BrandMarks.js.map +1 -0
  15. package/dist/components/CartSummary/CartSummary.d.ts +21 -0
  16. package/dist/components/CartSummary/CartSummary.js +30 -0
  17. package/dist/components/CartSummary/CartSummary.js.map +1 -0
  18. package/dist/components/CartSummary/Messages.d.ts +73 -0
  19. package/dist/components/CartSummary/Messages.js +76 -0
  20. package/dist/components/CartSummary/Messages.js.map +1 -0
  21. package/dist/components/Checkout/Checkout.d.ts +4 -0
  22. package/dist/components/Checkout/Checkout.js +40 -0
  23. package/dist/components/Checkout/Checkout.js.map +1 -0
  24. package/dist/components/CheckoutGate/CheckoutGate.d.ts +7 -0
  25. package/dist/components/CheckoutGate/CheckoutGate.js +35 -0
  26. package/dist/components/CheckoutGate/CheckoutGate.js.map +1 -0
  27. package/dist/components/CourseEnrollmentCard/CourseEnrollmentCard.d.ts +9 -0
  28. package/dist/components/CourseEnrollmentCard/CourseEnrollmentCard.js +31 -0
  29. package/dist/components/CourseEnrollmentCard/CourseEnrollmentCard.js.map +1 -0
  30. package/dist/components/CourseEnrollmentCard/Messages.d.ts +33 -0
  31. package/dist/components/CourseEnrollmentCard/Messages.js +35 -0
  32. package/dist/components/CourseEnrollmentCard/Messages.js.map +1 -0
  33. package/dist/components/EdxUnlimitedBanner/EdxUnlimitedBanner.d.ts +13 -0
  34. package/dist/components/EdxUnlimitedBanner/EdxUnlimitedBanner.js +11 -0
  35. package/dist/components/EdxUnlimitedBanner/EdxUnlimitedBanner.js.map +1 -0
  36. package/dist/components/EnrollmentSummary/EnrollmentSummary.d.ts +15 -0
  37. package/dist/components/EnrollmentSummary/EnrollmentSummary.js +21 -0
  38. package/dist/components/EnrollmentSummary/EnrollmentSummary.js.map +1 -0
  39. package/dist/components/EnrollmentSummary/Messages.d.ts +33 -0
  40. package/dist/components/EnrollmentSummary/Messages.js +35 -0
  41. package/dist/components/EnrollmentSummary/Messages.js.map +1 -0
  42. package/dist/components/PaymentFields/PaymentFields.d.ts +9 -0
  43. package/dist/components/PaymentFields/PaymentFields.js +7 -0
  44. package/dist/components/PaymentFields/PaymentFields.js.map +1 -0
  45. package/dist/components/PaymentForm/Messages.d.ts +88 -0
  46. package/dist/components/PaymentForm/Messages.js +95 -0
  47. package/dist/components/PaymentForm/Messages.js.map +1 -0
  48. package/dist/components/PaymentForm/PaymentForm.d.ts +21 -0
  49. package/dist/components/PaymentForm/PaymentForm.js +69 -0
  50. package/dist/components/PaymentForm/PaymentForm.js.map +1 -0
  51. package/dist/components/StatusScreens/Messages.d.ts +103 -0
  52. package/dist/components/StatusScreens/Messages.js +108 -0
  53. package/dist/components/StatusScreens/Messages.js.map +1 -0
  54. package/dist/components/StatusScreens/StatusScreens.d.ts +23 -0
  55. package/dist/components/StatusScreens/StatusScreens.js +25 -0
  56. package/dist/components/StatusScreens/StatusScreens.js.map +1 -0
  57. package/dist/constants.d.ts +59 -0
  58. package/dist/constants.js +276 -0
  59. package/dist/constants.js.map +1 -0
  60. package/dist/data/hooks/keys.d.ts +14 -0
  61. package/dist/data/hooks/keys.js +15 -0
  62. package/dist/data/hooks/keys.js.map +1 -0
  63. package/dist/data/hooks/useCheckout.d.ts +57 -0
  64. package/dist/data/hooks/useCheckout.js +249 -0
  65. package/dist/data/hooks/useCheckout.js.map +1 -0
  66. package/dist/data/hooks/useCheckoutGate.d.ts +15 -0
  67. package/dist/data/hooks/useCheckoutGate.js +39 -0
  68. package/dist/data/hooks/useCheckoutGate.js.map +1 -0
  69. package/dist/data/hooks/useCheckoutMutations.d.ts +16 -0
  70. package/dist/data/hooks/useCheckoutMutations.js +131 -0
  71. package/dist/data/hooks/useCheckoutMutations.js.map +1 -0
  72. package/dist/data/hooks/useCheckoutQueries.d.ts +30 -0
  73. package/dist/data/hooks/useCheckoutQueries.js +97 -0
  74. package/dist/data/hooks/useCheckoutQueries.js.map +1 -0
  75. package/dist/data/services/subs/urls.d.ts +2 -0
  76. package/dist/data/services/subs/urls.js +4 -0
  77. package/dist/data/services/subs/urls.js.map +1 -0
  78. package/dist/i18n/index.d.ts +1 -0
  79. package/dist/i18n/index.js +2 -0
  80. package/dist/i18n/index.js.map +1 -0
  81. package/dist/index.d.ts +2 -0
  82. package/dist/index.js +3 -0
  83. package/dist/index.js.map +1 -0
  84. package/dist/messages.d.ts +8 -0
  85. package/dist/messages.js +10 -0
  86. package/dist/messages.js.map +1 -0
  87. package/dist/routes.d.ts +18 -0
  88. package/dist/routes.js +67 -0
  89. package/dist/routes.js.map +1 -0
  90. package/dist/slots/ExampleSlot/ExampleSlot.d.ts +3 -0
  91. package/dist/slots/ExampleSlot/ExampleSlot.js +6 -0
  92. package/dist/slots/ExampleSlot/ExampleSlot.js.map +1 -0
  93. package/dist/slots.d.ts +3 -0
  94. package/dist/slots.js +7 -0
  95. package/dist/slots.js.map +1 -0
  96. package/dist/style.scss +14 -0
  97. package/dist/styles/components/brandMarks.scss +54 -0
  98. package/dist/styles/components/cartSummary.scss +18 -0
  99. package/dist/styles/components/checkout.scss +29 -0
  100. package/dist/styles/components/checkoutStepper.scss +9 -0
  101. package/dist/styles/components/courseEnrollmentCard.scss +38 -0
  102. package/dist/styles/components/edxUnlimitedBanner.scss +138 -0
  103. package/dist/styles/components/enrollmentSummary.scss +13 -0
  104. package/dist/styles/components/paymentFields.scss +15 -0
  105. package/dist/styles/components/paymentForm.scss +105 -0
  106. package/dist/styles/components/statusScreens.scss +29 -0
  107. package/dist/styles/components/successScreen.scss +0 -0
  108. package/dist/types.d.ts +222 -0
  109. package/dist/types.js +2 -0
  110. package/dist/types.js.map +1 -0
  111. package/dist/utils/auth.d.ts +7 -0
  112. package/dist/utils/auth.js +28 -0
  113. package/dist/utils/auth.js.map +1 -0
  114. package/dist/utils/billingDetails.d.ts +30 -0
  115. package/dist/utils/billingDetails.js +47 -0
  116. package/dist/utils/billingDetails.js.map +1 -0
  117. package/dist/utils/bootstrapApi.d.ts +20 -0
  118. package/dist/utils/bootstrapApi.js +142 -0
  119. package/dist/utils/bootstrapApi.js.map +1 -0
  120. package/dist/utils/cartApi.d.ts +16 -0
  121. package/dist/utils/cartApi.js +118 -0
  122. package/dist/utils/cartApi.js.map +1 -0
  123. package/dist/utils/checkout.d.ts +6 -0
  124. package/dist/utils/checkout.js +28 -0
  125. package/dist/utils/checkout.js.map +1 -0
  126. package/dist/utils/fetchActiveSubscriptions.d.ts +4 -0
  127. package/dist/utils/fetchActiveSubscriptions.js +36 -0
  128. package/dist/utils/fetchActiveSubscriptions.js.map +1 -0
  129. package/dist/utils/fetchBillingPlans.d.ts +2 -0
  130. package/dist/utils/fetchBillingPlans.js +48 -0
  131. package/dist/utils/fetchBillingPlans.js.map +1 -0
  132. package/dist/utils/graphqlClient.d.ts +16 -0
  133. package/dist/utils/graphqlClient.js +39 -0
  134. package/dist/utils/graphqlClient.js.map +1 -0
  135. package/dist/utils/index.d.ts +3 -0
  136. package/dist/utils/index.js +17 -0
  137. package/dist/utils/index.js.map +1 -0
  138. package/dist/utils/provision.d.ts +25 -0
  139. package/dist/utils/provision.js +95 -0
  140. package/dist/utils/provision.js.map +1 -0
  141. package/dist/utils/queries.d.ts +35 -0
  142. package/dist/utils/queries.js +168 -0
  143. package/dist/utils/queries.js.map +1 -0
  144. package/dist/utils/stripe.d.ts +7 -0
  145. package/dist/utils/stripe.js +43 -0
  146. package/dist/utils/stripe.js.map +1 -0
  147. package/dist/widgets/ExampleHeader/app.d.ts +3 -0
  148. package/dist/widgets/ExampleHeader/app.js +19 -0
  149. package/dist/widgets/ExampleHeader/app.js.map +1 -0
  150. package/dist/widgets/ExampleHeader/index.d.ts +1 -0
  151. package/dist/widgets/ExampleHeader/index.js +2 -0
  152. package/dist/widgets/ExampleHeader/index.js.map +1 -0
  153. package/package.json +85 -0
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Status, Phase } from './constants';\n\nexport type { Status, Phase };\n\n// --- Bootstrap domain --------------------------------------------------------\nexport interface TwouCustomerContact {\n email?: string,\n}\n\nexport interface TwouCustomerParty {\n paName: string,\n indFirstName?: string,\n indLastName?: string,\n paContacts: { edges: { node: TwouCustomerContact }[] },\n}\n\nexport interface TwouCustomer {\n uid: string,\n parId: string,\n parParty: TwouCustomerParty,\n}\n\nexport type GqlRunner = (query: string, variables?: Record<string, unknown>, retry?: boolean) => Promise<any>;\n\n// --- Cart domain -------------------------------------------------------------\nexport interface Cart {\n biId: string,\n uid: string,\n}\n\nexport interface CartIds {\n customerUid: string,\n salableUid: string,\n billingPlanUid: string,\n}\n\nexport type BillingCycle = 'monthly' | 'annual';\n\nexport type BillingPeriod = 'MONTHLY' | 'ANNUAL' | 'QUARTERLY' | string;\n\nexport interface BillingPlanOwnershipModel {\n uid: string,\n omName: string,\n}\n\nexport interface BillingPlan {\n uid: string,\n stripePriceId: string,\n /** Price amount e.g. \"39.99\" */\n amount: string,\n /** Currency code e.g. \"USD\" */\n currency: string,\n isTemplate: boolean,\n ownershipModel: BillingPlanOwnershipModel,\n}\n\nexport interface SubscriptionDetails {\n biId: string, // \"BI301ZDAD6CMICG2\"\n trialStart: string | null,\n trialEnd: string | null,\n renewalType: string | null, // \"Annual\" | \"Monthly\"\n cardLast4: string | null, // \"4242\"\n cardBrand: string | null, // \"visa\" | \"mastercard\" etc\n stripePaymentMethodId: string | null,\n planName: string | null, // \"Monthly Subscription\"\n}\n\nexport interface CurrentUserSubscriberResult {\n isSubscriber: boolean,\n subscriptionBiId: string | null,\n status: string | null,\n subscriptionStartDate: string | null,\n currentPeriodEnd: string | null,\n}\n\n/** Course shown on checkout / post-checkout when arriving via ?course_run_key=. */\nexport interface CourseDetail {\n maId: string,\n maName: string,\n maDescription: string | null,\n tShortDescription: string | null,\n imageUrl: string | null,\n tCourseKey: string | null,\n tBrandText: string | null,\n tPrimarySubjectArea: string | null,\n tLevelType: string | null,\n tLanguage: string | null,\n tPacingType: string | null,\n tDurationLow: number | null,\n tDurationHigh: number | null,\n tDurationUnit: string | null,\n tEffortLow: number | null,\n tEffortHigh: number | null,\n tEffortUnit: string | null,\n tUrlCourse: string | null,\n tB2cSubscriptionInclusion: boolean | null,\n tHasOfacRestrictions: boolean | null,\n tCatalogActive: boolean | null,\n}\n\n/** Returned by getBillingPlans — all available plans for the product. */\nexport interface BillingPlansResult {\n plans: BillingPlan[],\n}\n\n/** Returned by getSalableUid — the product uid and its ownership models. */\nexport interface ProductInfo {\n uid: string,\n maName: string,\n ownershipModels: { uid: string, omName: string }[],\n}\n\nexport interface CartStatusResult {\n status: Status | null,\n stripeSubscriptionId: string | null,\n lastComment: string,\n}\n\nexport interface CartBillingAddress {\n tFirstName: string,\n tLastName: string,\n upaLine1: string,\n upaCity: string,\n upaPostcode: string,\n gadStateOrProvince: string,\n gadCountry: { iso2Code: string },\n}\n\nexport interface UpdateCartItemArgs {\n cartItemUid: string,\n billingAddress?: CartBillingAddress,\n paymentMethodId?: string,\n}\n\n/** Auto-renewal consent captured on TwouCart at checkout confirmation (SUBS-228). */\nexport interface CheckoutConsent {\n /** Main auto-renewal checkbox — always required at submit. */\n tConsentRecurring: boolean,\n /** UK/CA resident checkbox; null when that checkbox is not shown. */\n tConsentUkCa: boolean | null,\n /** ISO-8601 timestamp when consent was captured. */\n tConsentAt: string,\n /** Exact disclosure text shown to the learner (audit trail). */\n tConsentText: string,\n}\n\nexport interface UpdateCartConsentArgs {\n cartUid: string,\n consent: CheckoutConsent,\n}\n\nexport interface BillingInfo {\n firstName: string,\n lastName: string,\n city: string,\n zip: string,\n country: string,\n line1: string,\n state: string,\n}\n\n// --- Checkout client surface -------------------------------------------------\nexport interface CheckoutClient {\n gql: GqlRunner,\n setToken: (token: string) => void,\n createCart: (vars: CartIds) => Promise<Cart>,\n getCartItem: (biId: string) => Promise<{ cartUid: string, cartItemUid: string }>,\n updateCartItem: (args: UpdateCartItemArgs) => Promise<void>,\n updateCartConsent: (args: UpdateCartConsentArgs) => Promise<void>,\n placeOrder: (cartUid: string) => Promise<unknown>,\n getCartStatus: (biId: string) => Promise<CartStatusResult>,\n findSubscriptionBiId: (biId: string) => Promise<SubscriptionDetails | null>,\n getSalableUid: (maId: string) => Promise<ProductInfo>,\n getBillingPlans: () => Promise<BillingPlansResult>,\n getActiveSubscriptions: () => Promise<{ active: number }>,\n isCurrentUserSubscriber: () => Promise<CurrentUserSubscriberResult | null>,\n getCourseDetail: (courseKey: string) => Promise<CourseDetail | null>,\n}\n\nexport interface DevAuth {\n iamUrl: string,\n clientId: string,\n clientSecret: string,\n}\n\n// --- Minimal Stripe.js types -------------------------------------------------\n// Just the surface this module uses. If you depend on `@stripe/stripe-js`,\n// you can replace these with its richer types.\nexport interface StripeElementChangeEvent {\n error?: { message?: string },\n [key: string]: unknown,\n}\n\nexport interface StripeElementLike {\n mount: (node: HTMLElement) => void,\n on: (event: string, handler: (e: StripeElementChangeEvent) => void) => void,\n unmount?: () => void,\n}\n\nexport interface StripeElementsLike {\n create: (type: string, options?: Record<string, unknown>) => StripeElementLike,\n}\n\nexport interface StripeError {\n message?: string,\n code?: string,\n}\n\nexport interface PaymentMethodResult {\n paymentMethod?: { id: string },\n error?: StripeError,\n}\n\nexport interface ConfirmResult {\n error?: StripeError,\n setupIntent?: unknown,\n paymentIntent?: unknown,\n}\n\nexport interface CreatePaymentMethodParams {\n type: 'card',\n card: StripeElementLike,\n billing_details?: Record<string, unknown>,\n}\n\nexport interface StripeLike {\n elements: () => StripeElementsLike,\n createPaymentMethod: (params: CreatePaymentMethodParams) => Promise<PaymentMethodResult>,\n confirmCardSetup: (clientSecret: string) => Promise<ConfirmResult>,\n confirmCardPayment: (clientSecret: string) => Promise<ConfirmResult>,\n}\n\ndeclare global {\n interface Window {\n Stripe?: (publishableKey: string) => StripeLike,\n }\n}\n\nexport interface TrialDateRange {\n /** e.g. \"June 22, 2026\" */\n displayStart: string,\n /** e.g. \"June 29, 2026\" */\n displayEnd: string,\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { CheckoutClient } from '../types';
2
+ interface CheckoutClientConfig {
3
+ apiUrl: string;
4
+ confirmIntentUrl?: string;
5
+ }
6
+ export declare function createCheckoutClient({ apiUrl }: CheckoutClientConfig): CheckoutClient;
7
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * api/index.ts — composes the pieces into a single checkout client and
3
+ * re-exports the API utilities.
4
+ */
5
+ import { createGqlClient } from './graphqlClient';
6
+ import * as cart from './cartApi';
7
+ import * as bootstrap from './bootstrapApi';
8
+ export function createCheckoutClient({ apiUrl }) {
9
+ const { gql, setToken } = createGqlClient({ apiUrl });
10
+ const fetchStatus = (biId) => cart.getCartStatus(gql, biId);
11
+ return {
12
+ gql,
13
+ setToken,
14
+ createCart: (vars) => cart.createCart(gql, vars),
15
+ getCartItem: (biId) => cart.getCartItem(gql, biId),
16
+ updateCartItem: (args) => cart.updateCartItem(gql, args),
17
+ updateCartConsent: (args) => cart.updateCartConsent(gql, args),
18
+ placeOrder: (cartUid) => cart.placeOrder(gql, cartUid),
19
+ getCartStatus: fetchStatus,
20
+ findSubscriptionBiId: (biId) => cart.findSubscriptionBiId(gql, biId),
21
+ getSalableUid: (maId) => bootstrap.getSalableUid(gql, maId),
22
+ getBillingPlans: () => bootstrap.getBillingPlans(gql),
23
+ getActiveSubscriptions: () => bootstrap.getActiveSubscriptions(gql),
24
+ isCurrentUserSubscriber: () => bootstrap.isCurrentUserSubscriber(gql),
25
+ getCourseDetail: (courseKey) => bootstrap.getCourseDetail(gql, courseKey),
26
+ };
27
+ }
28
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/utils/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAU5C,MAAM,UAAU,oBAAoB,CAClC,EAAE,MAAM,EAAwB;IAEhC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAEpE,OAAO;QACL,GAAG;QACH,QAAQ;QACR,UAAU,EAAE,CAAC,IAAa,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC;QACzD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC;QAClD,cAAc,EAAE,CAAC,IAAwB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC;QAC5E,iBAAiB,EAAE,CAAC,IAA2B,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC;QACrF,UAAU,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;QACtD,aAAa,EAAE,WAAW;QAC1B,oBAAoB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,EAAE,IAAI,CAAC;QACpE,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC;QAC3D,eAAe,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC;QACrD,sBAAsB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,sBAAsB,CAAC,GAAG,CAAC;QACnE,uBAAuB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,uBAAuB,CAAC,GAAG,CAAC;QACrE,eAAe,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,EAAE,SAAS,CAAC;KAC1E,CAAC;AACJ,CAAC","sourcesContent":["/**\n * api/index.ts — composes the pieces into a single checkout client and\n * re-exports the API utilities.\n */\nimport { createGqlClient } from './graphqlClient';\nimport * as cart from './cartApi';\nimport * as bootstrap from './bootstrapApi';\nimport type {\n CheckoutClient, CartIds, UpdateCartItemArgs, UpdateCartConsentArgs,\n} from '../types';\n\ninterface CheckoutClientConfig {\n apiUrl: string,\n confirmIntentUrl?: string,\n}\n\nexport function createCheckoutClient(\n { apiUrl }: CheckoutClientConfig,\n): CheckoutClient {\n const { gql, setToken } = createGqlClient({ apiUrl });\n const fetchStatus = (biId: string) => cart.getCartStatus(gql, biId);\n\n return {\n gql,\n setToken,\n createCart: (vars: CartIds) => cart.createCart(gql, vars),\n getCartItem: (biId) => cart.getCartItem(gql, biId),\n updateCartItem: (args: UpdateCartItemArgs) => cart.updateCartItem(gql, args),\n updateCartConsent: (args: UpdateCartConsentArgs) => cart.updateCartConsent(gql, args),\n placeOrder: (cartUid) => cart.placeOrder(gql, cartUid),\n getCartStatus: fetchStatus,\n findSubscriptionBiId: (biId) => cart.findSubscriptionBiId(gql, biId),\n getSalableUid: (maId) => bootstrap.getSalableUid(gql, maId),\n getBillingPlans: () => bootstrap.getBillingPlans(gql),\n getActiveSubscriptions: () => bootstrap.getActiveSubscriptions(gql),\n isCurrentUserSubscriber: () => bootstrap.isCurrentUserSubscriber(gql),\n getCourseDetail: (courseKey) => bootstrap.getCourseDetail(gql, courseKey),\n };\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import type { BillingPlan, BillingCycle } from '../types';
2
+ export interface PlanPrice {
3
+ uid: string;
4
+ stripePriceId: string;
5
+ amount: string;
6
+ currency: string;
7
+ label: string;
8
+ }
9
+ /**
10
+ * getPlanPrice — returns the price for the selected billing cycle.
11
+ *
12
+ * Matches plans by looking for "annual"/"year" or "month" in omName.
13
+ * Falls back to the first plan if no match is found.
14
+ *
15
+ * @example
16
+ * getPlanPrice(billingPlans, 'monthly')
17
+ * // → { uid: '...', amount: '11', currency: 'USD', label: '$11 USD/month' }
18
+ *
19
+ * getPlanPrice(billingPlans, 'annual')
20
+ * // → { uid: '...', amount: '111', currency: 'USD', label: '$111 USD/year' }
21
+ */
22
+ export declare function getPlanPrice(plans: BillingPlan[], cycle: BillingCycle): PlanPrice | null;
23
+ /**
24
+ * getRenewalLabel — returns a "Then $X/month" style label for CartSummary.
25
+ *
26
+ * @example
27
+ * getRenewalLabel(billingPlans, 'monthly') // → "Then $11 USD/month"
28
+ * getRenewalLabel(billingPlans, 'annual') // → "Then $111 USD/year"
29
+ */
30
+ export declare function getRenewalLabel(plans: BillingPlan[], cycle: BillingCycle): string;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * getPlanPrice — returns the price for the selected billing cycle.
3
+ *
4
+ * Matches plans by looking for "annual"/"year" or "month" in omName.
5
+ * Falls back to the first plan if no match is found.
6
+ *
7
+ * @example
8
+ * getPlanPrice(billingPlans, 'monthly')
9
+ * // → { uid: '...', amount: '11', currency: 'USD', label: '$11 USD/month' }
10
+ *
11
+ * getPlanPrice(billingPlans, 'annual')
12
+ * // → { uid: '...', amount: '111', currency: 'USD', label: '$111 USD/year' }
13
+ */
14
+ export function getPlanPrice(plans, cycle) {
15
+ var _a;
16
+ if (!plans.length)
17
+ return null;
18
+ const isAnnual = cycle === 'annual';
19
+ const match = (_a = plans.find((p) => {
20
+ var _a, _b;
21
+ const name = (_b = (_a = p.ownershipModel.omName) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
22
+ return isAnnual
23
+ ? name.includes('annual') || name.includes('year')
24
+ : name.includes('month');
25
+ })) !== null && _a !== void 0 ? _a : plans[0];
26
+ return {
27
+ uid: match.uid,
28
+ stripePriceId: match.stripePriceId,
29
+ amount: match.amount,
30
+ currency: match.currency,
31
+ label: `$${match.amount} ${match.currency}/${isAnnual ? 'year' : 'month'}`,
32
+ };
33
+ }
34
+ /**
35
+ * getRenewalLabel — returns a "Then $X/month" style label for CartSummary.
36
+ *
37
+ * @example
38
+ * getRenewalLabel(billingPlans, 'monthly') // → "Then $11 USD/month"
39
+ * getRenewalLabel(billingPlans, 'annual') // → "Then $111 USD/year"
40
+ */
41
+ export function getRenewalLabel(plans, cycle) {
42
+ const price = getPlanPrice(plans, cycle);
43
+ if (!price)
44
+ return '';
45
+ return `Then $${price.amount} ${price.currency}/${cycle === 'annual' ? 'year' : 'month'}`;
46
+ }
47
+ //# sourceMappingURL=billingDetails.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billingDetails.js","sourceRoot":"","sources":["../../src/utils/billingDetails.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAC1B,KAAoB,EACpB,KAAmB;;IAEnB,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAE/B,MAAM,QAAQ,GAAG,KAAK,KAAK,QAAQ,CAAC;IAEpC,MAAM,KAAK,GAAG,MAAA,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;;QAC7B,MAAM,IAAI,GAAG,MAAA,MAAA,CAAC,CAAC,cAAc,CAAC,MAAM,0CAAE,WAAW,EAAE,mCAAI,EAAE,CAAC;QAC1D,OAAO,QAAQ;YACb,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAClD,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC,CAAC,mCAAI,KAAK,CAAC,CAAC,CAAC,CAAC;IAEf,OAAO;QACL,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,KAAK,EAAE,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE;KAC3E,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAoB,EACpB,KAAmB;IAEnB,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,OAAO,SAAS,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;AAC5F,CAAC","sourcesContent":["import type { BillingPlan, BillingCycle } from '../types';\n\nexport interface PlanPrice {\n uid: string,\n stripePriceId: string,\n amount: string,\n currency: string,\n label: string, // e.g. \"$11 USD/month\"\n}\n\n/**\n * getPlanPrice — returns the price for the selected billing cycle.\n *\n * Matches plans by looking for \"annual\"/\"year\" or \"month\" in omName.\n * Falls back to the first plan if no match is found.\n *\n * @example\n * getPlanPrice(billingPlans, 'monthly')\n * // → { uid: '...', amount: '11', currency: 'USD', label: '$11 USD/month' }\n *\n * getPlanPrice(billingPlans, 'annual')\n * // → { uid: '...', amount: '111', currency: 'USD', label: '$111 USD/year' }\n */\nexport function getPlanPrice(\n plans: BillingPlan[],\n cycle: BillingCycle,\n): PlanPrice | null {\n if (!plans.length) return null;\n\n const isAnnual = cycle === 'annual';\n\n const match = plans.find((p) => {\n const name = p.ownershipModel.omName?.toLowerCase() ?? '';\n return isAnnual\n ? name.includes('annual') || name.includes('year')\n : name.includes('month');\n }) ?? plans[0];\n\n return {\n uid: match.uid,\n stripePriceId: match.stripePriceId,\n amount: match.amount,\n currency: match.currency,\n label: `$${match.amount} ${match.currency}/${isAnnual ? 'year' : 'month'}`,\n };\n}\n\n/**\n * getRenewalLabel — returns a \"Then $X/month\" style label for CartSummary.\n *\n * @example\n * getRenewalLabel(billingPlans, 'monthly') // → \"Then $11 USD/month\"\n * getRenewalLabel(billingPlans, 'annual') // → \"Then $111 USD/year\"\n */\nexport function getRenewalLabel(\n plans: BillingPlan[],\n cycle: BillingCycle,\n): string {\n const price = getPlanPrice(plans, cycle);\n if (!price) return '';\n return `Then $${price.amount} ${price.currency}/${cycle === 'annual' ? 'year' : 'month'}`;\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import type { GqlRunner, BillingPlansResult, ProductInfo, CurrentUserSubscriberResult, CourseDetail } from '../types';
2
+ /** Resolve the viax customer uid for the authenticated user by email. */
3
+ export declare function getCustomerUid(gql: GqlRunner, userEmail: string): Promise<string>;
4
+ /**
5
+ * Fetch the TwouSubscriptionPlan uid to use as biiSalable in createTwouCart.
6
+ * The maId is the product market-area identifier, e.g. "TWOU-SUB-PLAN-001".
7
+ * The returned `uid` is passed as biiSalable when creating the cart.
8
+ */
9
+ export declare function getSalableUid(gql: GqlRunner, maId: string): Promise<ProductInfo>;
10
+ /** Fetch all available customer billing plans. */
11
+ export declare function getBillingPlans(gql: GqlRunner): Promise<BillingPlansResult>;
12
+ /** Check whether the currently authenticated user already has an active subscription. */
13
+ export declare function isCurrentUserSubscriber(gql: GqlRunner): Promise<CurrentUserSubscriberResult | null>;
14
+ /** Fetch the number of active subscriptions. */
15
+ export declare function getActiveSubscriptions(gql: GqlRunner): Promise<{
16
+ active: number;
17
+ }>;
18
+ /** Fetch course details for the enrollment card by tCourseKey.
19
+ * Sends the course key to Viax exactly as provided (e.g. HarvardX+CS50+X). */
20
+ export declare function getCourseDetail(gql: GqlRunner, courseKey: string): Promise<CourseDetail | null>;
@@ -0,0 +1,142 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ /**
11
+ * api/bootstrapApi.ts — fetch the runtime ids needed to create a cart.
12
+ *
13
+ * customerUid — looked up by the user's email from their viax party record.
14
+ * salableUid — the product uid from getProduct(maId), used as biiSalable.
15
+ * billingPlanUid — selected from filterCustomerBillingPlan by the caller.
16
+ */
17
+ import { GET_CUSTOMER, GET_BILLING_PLANS, GET_PRODUCT, GET_ACTIVE_SUBSCRIPTIONS_STATE, GET_IS_CURRENT_USER_SUBSCRIBER, GET_COURSE_DETAIL } from './queries';
18
+ /** Resolve the viax customer uid for the authenticated user by email. */
19
+ export function getCustomerUid(gql, userEmail) {
20
+ return __awaiter(this, void 0, void 0, function* () {
21
+ var _a, _b, _c;
22
+ const filter = `parParty.paContacts.email = '${userEmail}'`;
23
+ const data = yield gql(GET_CUSTOMER, { filter });
24
+ const node = (_c = (_b = (_a = data === null || data === void 0 ? void 0 : data.filterTwouCustomer) === null || _a === void 0 ? void 0 : _a.edges) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.node;
25
+ if (!(node === null || node === void 0 ? void 0 : node.uid))
26
+ throw new Error(`No viax customer found for ${userEmail}. Ensure your account is linked.`);
27
+ return node.uid;
28
+ });
29
+ }
30
+ /**
31
+ * Fetch the TwouSubscriptionPlan uid to use as biiSalable in createTwouCart.
32
+ * The maId is the product market-area identifier, e.g. "TWOU-SUB-PLAN-001".
33
+ * The returned `uid` is passed as biiSalable when creating the cart.
34
+ */
35
+ export function getSalableUid(gql, maId) {
36
+ return __awaiter(this, void 0, void 0, function* () {
37
+ var _a, _b;
38
+ const data = yield gql(GET_PRODUCT, { maId });
39
+ const product = data === null || data === void 0 ? void 0 : data.getProduct;
40
+ if (!(product === null || product === void 0 ? void 0 : product.uid))
41
+ throw new Error(`Product "${maId}" not found.`);
42
+ return {
43
+ uid: product.uid,
44
+ maName: (_a = product.maName) !== null && _a !== void 0 ? _a : '',
45
+ ownershipModels: ((_b = product.saOwnershipModels) !== null && _b !== void 0 ? _b : []).map((m) => ({ uid: m.uid, omName: m.omName })),
46
+ };
47
+ });
48
+ }
49
+ /** Fetch all available customer billing plans. */
50
+ export function getBillingPlans(gql) {
51
+ return __awaiter(this, void 0, void 0, function* () {
52
+ var _a, _b;
53
+ const data = yield gql(GET_BILLING_PLANS);
54
+ const edges = (_b = (_a = data === null || data === void 0 ? void 0 : data.filterCustomerBillingPlan) === null || _a === void 0 ? void 0 : _a.edges) !== null && _b !== void 0 ? _b : [];
55
+ if (!edges.length)
56
+ throw new Error('No billing plans found.');
57
+ const plans = edges.map(({ node }) => {
58
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
59
+ return ({
60
+ uid: node.uid,
61
+ stripePriceId: node.tStripePriceId,
62
+ amount: (_b = (_a = node.tStripeAmount) === null || _a === void 0 ? void 0 : _a.amount) !== null && _b !== void 0 ? _b : '0',
63
+ currency: (_e = (_d = (_c = node.tStripeAmount) === null || _c === void 0 ? void 0 : _c.units) === null || _d === void 0 ? void 0 : _d.code) !== null && _e !== void 0 ? _e : 'USD',
64
+ isTemplate: node.bipIsTemplate,
65
+ ownershipModel: {
66
+ uid: (_g = (_f = node.bipOwnershipModel) === null || _f === void 0 ? void 0 : _f.uid) !== null && _g !== void 0 ? _g : '',
67
+ omName: (_j = (_h = node.bipOwnershipModel) === null || _h === void 0 ? void 0 : _h.omName) !== null && _j !== void 0 ? _j : '',
68
+ },
69
+ });
70
+ });
71
+ return { plans };
72
+ });
73
+ }
74
+ /** Check whether the currently authenticated user already has an active subscription. */
75
+ export function isCurrentUserSubscriber(gql) {
76
+ return __awaiter(this, void 0, void 0, function* () {
77
+ var _a, _b, _c, _d;
78
+ const data = yield gql(GET_IS_CURRENT_USER_SUBSCRIBER);
79
+ const result = data === null || data === void 0 ? void 0 : data.tIsCurrentUserSubscriber;
80
+ if (!result)
81
+ return null;
82
+ return {
83
+ isSubscriber: result.isSubscriber === true,
84
+ subscriptionBiId: (_a = result.subscriptionBiId) !== null && _a !== void 0 ? _a : null,
85
+ status: (_b = result.status) !== null && _b !== void 0 ? _b : null,
86
+ subscriptionStartDate: (_c = result.subscriptionStartDate) !== null && _c !== void 0 ? _c : null,
87
+ currentPeriodEnd: (_d = result.currentPeriodEnd) !== null && _d !== void 0 ? _d : null,
88
+ };
89
+ });
90
+ }
91
+ /** Fetch the number of active subscriptions. */
92
+ export function getActiveSubscriptions(gql) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ var _a;
95
+ const data = yield gql(GET_ACTIVE_SUBSCRIPTIONS_STATE);
96
+ const active = (_a = data === null || data === void 0 ? void 0 : data.tActiveSubscriptionsState) === null || _a === void 0 ? void 0 : _a.active;
97
+ if (typeof active !== 'number') {
98
+ throw new Error('Unexpected response shape for tActiveSubscriptionsState');
99
+ }
100
+ return { active };
101
+ });
102
+ }
103
+ /** Fetch course details for the enrollment card by tCourseKey.
104
+ * Sends the course key to Viax exactly as provided (e.g. HarvardX+CS50+X). */
105
+ export function getCourseDetail(gql, courseKey) {
106
+ return __awaiter(this, void 0, void 0, function* () {
107
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
108
+ const key = courseKey.trim();
109
+ if (!key)
110
+ return null;
111
+ // Escape single quotes for the filter string only — do not alter `+` or other chars.
112
+ const filter = `tCourseKey = '${key.replace(/'/g, "''")}'`;
113
+ const data = yield gql(GET_COURSE_DETAIL, { filter });
114
+ const node = (_c = (_b = (_a = data === null || data === void 0 ? void 0 : data.filterTwouCourse) === null || _a === void 0 ? void 0 : _a.edges) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.node;
115
+ if (!(node === null || node === void 0 ? void 0 : node.maId))
116
+ return null;
117
+ return {
118
+ maId: node.maId,
119
+ maName: (_d = node.maName) !== null && _d !== void 0 ? _d : '',
120
+ maDescription: (_e = node.maDescription) !== null && _e !== void 0 ? _e : null,
121
+ tShortDescription: (_f = node.tShortDescription) !== null && _f !== void 0 ? _f : null,
122
+ imageUrl: (_g = node.imageUrl) !== null && _g !== void 0 ? _g : null,
123
+ tCourseKey: (_h = node.tCourseKey) !== null && _h !== void 0 ? _h : null,
124
+ tBrandText: (_j = node.tBrandText) !== null && _j !== void 0 ? _j : null,
125
+ tPrimarySubjectArea: (_k = node.tPrimarySubjectArea) !== null && _k !== void 0 ? _k : null,
126
+ tLevelType: (_l = node.tLevelType) !== null && _l !== void 0 ? _l : null,
127
+ tLanguage: (_m = node.tLanguage) !== null && _m !== void 0 ? _m : null,
128
+ tPacingType: (_p = (_o = node.tPacingType) === null || _o === void 0 ? void 0 : _o.code) !== null && _p !== void 0 ? _p : null,
129
+ tDurationLow: (_q = node.tDurationLow) !== null && _q !== void 0 ? _q : null,
130
+ tDurationHigh: (_r = node.tDurationHigh) !== null && _r !== void 0 ? _r : null,
131
+ tDurationUnit: (_t = (_s = node.tDurationUnit) === null || _s === void 0 ? void 0 : _s.code) !== null && _t !== void 0 ? _t : null,
132
+ tEffortLow: (_u = node.tEffortLow) !== null && _u !== void 0 ? _u : null,
133
+ tEffortHigh: (_v = node.tEffortHigh) !== null && _v !== void 0 ? _v : null,
134
+ tEffortUnit: (_x = (_w = node.tEffortUnit) === null || _w === void 0 ? void 0 : _w.code) !== null && _x !== void 0 ? _x : null,
135
+ tUrlCourse: (_y = node.tUrlCourse) !== null && _y !== void 0 ? _y : null,
136
+ tB2cSubscriptionInclusion: (_z = node.tB2cSubscriptionInclusion) !== null && _z !== void 0 ? _z : null,
137
+ tHasOfacRestrictions: (_0 = node.tHasOfacRestrictions) !== null && _0 !== void 0 ? _0 : null,
138
+ tCatalogActive: (_1 = node.tCatalogActive) !== null && _1 !== void 0 ? _1 : null,
139
+ };
140
+ });
141
+ }
142
+ //# sourceMappingURL=bootstrapApi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrapApi.js","sourceRoot":"","sources":["../../src/utils/bootstrapApi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/bootstrapApi.ts — fetch the runtime ids needed to create a cart.\n *\n * customerUid — looked up by the user's email from their viax party record.\n * salableUid — the product uid from getProduct(maId), used as biiSalable.\n * billingPlanUid — selected from filterCustomerBillingPlan by the caller.\n */\nimport { GET_CUSTOMER, GET_BILLING_PLANS, GET_PRODUCT, GET_ACTIVE_SUBSCRIPTIONS_STATE, GET_IS_CURRENT_USER_SUBSCRIBER, GET_COURSE_DETAIL } from './queries';\nimport type { GqlRunner, BillingPlansResult, BillingPlan, TwouCustomer, ProductInfo, CurrentUserSubscriberResult, CourseDetail } from '../types';\n\n/** Resolve the viax customer uid for the authenticated user by email. */\nexport async function getCustomerUid(gql: GqlRunner, userEmail: string): Promise<string> {\n const filter = `parParty.paContacts.email = '${userEmail}'`;\n const data = await gql(GET_CUSTOMER, { filter });\n const node: TwouCustomer | undefined = data?.filterTwouCustomer?.edges?.[0]?.node;\n if (!node?.uid) throw new Error(`No viax customer found for ${userEmail}. Ensure your account is linked.`);\n return node.uid;\n}\n\n/**\n * Fetch the TwouSubscriptionPlan uid to use as biiSalable in createTwouCart.\n * The maId is the product market-area identifier, e.g. \"TWOU-SUB-PLAN-001\".\n * The returned `uid` is passed as biiSalable when creating the cart.\n */\nexport async function getSalableUid(gql: GqlRunner, maId: string): Promise<ProductInfo> {\n const data = await gql(GET_PRODUCT, { maId });\n const product = data?.getProduct;\n if (!product?.uid) throw new Error(`Product \"${maId}\" not found.`);\n return {\n uid: product.uid,\n maName: product.maName ?? '',\n ownershipModels: (product.saOwnershipModels ?? []).map(\n (m: { uid: string, omName: string }) => ({ uid: m.uid, omName: m.omName }),\n ),\n };\n}\n\n/** Fetch all available customer billing plans. */\nexport async function getBillingPlans(gql: GqlRunner): Promise<BillingPlansResult> {\n const data = await gql(GET_BILLING_PLANS);\n const edges: {\n node: {\n uid: string,\n tStripePriceId: string,\n tStripeAmount?: { amount: string, units?: { code: string } },\n bipIsTemplate: boolean,\n bipOwnershipModel?: { uid: string, omName: string },\n },\n }[] = data?.filterCustomerBillingPlan?.edges ?? [];\n\n if (!edges.length) throw new Error('No billing plans found.');\n\n const plans: BillingPlan[] = edges.map(({ node }) => ({\n uid: node.uid,\n stripePriceId: node.tStripePriceId,\n amount: node.tStripeAmount?.amount ?? '0',\n currency: node.tStripeAmount?.units?.code ?? 'USD',\n isTemplate: node.bipIsTemplate,\n ownershipModel: {\n uid: node.bipOwnershipModel?.uid ?? '',\n omName: node.bipOwnershipModel?.omName ?? '',\n },\n }));\n\n return { plans };\n}\n\n/** Check whether the currently authenticated user already has an active subscription. */\nexport async function isCurrentUserSubscriber(gql: GqlRunner): Promise<CurrentUserSubscriberResult | null> {\n const data = await gql(GET_IS_CURRENT_USER_SUBSCRIBER);\n const result = data?.tIsCurrentUserSubscriber;\n if (!result) return null;\n return {\n isSubscriber: result.isSubscriber === true,\n subscriptionBiId: result.subscriptionBiId ?? null,\n status: result.status ?? null,\n subscriptionStartDate: result.subscriptionStartDate ?? null,\n currentPeriodEnd: result.currentPeriodEnd ?? null,\n };\n}\n\n/** Fetch the number of active subscriptions. */\nexport async function getActiveSubscriptions(gql: GqlRunner): Promise<{ active: number }> {\n const data = await gql(GET_ACTIVE_SUBSCRIPTIONS_STATE);\n const active = data?.tActiveSubscriptionsState?.active;\n if (typeof active !== 'number') {\n throw new Error('Unexpected response shape for tActiveSubscriptionsState');\n }\n return { active };\n}\n\n/** Fetch course details for the enrollment card by tCourseKey.\n * Sends the course key to Viax exactly as provided (e.g. HarvardX+CS50+X). */\nexport async function getCourseDetail(gql: GqlRunner, courseKey: string): Promise<CourseDetail | null> {\n const key = courseKey.trim();\n if (!key) return null;\n\n // Escape single quotes for the filter string only — do not alter `+` or other chars.\n const filter = `tCourseKey = '${key.replace(/'/g, \"''\")}'`;\n const data = await gql(GET_COURSE_DETAIL, { filter });\n const node = data?.filterTwouCourse?.edges?.[0]?.node;\n if (!node?.maId) return null;\n\n return {\n maId: node.maId,\n maName: node.maName ?? '',\n maDescription: node.maDescription ?? null,\n tShortDescription: node.tShortDescription ?? null,\n imageUrl: node.imageUrl ?? null,\n tCourseKey: node.tCourseKey ?? null,\n tBrandText: node.tBrandText ?? null,\n tPrimarySubjectArea: node.tPrimarySubjectArea ?? null,\n tLevelType: node.tLevelType ?? null,\n tLanguage: node.tLanguage ?? null,\n tPacingType: node.tPacingType?.code ?? null,\n tDurationLow: node.tDurationLow ?? null,\n tDurationHigh: node.tDurationHigh ?? null,\n tDurationUnit: node.tDurationUnit?.code ?? null,\n tEffortLow: node.tEffortLow ?? null,\n tEffortHigh: node.tEffortHigh ?? null,\n tEffortUnit: node.tEffortUnit?.code ?? null,\n tUrlCourse: node.tUrlCourse ?? null,\n tB2cSubscriptionInclusion: node.tB2cSubscriptionInclusion ?? null,\n tHasOfacRestrictions: node.tHasOfacRestrictions ?? null,\n tCatalogActive: node.tCatalogActive ?? null,\n };\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { GqlRunner, Cart, CartIds, CartStatusResult, UpdateCartItemArgs, UpdateCartConsentArgs, SubscriptionDetails } from '../types';
2
+ /** createTwouCart → { biId, uid } */
3
+ export declare function createCart(gql: GqlRunner, { customerUid, salableUid, billingPlanUid }: CartIds): Promise<Cart>;
4
+ /** → { cartUid, cartItemUid } */
5
+ export declare function getCartItem(gql: GqlRunner, biId: string): Promise<{
6
+ cartUid: string;
7
+ cartItemUid: string;
8
+ }>;
9
+ export declare function updateCartItem(gql: GqlRunner, { cartItemUid, billingAddress, paymentMethodId }: UpdateCartItemArgs): Promise<void>;
10
+ /** Persist checkout consent checkboxes onto the TwouCart BI. */
11
+ export declare function updateCartConsent(gql: GqlRunner, { cartUid, consent }: UpdateCartConsentArgs): Promise<void>;
12
+ /** "Place order" — triggers the server-side workflow. */
13
+ export declare function placeOrder(gql: GqlRunner, cartUid: string): Promise<unknown>;
14
+ /** → { status, stripeSubscriptionId, lastComment } */
15
+ export declare function getCartStatus(gql: GqlRunner, biId: string): Promise<CartStatusResult>;
16
+ export declare function findSubscriptionBiId(gql: GqlRunner, cartBiId: string): Promise<SubscriptionDetails | null>;
@@ -0,0 +1,118 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ /**
11
+ * api/cartApi.ts — one function per checkout operation.
12
+ * Each takes the `gql` runner as its first argument so they stay pure and
13
+ * independently testable (mock `gql`, assert the variables).
14
+ */
15
+ import { CREATE_CART, GET_CART_ITEM, UPDATE_CART_ITEM, UPDATE_CART, PLACE_ORDER, GET_CART_STATUS, FIND_SUBSCRIPTION, } from './queries';
16
+ /** createTwouCart → { biId, uid } */
17
+ export function createCart(gql_1, _a) {
18
+ return __awaiter(this, arguments, void 0, function* (gql, { customerUid, salableUid, billingPlanUid }) {
19
+ const data = yield gql(CREATE_CART, {
20
+ input: {
21
+ prPartyPrimary: { uid: customerUid },
22
+ hiConsistsOf: [{ biiSalable: { uid: salableUid }, paybBillingPlan: { uid: billingPlanUid } }],
23
+ },
24
+ });
25
+ return data.createTwouCart;
26
+ });
27
+ }
28
+ /** → { cartUid, cartItemUid } */
29
+ export function getCartItem(gql, biId) {
30
+ return __awaiter(this, void 0, void 0, function* () {
31
+ var _a, _b;
32
+ const data = yield gql(GET_CART_ITEM, { biId });
33
+ const bi = data.getBusinessInteraction;
34
+ const cartUid = bi === null || bi === void 0 ? void 0 : bi.uid;
35
+ const cartItemUid = (_b = (_a = bi === null || bi === void 0 ? void 0 : bi.hiConsistsOf) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.uid;
36
+ if (!cartUid || !cartItemUid) {
37
+ throw new Error(`Cart item not found for cart "${biId}".`);
38
+ }
39
+ return { cartUid, cartItemUid };
40
+ });
41
+ }
42
+ export function updateCartItem(gql_1, _a) {
43
+ return __awaiter(this, arguments, void 0, function* (gql, { cartItemUid, billingAddress, paymentMethodId }) {
44
+ const input = { uid: cartItemUid };
45
+ if (billingAddress)
46
+ input.paybBillingAddress = Object.assign({ typeName: 'ViaxUrbanPropertyAddress' }, billingAddress);
47
+ if (paymentMethodId)
48
+ input.paybPayment = { tStripePaymentMethodId: paymentMethodId };
49
+ yield gql(UPDATE_CART_ITEM, { input });
50
+ });
51
+ }
52
+ /** Persist checkout consent checkboxes onto the TwouCart BI. */
53
+ export function updateCartConsent(gql_1, _a) {
54
+ return __awaiter(this, arguments, void 0, function* (gql, { cartUid, consent }) {
55
+ yield gql(UPDATE_CART, {
56
+ input: {
57
+ uid: cartUid,
58
+ tConsentRecurring: consent.tConsentRecurring,
59
+ tConsentUkCa: consent.tConsentUkCa,
60
+ tConsentAt: consent.tConsentAt,
61
+ tConsentText: consent.tConsentText,
62
+ },
63
+ });
64
+ });
65
+ }
66
+ /** "Place order" — triggers the server-side workflow. */
67
+ export function placeOrder(gql, cartUid) {
68
+ return __awaiter(this, void 0, void 0, function* () {
69
+ const data = yield gql(PLACE_ORDER, {
70
+ input: {
71
+ businessProcessable: { uid: cartUid, typeName: 'TwouCart' },
72
+ transitionCode: 'Sanction_Check',
73
+ },
74
+ });
75
+ return data.startBusinessProcessableTransition;
76
+ });
77
+ }
78
+ /** → { status, stripeSubscriptionId, lastComment } */
79
+ export function getCartStatus(gql, biId) {
80
+ return __awaiter(this, void 0, void 0, function* () {
81
+ var _a, _b, _c, _d, _e, _f, _g;
82
+ const data = yield gql(GET_CART_STATUS, { biId });
83
+ const bi = (data === null || data === void 0 ? void 0 : data.getBusinessInteraction) || {};
84
+ const comments = ((_b = (_a = bi.cmComments) === null || _a === void 0 ? void 0 : _a.edges) === null || _b === void 0 ? void 0 : _b.map((e) => e.node.comText)) || [];
85
+ return {
86
+ status: ((_d = (_c = bi.bpStatus) === null || _c === void 0 ? void 0 : _c.code) !== null && _d !== void 0 ? _d : null),
87
+ stripeSubscriptionId: (_g = (_f = (_e = bi.hiConsistsOf) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.tStripeSubscriptionId) !== null && _g !== void 0 ? _g : null,
88
+ lastComment: comments.length ? comments[comments.length - 1] : '',
89
+ };
90
+ });
91
+ }
92
+ export function findSubscriptionBiId(gql, cartBiId) {
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
95
+ try {
96
+ const data = yield gql(FIND_SUBSCRIPTION, { filter: `biRelatesTo.biId = '${cartBiId}'` });
97
+ const node = (_c = (_b = (_a = data === null || data === void 0 ? void 0 : data.filterTwouSubscription) === null || _a === void 0 ? void 0 : _a.edges) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.node;
98
+ if (!(node === null || node === void 0 ? void 0 : node.biId))
99
+ return null;
100
+ const item = (_d = node.hiConsistsOf) === null || _d === void 0 ? void 0 : _d[0];
101
+ const payment = item === null || item === void 0 ? void 0 : item.paybPayment;
102
+ return {
103
+ biId: node.biId,
104
+ trialStart: (_e = node.tTrialStart) !== null && _e !== void 0 ? _e : null,
105
+ trialEnd: (_f = node.tTrialEnd) !== null && _f !== void 0 ? _f : null,
106
+ renewalType: (_g = node.tRenewalType) !== null && _g !== void 0 ? _g : null,
107
+ cardLast4: (_h = payment === null || payment === void 0 ? void 0 : payment.tPaymentMethodLast4) !== null && _h !== void 0 ? _h : null, // ← last four digits
108
+ cardBrand: (_j = payment === null || payment === void 0 ? void 0 : payment.tPaymentMethodBrand) !== null && _j !== void 0 ? _j : null, // ← card type (Visa, Mastercard etc)
109
+ stripePaymentMethodId: (_k = payment === null || payment === void 0 ? void 0 : payment.tStripePaymentMethodId) !== null && _k !== void 0 ? _k : null,
110
+ planName: (_o = (_m = (_l = item === null || item === void 0 ? void 0 : item.paybBillingPlan) === null || _l === void 0 ? void 0 : _l.bipOwnershipModel) === null || _m === void 0 ? void 0 : _m.omName) !== null && _o !== void 0 ? _o : null,
111
+ };
112
+ }
113
+ catch (_p) {
114
+ return null;
115
+ }
116
+ });
117
+ }
118
+ //# sourceMappingURL=cartApi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cartApi.js","sourceRoot":"","sources":["../../src/utils/cartApi.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * api/cartApi.ts — one function per checkout operation.\n * Each takes the `gql` runner as its first argument so they stay pure and\n * independently testable (mock `gql`, assert the variables).\n */\nimport {\n CREATE_CART, GET_CART_ITEM, UPDATE_CART_ITEM, UPDATE_CART,\n PLACE_ORDER, GET_CART_STATUS, FIND_SUBSCRIPTION,\n} from './queries';\nimport type {\n GqlRunner, Cart, CartIds, CartStatusResult, UpdateCartItemArgs,\n UpdateCartConsentArgs, Status, SubscriptionDetails,\n} from '../types';\n\n/** createTwouCart → { biId, uid } */\nexport async function createCart(gql: GqlRunner, { customerUid, salableUid, billingPlanUid }: CartIds): Promise<Cart> {\n const data = await gql(CREATE_CART, {\n input: {\n prPartyPrimary: { uid: customerUid },\n hiConsistsOf: [{ biiSalable: { uid: salableUid }, paybBillingPlan: { uid: billingPlanUid } }],\n },\n });\n return data.createTwouCart as Cart;\n}\n\n/** → { cartUid, cartItemUid } */\nexport async function getCartItem(gql: GqlRunner, biId: string): Promise<{ cartUid: string, cartItemUid: string }> {\n const data = await gql(GET_CART_ITEM, { biId });\n const bi = data.getBusinessInteraction;\n const cartUid = bi?.uid as string | undefined;\n const cartItemUid = bi?.hiConsistsOf?.[0]?.uid as string | undefined;\n\n if (!cartUid || !cartItemUid) {\n throw new Error(`Cart item not found for cart \"${biId}\".`);\n }\n\n return { cartUid, cartItemUid };\n}\n\nexport async function updateCartItem(\n gql: GqlRunner,\n { cartItemUid, billingAddress, paymentMethodId }: UpdateCartItemArgs,\n): Promise<void> {\n const input: Record<string, unknown> = { uid: cartItemUid };\n if (billingAddress) input.paybBillingAddress = { typeName: 'ViaxUrbanPropertyAddress', ...billingAddress };\n if (paymentMethodId) input.paybPayment = { tStripePaymentMethodId: paymentMethodId };\n await gql(UPDATE_CART_ITEM, { input });\n}\n\n/** Persist checkout consent checkboxes onto the TwouCart BI. */\nexport async function updateCartConsent(\n gql: GqlRunner,\n { cartUid, consent }: UpdateCartConsentArgs,\n): Promise<void> {\n await gql(UPDATE_CART, {\n input: {\n uid: cartUid,\n tConsentRecurring: consent.tConsentRecurring,\n tConsentUkCa: consent.tConsentUkCa,\n tConsentAt: consent.tConsentAt,\n tConsentText: consent.tConsentText,\n },\n });\n}\n\n/** \"Place order\" — triggers the server-side workflow. */\nexport async function placeOrder(gql: GqlRunner, cartUid: string): Promise<unknown> {\n const data = await gql(PLACE_ORDER, {\n input: {\n businessProcessable: { uid: cartUid, typeName: 'TwouCart' },\n transitionCode: 'Sanction_Check',\n },\n });\n return data.startBusinessProcessableTransition;\n}\n\n/** → { status, stripeSubscriptionId, lastComment } */\nexport async function getCartStatus(gql: GqlRunner, biId: string): Promise<CartStatusResult> {\n const data = await gql(GET_CART_STATUS, { biId });\n const bi = data?.getBusinessInteraction || {};\n const comments: string[] = bi.cmComments?.edges?.map((e: { node: { comText: string } }) => e.node.comText) || [];\n return {\n status: (bi.bpStatus?.code ?? null) as Status | null,\n stripeSubscriptionId: bi.hiConsistsOf?.[0]?.tStripeSubscriptionId ?? null,\n lastComment: comments.length ? comments[comments.length - 1] : '',\n };\n}\n\nexport async function findSubscriptionBiId(gql: GqlRunner, cartBiId: string): Promise<SubscriptionDetails | null> {\n try {\n const data = await gql(FIND_SUBSCRIPTION, { filter: `biRelatesTo.biId = '${cartBiId}'` });\n const node = data?.filterTwouSubscription?.edges?.[0]?.node;\n if (!node?.biId) return null;\n\n const item = node.hiConsistsOf?.[0];\n const payment = item?.paybPayment;\n\n return {\n biId: node.biId,\n trialStart: node.tTrialStart ?? null,\n trialEnd: node.tTrialEnd ?? null,\n renewalType: node.tRenewalType ?? null,\n cardLast4: payment?.tPaymentMethodLast4 ?? null, // ← last four digits\n cardBrand: payment?.tPaymentMethodBrand ?? null, // ← card type (Visa, Mastercard etc)\n stripePaymentMethodId: payment?.tStripePaymentMethodId ?? null,\n planName: item?.paybBillingPlan?.bipOwnershipModel?.omName ?? null,\n };\n } catch {\n return null;\n }\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { CheckoutClient } from '../types';
2
+ interface CheckoutClientConfig {
3
+ apiUrl: string;
4
+ }
5
+ export declare function createCheckoutClient({ apiUrl }: CheckoutClientConfig): CheckoutClient;
6
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * api/index.ts — composes the pieces into a single checkout client and
3
+ * re-exports the API utilities.
4
+ */
5
+ import { createGqlClient } from './graphqlClient';
6
+ import * as cart from './cartApi';
7
+ import * as bootstrap from './bootstrapApi';
8
+ export function createCheckoutClient({ apiUrl }) {
9
+ const { gql, setToken } = createGqlClient({ apiUrl });
10
+ const fetchStatus = (biId) => cart.getCartStatus(gql, biId);
11
+ return {
12
+ gql,
13
+ setToken,
14
+ createCart: (vars) => cart.createCart(gql, vars),
15
+ getCartItem: (biId) => cart.getCartItem(gql, biId),
16
+ updateCartItem: (args) => cart.updateCartItem(gql, args),
17
+ updateCartConsent: (args) => cart.updateCartConsent(gql, args),
18
+ placeOrder: (cartUid) => cart.placeOrder(gql, cartUid),
19
+ getCartStatus: fetchStatus,
20
+ findSubscriptionBiId: (biId) => cart.findSubscriptionBiId(gql, biId),
21
+ getSalableUid: (maId) => bootstrap.getSalableUid(gql, maId),
22
+ getBillingPlans: () => bootstrap.getBillingPlans(gql),
23
+ getActiveSubscriptions: () => bootstrap.getActiveSubscriptions(gql),
24
+ isCurrentUserSubscriber: () => bootstrap.isCurrentUserSubscriber(gql),
25
+ getCourseDetail: (courseKey) => bootstrap.getCourseDetail(gql, courseKey),
26
+ };
27
+ }
28
+ //# sourceMappingURL=checkout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkout.js","sourceRoot":"","sources":["../../src/utils/checkout.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC;AAS5C,MAAM,UAAU,oBAAoB,CAClC,EAAE,MAAM,EAAwB;IAEhC,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACtD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAEpE,OAAO;QACL,GAAG;QACH,QAAQ;QACR,UAAU,EAAE,CAAC,IAAa,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC;QACzD,WAAW,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC;QAClD,cAAc,EAAE,CAAC,IAAwB,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC;QAC5E,iBAAiB,EAAE,CAAC,IAA2B,EAAE,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC;QACrF,UAAU,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;QACtD,aAAa,EAAE,WAAW;QAC1B,oBAAoB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,EAAE,IAAI,CAAC;QACpE,aAAa,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC;QAC3D,eAAe,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC;QACrD,sBAAsB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,sBAAsB,CAAC,GAAG,CAAC;QACnE,uBAAuB,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,uBAAuB,CAAC,GAAG,CAAC;QACrE,eAAe,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,EAAE,SAAS,CAAC;KAC1E,CAAC;AACJ,CAAC","sourcesContent":["/**\n * api/index.ts — composes the pieces into a single checkout client and\n * re-exports the API utilities.\n */\nimport { createGqlClient } from './graphqlClient';\nimport * as cart from './cartApi';\nimport * as bootstrap from './bootstrapApi';\nimport type {\n CheckoutClient, CartIds, UpdateCartItemArgs, UpdateCartConsentArgs,\n} from '../types';\n\ninterface CheckoutClientConfig {\n apiUrl: string,\n}\n\nexport function createCheckoutClient(\n { apiUrl }: CheckoutClientConfig,\n): CheckoutClient {\n const { gql, setToken } = createGqlClient({ apiUrl });\n const fetchStatus = (biId: string) => cart.getCartStatus(gql, biId);\n\n return {\n gql,\n setToken,\n createCart: (vars: CartIds) => cart.createCart(gql, vars),\n getCartItem: (biId) => cart.getCartItem(gql, biId),\n updateCartItem: (args: UpdateCartItemArgs) => cart.updateCartItem(gql, args),\n updateCartConsent: (args: UpdateCartConsentArgs) => cart.updateCartConsent(gql, args),\n placeOrder: (cartUid) => cart.placeOrder(gql, cartUid),\n getCartStatus: fetchStatus,\n findSubscriptionBiId: (biId) => cart.findSubscriptionBiId(gql, biId),\n getSalableUid: (maId) => bootstrap.getSalableUid(gql, maId),\n getBillingPlans: () => bootstrap.getBillingPlans(gql),\n getActiveSubscriptions: () => bootstrap.getActiveSubscriptions(gql),\n isCurrentUserSubscriber: () => bootstrap.isCurrentUserSubscriber(gql),\n getCourseDetail: (courseKey) => bootstrap.getCourseDetail(gql, courseKey),\n };\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export interface ActiveSubscriptionsResult {
2
+ active: number;
3
+ }
4
+ export declare function fetchActiveSubscriptions(apiUrl: string): Promise<ActiveSubscriptionsResult>;
@@ -0,0 +1,36 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ /**
11
+ * api/fetchActiveSubscriptions.ts
12
+ *
13
+ * Fetches the number of active subscriptions without authentication.
14
+ * Used on mount to show the live learner count in CartSummary.
15
+ */
16
+ import { GET_ACTIVE_SUBSCRIPTIONS_STATE } from './queries';
17
+ export function fetchActiveSubscriptions(apiUrl) {
18
+ return __awaiter(this, void 0, void 0, function* () {
19
+ var _a, _b, _c, _d;
20
+ const res = yield fetch(apiUrl, {
21
+ method: 'POST',
22
+ headers: { 'Content-Type': 'application/json' },
23
+ body: JSON.stringify({ query: GET_ACTIVE_SUBSCRIPTIONS_STATE }),
24
+ });
25
+ if (!res.ok)
26
+ throw new Error(`Failed to fetch active subscriptions (${res.status})`);
27
+ const json = yield res.json();
28
+ if ((_a = json.errors) === null || _a === void 0 ? void 0 : _a.length)
29
+ throw new Error(((_b = json.errors[0]) === null || _b === void 0 ? void 0 : _b.message) || 'GraphQL error');
30
+ const active = (_d = (_c = json.data) === null || _c === void 0 ? void 0 : _c.tActiveSubscriptionsState) === null || _d === void 0 ? void 0 : _d.active;
31
+ if (typeof active !== 'number')
32
+ throw new Error('Unexpected response shape for tActiveSubscriptionsState');
33
+ return { active };
34
+ });
35
+ }
36
+ //# sourceMappingURL=fetchActiveSubscriptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetchActiveSubscriptions.js","sourceRoot":"","sources":["../../src/utils/fetchActiveSubscriptions.ts"],"names":[],"mappings":";;;;;;;;;AAAA;;;;;GAKG;AACH,OAAO,EAAE,8BAA8B,EAAE,MAAM,WAAW,CAAC;AAM3D,MAAM,UAAgB,wBAAwB,CAC5C,MAAc;;;QAEd,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE;YAC9B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,8BAA8B,EAAE,CAAC;SAChE,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;QAErF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,MAAA,IAAI,CAAC,MAAM,0CAAE,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,CAAA,MAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,0CAAE,OAAO,KAAI,eAAe,CAAC,CAAC;QAErF,MAAM,MAAM,GAAG,MAAA,MAAA,IAAI,CAAC,IAAI,0CAAE,yBAAyB,0CAAE,MAAM,CAAC;QAC5D,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAE3G,OAAO,EAAE,MAAM,EAAE,CAAC;IACpB,CAAC;CAAA","sourcesContent":["/**\n * api/fetchActiveSubscriptions.ts\n *\n * Fetches the number of active subscriptions without authentication.\n * Used on mount to show the live learner count in CartSummary.\n */\nimport { GET_ACTIVE_SUBSCRIPTIONS_STATE } from './queries';\n\nexport interface ActiveSubscriptionsResult {\n active: number,\n}\n\nexport async function fetchActiveSubscriptions(\n apiUrl: string,\n): Promise<ActiveSubscriptionsResult> {\n const res = await fetch(apiUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ query: GET_ACTIVE_SUBSCRIPTIONS_STATE }),\n });\n\n if (!res.ok) throw new Error(`Failed to fetch active subscriptions (${res.status})`);\n\n const json = await res.json();\n if (json.errors?.length) throw new Error(json.errors[0]?.message || 'GraphQL error');\n\n const active = json.data?.tActiveSubscriptionsState?.active;\n if (typeof active !== 'number') throw new Error('Unexpected response shape for tActiveSubscriptionsState');\n\n return { active };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import type { BillingPlansResult } from '../types';
2
+ export declare function fetchBillingPlans(apiUrl: string): Promise<BillingPlansResult>;