@stigg/react-sdk 6.9.1 → 6.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/components/checkout/CheckoutProvider.d.ts +1 -0
  2. package/dist/components/checkout/hooks/useCheckoutModel.d.ts +2 -0
  3. package/dist/components/checkout/steps/payment/PaymentMethods.d.ts +2 -2
  4. package/dist/components/checkout/steps/payment/PaymentStep.d.ts +1 -1
  5. package/dist/components/checkout/steps/payment/zuora/ZuoraPaymentForm.d.ts +3 -0
  6. package/dist/components/checkout/steps/payment/zuora/index.d.ts +3 -0
  7. package/dist/components/checkout/steps/payment/zuora/useZuoraIntegration.d.ts +5 -0
  8. package/dist/components/checkout/steps/payment/zuora/useZuoraSubmit.d.ts +11 -0
  9. package/dist/components/checkout/steps/payment/zuora/zuora.utils.d.ts +34 -0
  10. package/dist/react-sdk.cjs.development.js +583 -48
  11. package/dist/react-sdk.cjs.development.js.map +1 -1
  12. package/dist/react-sdk.cjs.production.min.js +1 -1
  13. package/dist/react-sdk.cjs.production.min.js.map +1 -1
  14. package/dist/react-sdk.esm.js +583 -48
  15. package/dist/react-sdk.esm.js.map +1 -1
  16. package/package.json +2 -2
  17. package/src/components/checkout/CheckoutContainer.tsx +19 -3
  18. package/src/components/checkout/CheckoutProvider.tsx +2 -0
  19. package/src/components/checkout/hooks/useCheckoutModel.ts +14 -3
  20. package/src/components/checkout/steps/payment/PaymentMethods.tsx +15 -2
  21. package/src/components/checkout/steps/payment/PaymentStep.tsx +8 -1
  22. package/src/components/checkout/steps/payment/zuora/ZuoraPaymentForm.tsx +128 -0
  23. package/src/components/checkout/steps/payment/zuora/index.ts +11 -0
  24. package/src/components/checkout/steps/payment/zuora/useZuoraIntegration.ts +56 -0
  25. package/src/components/checkout/steps/payment/zuora/useZuoraSubmit.ts +125 -0
  26. package/src/components/checkout/steps/payment/zuora/zuora.utils.ts +72 -0
  27. package/src/components/checkout/summary/CheckoutSummary.tsx +71 -44
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "6.9.1",
2
+ "version": "6.11.0",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -114,7 +114,7 @@
114
114
  "@emotion/react": "^11.10.5",
115
115
  "@emotion/styled": "^11.10.5",
116
116
  "@mui/material": "^5.12.0",
117
- "@stigg/js-client-sdk": "3.60.0",
117
+ "@stigg/js-client-sdk": "3.60.2",
118
118
  "@stripe/react-stripe-js": "^2.1.1",
119
119
  "@stripe/stripe-js": "^1.54.1",
120
120
  "@types/styled-components": "^5.1.26",
@@ -33,7 +33,12 @@ const getStepProps = (
33
33
  onBillingAddressChange,
34
34
  onChangePlan,
35
35
  collectPhoneNumber,
36
- }: Pick<CheckoutContainerProps, 'onBillingAddressChange' | 'onChangePlan' | 'collectPhoneNumber'>,
36
+ onCheckout,
37
+ onCheckoutCompleted,
38
+ }: Pick<
39
+ CheckoutContainerProps,
40
+ 'onBillingAddressChange' | 'onChangePlan' | 'collectPhoneNumber' | 'onCheckout' | 'onCheckoutCompleted'
41
+ >,
37
42
  ): StepProps => {
38
43
  switch (currentStep.key) {
39
44
  case CheckoutStepKey.PLAN:
@@ -43,7 +48,12 @@ const getStepProps = (
43
48
  case CheckoutStepKey.PAYMENT:
44
49
  return {
45
50
  content: (
46
- <PaymentStep onBillingAddressChange={onBillingAddressChange} collectPhoneNumber={collectPhoneNumber} />
51
+ <PaymentStep
52
+ onBillingAddressChange={onBillingAddressChange}
53
+ collectPhoneNumber={collectPhoneNumber}
54
+ onCheckout={onCheckout}
55
+ onCheckoutCompleted={onCheckoutCompleted}
56
+ />
47
57
  ),
48
58
  };
49
59
  default:
@@ -96,7 +106,13 @@ export function CheckoutContainer({
96
106
  !!activeSubscription &&
97
107
  activeSubscription.pricingType !== PricingType.Free;
98
108
 
99
- const { content } = getStepProps(currentStep, { onBillingAddressChange, onChangePlan, collectPhoneNumber });
109
+ const { content } = getStepProps(currentStep, {
110
+ onBillingAddressChange,
111
+ onChangePlan,
112
+ collectPhoneNumber,
113
+ onCheckout,
114
+ onCheckoutCompleted,
115
+ });
100
116
 
101
117
  const checkoutContent = (
102
118
  <>
@@ -36,6 +36,7 @@ export interface CheckoutContextState {
36
36
  paymentStep: PaymentStepState;
37
37
  widgetState: WidgetState;
38
38
  isWidgetWatermarkEnabled: boolean;
39
+ checkoutSuccess: boolean;
39
40
  }
40
41
 
41
42
  export const CheckoutContext = React.createContext<
@@ -154,6 +155,7 @@ export function CheckoutProvider({
154
155
  resourceId: checkout?.resource?.id,
155
156
  widgetState: { readOnly: false, isValid: true, isLoadingCheckoutData: isLoading },
156
157
  isWidgetWatermarkEnabled,
158
+ checkoutSuccess: false,
157
159
  };
158
160
 
159
161
  return initialState;
@@ -7,8 +7,8 @@ export type WidgetState = {
7
7
  };
8
8
 
9
9
  function useCheckoutState() {
10
- const [{ checkout, widgetState, checkoutLocalization }] = useCheckoutContext();
11
- return { checkoutState: checkout, widgetState, checkoutLocalization };
10
+ const [{ checkout, widgetState, checkoutLocalization, checkoutSuccess }] = useCheckoutContext();
11
+ return { checkoutState: checkout, widgetState, checkoutLocalization, checkoutSuccess };
12
12
  }
13
13
 
14
14
  function useSetWidgetReadonly() {
@@ -29,14 +29,25 @@ function useSetIsValid() {
29
29
  });
30
30
  }
31
31
 
32
+ function useSetCheckoutSuccess() {
33
+ const [, setState] = useCheckoutContext();
34
+
35
+ return (checkoutSuccess: boolean) =>
36
+ setState((draft) => {
37
+ draft.checkoutSuccess = checkoutSuccess;
38
+ });
39
+ }
40
+
32
41
  export function useCheckoutModel() {
33
- const { checkoutState, widgetState, checkoutLocalization } = useCheckoutState();
42
+ const { checkoutState, widgetState, checkoutLocalization, checkoutSuccess } = useCheckoutState();
34
43
 
35
44
  return {
36
45
  checkoutState,
37
46
  widgetState,
38
47
  checkoutLocalization,
48
+ checkoutSuccess,
39
49
  setWidgetReadOnly: useSetWidgetReadonly(),
40
50
  setIsValid: useSetIsValid(),
51
+ setCheckoutSuccess: useSetCheckoutSuccess(),
41
52
  };
42
53
  }
@@ -13,6 +13,8 @@ import {
13
13
  PaymentMethodTextContainer,
14
14
  } from './PaymentMethods.style';
15
15
  import { StripePaymentForm } from './stripe';
16
+ import { ZuoraPaymentForm } from './zuora/ZuoraPaymentForm';
17
+ import { useZuoraIntegration } from './zuora/useZuoraIntegration';
16
18
  import { CheckoutLocalization } from '../../configurations/textOverrides';
17
19
  import { CheckoutContainerProps } from '../../CheckoutContainer';
18
20
 
@@ -30,7 +32,10 @@ export type NewPaymentMethodProps = Pick<PaymentMethodLayoutProps, 'checked' | '
30
32
  onSelect: () => void;
31
33
  checkoutLocalization: CheckoutLocalization;
32
34
  hasExistingPaymentMethod: boolean;
33
- } & Pick<CheckoutContainerProps, 'onBillingAddressChange' | 'collectPhoneNumber'>;
35
+ } & Pick<
36
+ CheckoutContainerProps,
37
+ 'onBillingAddressChange' | 'collectPhoneNumber' | 'onCheckout' | 'onCheckoutCompleted'
38
+ >;
34
39
 
35
40
  function PaymentMethodLayout({ checked, text, subtitle, readOnly }: PaymentMethodLayoutProps) {
36
41
  return (
@@ -77,7 +82,11 @@ export function NewPaymentMethod({
77
82
  checkoutLocalization,
78
83
  onBillingAddressChange,
79
84
  collectPhoneNumber,
85
+ onCheckout,
86
+ onCheckoutCompleted,
80
87
  }: NewPaymentMethodProps) {
88
+ const { isZuoraIntegration } = useZuoraIntegration();
89
+
81
90
  return (
82
91
  <NewPaymentMethodContainer item $hideBorders={!hasExistingPaymentMethod} onClick={onSelect} $disabled={readOnly}>
83
92
  {hasExistingPaymentMethod && (
@@ -88,7 +97,11 @@ export function NewPaymentMethod({
88
97
  />
89
98
  )}
90
99
  <Collapse in={checked}>
91
- <StripePaymentForm onBillingAddressChange={onBillingAddressChange} collectPhoneNumber={collectPhoneNumber} />
100
+ {isZuoraIntegration ? (
101
+ <ZuoraPaymentForm onCheckout={onCheckout} onCheckoutCompleted={onCheckoutCompleted} />
102
+ ) : (
103
+ <StripePaymentForm onBillingAddressChange={onBillingAddressChange} collectPhoneNumber={collectPhoneNumber} />
104
+ )}
92
105
  </Collapse>
93
106
  </NewPaymentMethodContainer>
94
107
  );
@@ -16,7 +16,12 @@ const PaymentContainer = styled(Grid)`
16
16
  export function PaymentStep({
17
17
  onBillingAddressChange,
18
18
  collectPhoneNumber,
19
- }: Pick<CheckoutContainerProps, 'onBillingAddressChange' | 'collectPhoneNumber'>) {
19
+ onCheckout,
20
+ onCheckoutCompleted,
21
+ }: Pick<
22
+ CheckoutContainerProps,
23
+ 'onBillingAddressChange' | 'collectPhoneNumber' | 'onCheckout' | 'onCheckoutCompleted'
24
+ >) {
20
25
  const { checkoutState, checkoutLocalization, widgetState } = useCheckoutModel();
21
26
  const { customer } = checkoutState || {};
22
27
  const { errorMessage, useNewPaymentMethod, setUseNewPaymentMethod } = usePaymentStepModel();
@@ -51,6 +56,8 @@ export function PaymentStep({
51
56
  onSelect={() => handleOnSelect(true)}
52
57
  onBillingAddressChange={onBillingAddressChange}
53
58
  collectPhoneNumber={collectPhoneNumber}
59
+ onCheckout={onCheckout}
60
+ onCheckoutCompleted={onCheckoutCompleted}
54
61
  />
55
62
  </PaymentContainer>
56
63
  );
@@ -0,0 +1,128 @@
1
+ import React, { useCallback, useEffect, useRef } from 'react';
2
+ import styled from '@emotion/styled';
3
+ import Box from '@mui/material/Box';
4
+ import CircularProgress from '@mui/material/CircularProgress';
5
+ import { Currency, ZuoraCheckoutCredentialsFragment } from '@stigg/js-client-sdk';
6
+ import { useZuoraIntegration } from './useZuoraIntegration';
7
+ import { createZuoraPaymentFormConfig, ZuoraPaymentResult } from './zuora.utils';
8
+ import { useZuoraSubmit } from './useZuoraSubmit';
9
+ import { useCheckoutModel, usePaymentStepModel, usePlanStepModel } from '../../../hooks';
10
+ import { CheckoutContainerProps } from '../../../CheckoutContainer';
11
+
12
+ const Container = styled(Box)`
13
+ position: relative;
14
+ max-width: 500px;
15
+ `;
16
+
17
+ const PaymentContainer = styled(Box)<{ $showLoading: boolean }>`
18
+ opacity: ${({ $showLoading }) => ($showLoading ? 0.6 : 1)};
19
+ pointer-events: ${({ $showLoading }) => ($showLoading ? 'none' : 'auto')};
20
+ `;
21
+
22
+ const LoadingOverlay = styled(Box)`
23
+ position: absolute;
24
+ top: 40px;
25
+ left: 0;
26
+ right: 0;
27
+ bottom: 0;
28
+ background-color: rgba(255, 255, 255, 0.8);
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ border-radius: 4px;
33
+ z-index: 10;
34
+ `;
35
+
36
+ const LoadingContent = styled(Box)`
37
+ display: flex;
38
+ flex-direction: column;
39
+ align-items: center;
40
+ gap: 12px;
41
+ `;
42
+
43
+ export function ZuoraPaymentForm({
44
+ onCheckout,
45
+ onCheckoutCompleted,
46
+ }: Pick<CheckoutContainerProps, 'onCheckout' | 'onCheckoutCompleted'>) {
47
+ const { isZuoraIntegration, isInitialized, createPaymentSession } = useZuoraIntegration();
48
+ const { setErrorMessage } = usePaymentStepModel();
49
+ const { billingCountryCode } = usePlanStepModel();
50
+ const { checkoutState, widgetState } = useCheckoutModel();
51
+ const { handleZuoraSubmit } = useZuoraSubmit({ onCheckout, onCheckoutCompleted });
52
+ const hasRenderedRef = useRef(false);
53
+ const [paymentFormLoading, setPaymentFormLoading] = React.useState(false);
54
+
55
+ const currency = checkoutState?.plan?.pricePoints?.[0]?.currency || Currency.Usd;
56
+ const { publishableKey } = checkoutState?.billingIntegration.billingCredentials as ZuoraCheckoutCredentialsFragment;
57
+
58
+ const handleZuoraComplete = useCallback(
59
+ async (result: ZuoraPaymentResult) => {
60
+ if (!result.success || !result.paymentMethodId) {
61
+ const errorMessage = result.error?.message || 'Payment failed';
62
+ setErrorMessage(errorMessage);
63
+ await onCheckoutCompleted({ success: false, error: errorMessage });
64
+ return;
65
+ }
66
+
67
+ await handleZuoraSubmit(result.paymentMethodId);
68
+ },
69
+ [handleZuoraSubmit, setErrorMessage, onCheckoutCompleted],
70
+ );
71
+
72
+ useEffect(() => {
73
+ const renderZuoraForm = async () => {
74
+ if (!isInitialized || hasRenderedRef.current) {
75
+ return;
76
+ }
77
+
78
+ setPaymentFormLoading(true);
79
+ setErrorMessage(undefined);
80
+
81
+ try {
82
+ hasRenderedRef.current = true;
83
+
84
+ const zuora = window.Zuora(publishableKey);
85
+ const config = createZuoraPaymentFormConfig(
86
+ createPaymentSession,
87
+ handleZuoraComplete,
88
+ currency,
89
+ billingCountryCode,
90
+ );
91
+
92
+ const paymentForm = await zuora.createPaymentForm(config);
93
+ paymentForm.mount('#zuora_payment');
94
+ } catch (err) {
95
+ hasRenderedRef.current = false;
96
+ setErrorMessage('Failed to render Zuora payment form');
97
+ } finally {
98
+ setPaymentFormLoading(false);
99
+ }
100
+ };
101
+
102
+ void renderZuoraForm();
103
+
104
+ return () => {
105
+ hasRenderedRef.current = false;
106
+ };
107
+ // Only depend on isInitialized to prevent multiple form renders
108
+ // createPaymentSession and handleZuoraComplete are intentionally omitted
109
+ // eslint-disable-next-line react-hooks/exhaustive-deps
110
+ }, [isInitialized]);
111
+
112
+ if (!isZuoraIntegration) {
113
+ return null;
114
+ }
115
+
116
+ return (
117
+ <Container>
118
+ <PaymentContainer id="zuora_payment" $showLoading={widgetState.readOnly || paymentFormLoading} />
119
+ {(widgetState.readOnly || paymentFormLoading) && (
120
+ <LoadingOverlay>
121
+ <LoadingContent>
122
+ <CircularProgress size={28} />
123
+ </LoadingContent>
124
+ </LoadingOverlay>
125
+ )}
126
+ </Container>
127
+ );
128
+ }
@@ -0,0 +1,11 @@
1
+ export { ZuoraPaymentForm } from './ZuoraPaymentForm';
2
+ export { useZuoraIntegration } from './useZuoraIntegration';
3
+ export {
4
+ isZuoraLibraryAvailable,
5
+ loadZuoraLibrary,
6
+ createZuoraPaymentFormConfig,
7
+ ZuoraInstance,
8
+ ZuoraPaymentFormConfig,
9
+ ZuoraPaymentForm as ZuoraPaymentFormType,
10
+ ZuoraPaymentResult,
11
+ } from './zuora.utils';
@@ -0,0 +1,56 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import { useCheckoutModel, usePaymentStepModel, usePlanStepModel } from '../../../hooks';
3
+ import { isZuoraLibraryAvailable, loadZuoraLibrary } from './zuora.utils';
4
+ import { BillingVendorIdentifier, useStiggContext } from '../../../../..';
5
+
6
+ export function useZuoraIntegration() {
7
+ const { stigg } = useStiggContext();
8
+ const { setErrorMessage } = usePaymentStepModel();
9
+ const { billingCountryCode } = usePlanStepModel();
10
+ const [isInitialized, setIsInitialized] = useState(false);
11
+ const { checkoutState } = useCheckoutModel();
12
+ const { billingIntegration } = checkoutState || {};
13
+
14
+ const createPaymentSession = useCallback(async () => {
15
+ if (!checkoutState?.customer.id || !checkoutState?.plan.id) {
16
+ throw new Error('Customer and plan are required');
17
+ }
18
+
19
+ const { token } = await stigg.createPaymentSession({
20
+ planId: checkoutState.plan.id,
21
+ billingCountryCode,
22
+ });
23
+
24
+ return token;
25
+ }, [checkoutState, stigg, billingCountryCode]);
26
+
27
+ const initializeZuora = useCallback(() => {
28
+ try {
29
+ if (!isZuoraLibraryAvailable()) {
30
+ loadZuoraLibrary();
31
+ }
32
+
33
+ setIsInitialized(true);
34
+ } catch (err) {
35
+ setErrorMessage('Failed to initialize Zuora');
36
+ }
37
+ }, [setIsInitialized, setErrorMessage]);
38
+
39
+ useEffect(() => {
40
+ if (billingIntegration) {
41
+ const { billingIdentifier } = billingIntegration;
42
+
43
+ if (billingIdentifier === BillingVendorIdentifier.Zuora) {
44
+ void initializeZuora();
45
+ }
46
+ }
47
+ }, [billingIntegration, initializeZuora]);
48
+
49
+ const isZuoraIntegration = billingIntegration?.billingIdentifier === BillingVendorIdentifier.Zuora;
50
+
51
+ return {
52
+ isZuoraIntegration,
53
+ isInitialized,
54
+ createPaymentSession,
55
+ };
56
+ }
@@ -0,0 +1,125 @@
1
+ import { useCallback } from 'react';
2
+ import { ApplySubscription } from '@stigg/js-client-sdk';
3
+ import { useStiggContext } from '../../../../..';
4
+ import { useSubscriptionState } from '../../../hooks/useSubscriptionState';
5
+ import { useCheckoutModel, usePaymentStepModel } from '../../../hooks';
6
+ import { CheckoutContainerProps } from '../../../CheckoutContainer';
7
+ import { ANIMATION_DURATION } from '../../../summary/CheckoutSuccess';
8
+
9
+ const delay = (ms: number) => new Promise((res) => setTimeout(res, ms));
10
+
11
+ export type ZuoraSubmitResult = {
12
+ success: boolean;
13
+ errorMessage?: string;
14
+ };
15
+
16
+ export type UseZuoraSubmitProps = {
17
+ onSuccess?: () => void;
18
+ } & Pick<CheckoutContainerProps, 'onCheckout' | 'onCheckoutCompleted' | 'disableSuccessAnimation'>;
19
+
20
+ export function useZuoraSubmit({
21
+ onCheckout,
22
+ onCheckoutCompleted,
23
+ onSuccess,
24
+ disableSuccessAnimation,
25
+ }: UseZuoraSubmitProps) {
26
+ const { stigg } = useStiggContext();
27
+ const subscriptionState = useSubscriptionState();
28
+ const { checkoutState, setWidgetReadOnly, setCheckoutSuccess } = useCheckoutModel();
29
+ const { setErrorMessage } = usePaymentStepModel();
30
+
31
+ const handleZuoraSubmit = useCallback(
32
+ async (paymentMethodId?: string): Promise<ZuoraSubmitResult> => {
33
+ setWidgetReadOnly(true);
34
+
35
+ let success = true;
36
+ let errorMessage: string | undefined;
37
+
38
+ const currentSubscriptionState = subscriptionState;
39
+ if (!currentSubscriptionState) {
40
+ success = false;
41
+ errorMessage = 'Unexpected error, please contact support.';
42
+ } else {
43
+ const checkoutParams: ApplySubscription = {
44
+ ...currentSubscriptionState,
45
+ paymentMethodId,
46
+ };
47
+
48
+ const checkoutAction = async (params: ApplySubscription) => {
49
+ try {
50
+ const applySubscriptionResults = await stigg.applySubscription(params);
51
+ return {
52
+ success: true,
53
+ results: applySubscriptionResults,
54
+ };
55
+ } catch (e) {
56
+ console.error(e);
57
+ return {
58
+ success: false,
59
+ errorMessage: (e as any)?.message || 'Failed to apply subscription',
60
+ };
61
+ }
62
+ };
63
+
64
+ try {
65
+ if (onCheckout) {
66
+ const externalCheckoutResults = await onCheckout({
67
+ customerId: checkoutState!.customer.id,
68
+ checkoutParams,
69
+ checkoutAction,
70
+ });
71
+
72
+ if (externalCheckoutResults.errorMessage) {
73
+ errorMessage = externalCheckoutResults.errorMessage;
74
+ }
75
+
76
+ success = success && externalCheckoutResults.success && !errorMessage;
77
+ } else {
78
+ const checkoutActionResults = await checkoutAction(checkoutParams);
79
+ if (!checkoutActionResults.success && checkoutActionResults.errorMessage) {
80
+ errorMessage = checkoutActionResults.errorMessage;
81
+ }
82
+ success = checkoutActionResults.success && !errorMessage;
83
+ }
84
+ } catch (err) {
85
+ success = false;
86
+ errorMessage = (err as Error).message || 'Failed to process checkout';
87
+ }
88
+ }
89
+
90
+ if (success) {
91
+ setCheckoutSuccess(true);
92
+
93
+ if (onSuccess) {
94
+ onSuccess();
95
+
96
+ if (!disableSuccessAnimation) {
97
+ await delay(ANIMATION_DURATION);
98
+ }
99
+ }
100
+ } else {
101
+ setErrorMessage(errorMessage);
102
+ }
103
+
104
+ setWidgetReadOnly(false);
105
+
106
+ await onCheckoutCompleted({ success, error: errorMessage });
107
+
108
+ return { success, errorMessage };
109
+ },
110
+ [
111
+ stigg,
112
+ subscriptionState,
113
+ checkoutState,
114
+ onCheckout,
115
+ onCheckoutCompleted,
116
+ onSuccess,
117
+ disableSuccessAnimation,
118
+ setWidgetReadOnly,
119
+ setCheckoutSuccess,
120
+ setErrorMessage,
121
+ ],
122
+ );
123
+
124
+ return { handleZuoraSubmit };
125
+ }
@@ -0,0 +1,72 @@
1
+ import { Currency } from '@stigg/js-client-sdk';
2
+
3
+ // Zuora Payment Form v3 API types
4
+ declare global {
5
+ interface Window {
6
+ Zuora: (publishableKey: string) => ZuoraInstance;
7
+ }
8
+ }
9
+
10
+ export type ZuoraInstance = {
11
+ createPaymentForm: (config: ZuoraPaymentFormConfig) => Promise<ZuoraPaymentForm>;
12
+ };
13
+
14
+ export type ZuoraPaymentFormConfig = {
15
+ locale?: string;
16
+ region?: string;
17
+ currency?: string;
18
+ amount?: string;
19
+ profile?: string;
20
+ createPaymentSession: () => Promise<string>;
21
+ onComplete: (result: ZuoraPaymentResult) => Promise<void>;
22
+ };
23
+
24
+ export type ZuoraPaymentForm = {
25
+ mount: (containerId: string) => void;
26
+ };
27
+
28
+ export type ZuoraPaymentResult = {
29
+ success: boolean;
30
+ paymentMethodId?: string;
31
+ paymentId?: string;
32
+ error?: {
33
+ type: string;
34
+ code: string;
35
+ message: string;
36
+ };
37
+ };
38
+
39
+ export function isZuoraLibraryAvailable(): boolean {
40
+ return !!window.Zuora;
41
+ }
42
+
43
+ export function loadZuoraLibrary() {
44
+ const script = document.createElement('script');
45
+ script.type = 'text/javascript';
46
+ script.src = 'https://js.zuora.com/payment/v3/zuora.js';
47
+ script.async = true;
48
+
49
+ script.onerror = () => {
50
+ throw new Error('Failed to load Zuora library');
51
+ };
52
+
53
+ document.head.appendChild(script);
54
+ }
55
+
56
+ export function createZuoraPaymentFormConfig(
57
+ createPaymentSession: () => Promise<string>,
58
+ onComplete: (result: ZuoraPaymentResult) => Promise<void>,
59
+ currency: Currency,
60
+ billingCountryCode?: string,
61
+ ): ZuoraPaymentFormConfig {
62
+ const AUTH_AMOUNT = '1';
63
+
64
+ return {
65
+ locale: billingCountryCode || 'en',
66
+ region: billingCountryCode?.toUpperCase() || 'US',
67
+ currency,
68
+ amount: AUTH_AMOUNT,
69
+ createPaymentSession,
70
+ onComplete,
71
+ };
72
+ }