@stigg/react-sdk 4.4.0-beta.3 → 4.4.0-beta.5

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 (59) hide show
  1. package/dist/components/checkout/Checkout.d.ts +1 -1
  2. package/dist/components/checkout/CheckoutContainer.d.ts +6 -2
  3. package/dist/components/checkout/CheckoutProvider.d.ts +3 -1
  4. package/dist/components/checkout/components/DowngradeToFreeContainer.d.ts +28 -0
  5. package/dist/components/checkout/hooks/usePaymentStepModel.d.ts +8 -2
  6. package/dist/components/checkout/hooks/usePreviewSubscription.d.ts +3 -0
  7. package/dist/components/checkout/hooks/useProgressBarModel.d.ts +2 -0
  8. package/dist/components/checkout/hooks/useSubscriptionModel.d.ts +2 -1
  9. package/dist/components/checkout/index.d.ts +1 -0
  10. package/dist/components/checkout/steps/payment/PaymentMethods.d.ts +3 -2
  11. package/dist/components/checkout/steps/payment/PaymentStep.d.ts +2 -1
  12. package/dist/components/checkout/steps/payment/stripe/StripePaymentForm.d.ts +2 -1
  13. package/dist/components/checkout/steps/payment/stripe/stripe.utils.d.ts +4 -0
  14. package/dist/components/checkout/steps/payment/stripe/useSubmit.d.ts +2 -1
  15. package/dist/components/checkout/steps/plan/CheckoutChargeList.d.ts +6 -1
  16. package/dist/components/checkout/summary/CheckoutSummary.d.ts +3 -1
  17. package/dist/components/checkout/summary/components/LineItems.d.ts +2 -2
  18. package/dist/components/checkout/textOverrides.d.ts +4 -1
  19. package/dist/components/checkout/types.d.ts +7 -0
  20. package/dist/components/paywall/paywallTextOverrides.d.ts +4 -0
  21. package/dist/components/utils/getPaidPriceText.d.ts +3 -1
  22. package/dist/react-sdk.cjs.development.js +607 -271
  23. package/dist/react-sdk.cjs.development.js.map +1 -1
  24. package/dist/react-sdk.cjs.production.min.js +1 -1
  25. package/dist/react-sdk.cjs.production.min.js.map +1 -1
  26. package/dist/react-sdk.esm.js +619 -271
  27. package/dist/react-sdk.esm.js.map +1 -1
  28. package/package.json +2 -2
  29. package/src/assets/payment-method.svg +3 -10
  30. package/src/components/checkout/Checkout.tsx +3 -1
  31. package/src/components/checkout/CheckoutContainer.tsx +48 -22
  32. package/src/components/checkout/CheckoutProvider.tsx +8 -4
  33. package/src/components/checkout/components/DowngradeToFreeContainer.tsx +98 -0
  34. package/src/components/checkout/hooks/usePaymentStepModel.ts +22 -3
  35. package/src/components/checkout/hooks/usePlanStepModel.ts +5 -5
  36. package/src/components/checkout/hooks/usePreviewSubscription.ts +34 -4
  37. package/src/components/checkout/hooks/useProgressBarModel.ts +15 -0
  38. package/src/components/checkout/hooks/useSubscriptionModel.ts +8 -2
  39. package/src/components/checkout/hooks/useSubscriptionState.ts +2 -1
  40. package/src/components/checkout/index.ts +1 -0
  41. package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +3 -2
  42. package/src/components/checkout/steps/payment/PaymentMethods.tsx +13 -6
  43. package/src/components/checkout/steps/payment/PaymentStep.tsx +3 -1
  44. package/src/components/checkout/steps/payment/stripe/StripePaymentForm.tsx +35 -4
  45. package/src/components/checkout/steps/payment/stripe/stripe.utils.ts +4 -3
  46. package/src/components/checkout/steps/payment/stripe/useSubmit.ts +54 -45
  47. package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +41 -10
  48. package/src/components/checkout/summary/CheckoutSuccess.tsx +1 -1
  49. package/src/components/checkout/summary/CheckoutSummary.tsx +24 -19
  50. package/src/components/checkout/summary/components/LineItems.tsx +8 -16
  51. package/src/components/checkout/textOverrides.ts +5 -4
  52. package/src/components/checkout/types.ts +9 -0
  53. package/src/components/paywall/PlanPrice.tsx +10 -2
  54. package/src/components/paywall/paywallTextOverrides.ts +3 -0
  55. package/src/components/utils/getPaidPriceText.ts +8 -2
  56. package/src/components/utils/getPlanPrice.ts +1 -1
  57. package/dist/components/checkout/steps/surprise/SurpriseStep.d.ts +0 -2
  58. package/src/assets/nyancat.svg +0 -634
  59. package/src/components/checkout/steps/surprise/SurpriseStep.tsx +0 -27
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.4.0-beta.3",
2
+ "version": "4.4.0-beta.5",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -101,7 +101,7 @@
101
101
  "@emotion/react": "^11.10.5",
102
102
  "@emotion/styled": "^11.10.5",
103
103
  "@mui/material": "^5.10.13",
104
- "@stigg/js-client-sdk": "2.21.0",
104
+ "@stigg/js-client-sdk": "2.22.0",
105
105
  "@stripe/react-stripe-js": "^2.1.1",
106
106
  "@stripe/stripe-js": "^1.54.1",
107
107
  "@types/styled-components": "^5.1.26",
@@ -1,11 +1,4 @@
1
- <svg width="24" height="16" viewBox="0 0 24 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
- <g clip-path="url(#clip0_239_13697)">
3
- <path d="M22 0H2C0.89543 0 0 0.89543 0 2V14C0 15.1046 0.89543 16 2 16H22C23.1046 16 24 15.1046 24 14V2C24 0.89543 23.1046 0 22 0Z" fill="#C6C6C7"/>
4
- </g>
5
- <path opacity="0.3" d="M18.75 12.4286H16.5C16.0125 12.4286 15.75 12.2 15.75 11.6667C15.75 11.1334 16.0125 10.9048 16.5 10.9048H18.75C19.2375 10.9048 19.5 11.1334 19.5 11.6667C19.5 12.2 19.2375 12.4286 18.75 12.4286ZM14.25 12.4286H12C11.5125 12.4286 11.25 12.2 11.25 11.6667C11.25 11.1334 11.5125 10.9048 12 10.9048H14.25C14.7375 10.9048 15 11.1334 15 11.6667C15 12.2 14.7375 12.4286 14.25 12.4286ZM9.75 12.4286H7.5C7.0125 12.4286 6.75 12.2 6.75 11.6667C6.75 11.1334 7.0125 10.9048 7.5 10.9048H9.75C10.2375 10.9048 10.5 11.1334 10.5 11.6667C10.5 12.2 10.2375 12.4286 9.75 12.4286ZM5.25 12.4286H3C2.5125 12.4286 2.25 12.2 2.25 11.6667C2.25 11.1334 2.5125 10.9048 3 10.9048H5.25C5.7375 10.9048 6 11.1334 6 11.6667C6 12.2 5.7375 12.4286 5.25 12.4286Z" fill="black"/>
6
- <defs>
7
- <clipPath id="clip0_239_13697">
8
- <rect width="24" height="16" fill="white"/>
9
- </clipPath>
10
- </defs>
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M21.1667 4H2.83333C1.82081 4 1 4.89543 1 6V18C1 19.1046 1.82081 20 2.83333 20H21.1667C22.1792 20 23 19.1046 23 18V6C23 4.89543 22.1792 4 21.1667 4Z" fill="#C6C6C7"/>
3
+ <path opacity="0.3" d="M19.75 16.4286H17.5C17.0125 16.4286 16.75 16.2 16.75 15.6667C16.75 15.1334 17.0125 14.9048 17.5 14.9048H19.75C20.2375 14.9048 20.5 15.1334 20.5 15.6667C20.5 16.2 20.2375 16.4286 19.75 16.4286ZM15.25 16.4286H13C12.5125 16.4286 12.25 16.2 12.25 15.6667C12.25 15.1334 12.5125 14.9048 13 14.9048H15.25C15.7375 14.9048 16 15.1334 16 15.6667C16 16.2 15.7375 16.4286 15.25 16.4286ZM10.75 16.4286H8.5C8.0125 16.4286 7.75 16.2 7.75 15.6667C7.75 15.1334 8.0125 14.9048 8.5 14.9048H10.75C11.2375 14.9048 11.5 15.1334 11.5 15.6667C11.5 16.2 11.2375 16.4286 10.75 16.4286ZM6.25 16.4286H4C3.5125 16.4286 3.25 16.2 3.25 15.6667C3.25 15.1334 3.5125 14.9048 4 14.9048H6.25C6.7375 14.9048 7 15.1334 7 15.6667C7 16.2 6.7375 16.4286 6.25 16.4286Z" fill="black"/>
11
4
  </svg>
@@ -12,6 +12,7 @@ export const Checkout = ({
12
12
  preferredBillingPeriod,
13
13
  billingCountryCode,
14
14
  billableFeatures,
15
+ billingInformation,
15
16
  ...containerProps
16
17
  }: CheckoutProps) => {
17
18
  return (
@@ -22,7 +23,8 @@ export const Checkout = ({
22
23
  planId={planId}
23
24
  preferredBillingPeriod={preferredBillingPeriod}
24
25
  billingCountryCode={billingCountryCode}
25
- billableFeatures={billableFeatures}>
26
+ billableFeatures={billableFeatures}
27
+ billingInformation={billingInformation}>
26
28
  <CheckoutContainer {...containerProps} />
27
29
  </CheckoutProvider>
28
30
  );
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
2
  import { Elements } from '@stripe/react-stripe-js';
3
- import { ApplySubscription, CheckoutStatePlan } from '@stigg/js-client-sdk';
3
+ import { ApplySubscription, BillingAddress, CheckoutStatePlan, PricingType } from '@stigg/js-client-sdk';
4
4
  import { CheckoutContent, CheckoutLayout, CheckoutPanel } from './CheckoutContainer.style';
5
5
  import { CheckoutProgressBar } from './progressBar/CheckoutProgressBar';
6
6
  import { CheckoutSummary, CheckoutSummarySkeleton } from './summary';
7
- import { CheckoutStep, CheckoutStepKey, useProgressBarModel } from './hooks';
7
+ import { CheckoutStep, CheckoutStepKey, useCheckoutModel, useProgressBarModel } from './hooks';
8
8
  import { PlanHeader } from './planHeader';
9
9
  import { CheckoutAddonsStep } from './steps/addons';
10
10
  import { PaymentStep } from './steps/payment';
@@ -12,22 +12,24 @@ import { useStripeIntegration } from './steps/payment/stripe';
12
12
  import { CheckoutPlanStep } from './steps/plan';
13
13
  import { useCheckoutContext } from './CheckoutProvider';
14
14
  import { ContentLoadingSkeleton } from './components';
15
-
16
- // import { SurpriseStep } from './steps/surprise/SurpriseStep';
15
+ import { DowngradeToFreePlan } from './components/DowngradeToFreeContainer';
17
16
 
18
17
  type StepProps = {
19
18
  allowChangePlan?: boolean;
20
19
  content: React.ReactNode;
21
20
  };
22
21
 
23
- const getStepProps = (currentStep: CheckoutStep): StepProps => {
22
+ const getStepProps = (
23
+ currentStep: CheckoutStep,
24
+ { onBillingAddressChange }: Pick<CheckoutContainerProps, 'onBillingAddressChange'>,
25
+ ): StepProps => {
24
26
  switch (currentStep.key) {
25
27
  case CheckoutStepKey.PLAN:
26
28
  return { allowChangePlan: true, content: <CheckoutPlanStep /> };
27
29
  case CheckoutStepKey.ADDONS:
28
30
  return { content: <CheckoutAddonsStep /> };
29
31
  case CheckoutStepKey.PAYMENT:
30
- return { content: <PaymentStep /> };
32
+ return { content: <PaymentStep onBillingAddressChange={onBillingAddressChange} /> };
31
33
  default:
32
34
  return { content: null };
33
35
  }
@@ -43,26 +45,47 @@ export type CheckoutContainerProps = {
43
45
  onCheckout?: (params: OnCheckoutParams) => Promise<CheckoutResult>;
44
46
  onCheckoutCompleted: (params: OnCheckoutCompletedParams) => Promise<void>;
45
47
  onChangePlan?: (params: { currentPlan: CheckoutStatePlan | undefined }) => void;
48
+ onBillingAddressChange?: (params: { billingAddress: BillingAddress }) => Promise<void>;
49
+ disablePromotionCode?: boolean;
46
50
  };
47
51
 
48
- export function CheckoutContainer({ onCheckout, onCheckoutCompleted, onChangePlan }: CheckoutContainerProps) {
52
+ export function CheckoutContainer({
53
+ onCheckout,
54
+ onCheckoutCompleted,
55
+ onChangePlan,
56
+ onBillingAddressChange,
57
+ disablePromotionCode,
58
+ }: CheckoutContainerProps) {
49
59
  const { stripePromise, setupIntentClientSecret } = useStripeIntegration();
50
60
  const [{ stiggTheme, widgetState }] = useCheckoutContext();
51
61
  const { currentStep } = useProgressBarModel();
52
62
 
53
63
  const { isLoadingCheckoutData } = widgetState;
54
64
 
55
- // uncomment for fun!
56
- // if (activeStep > 2) {
57
- // return <SurpriseStep />;
58
- // }
65
+ const { checkoutState, checkoutLocalization } = useCheckoutModel();
66
+ const { plan, activeSubscription } = checkoutState || {};
67
+ const isFreeDowngrade =
68
+ !!plan &&
69
+ plan.pricingType === PricingType.Free &&
70
+ !!activeSubscription &&
71
+ activeSubscription.pricingType !== PricingType.Free;
59
72
 
60
- const { content, allowChangePlan } = getStepProps(currentStep);
73
+ const { content, allowChangePlan } = getStepProps(currentStep, { onBillingAddressChange });
61
74
 
62
75
  const checkoutContent = (
63
76
  <>
64
- <PlanHeader allowChangePlan={allowChangePlan} onChangePlan={onChangePlan} />
65
- {content}
77
+ {isFreeDowngrade ? (
78
+ <DowngradeToFreePlan
79
+ checkoutLocalization={checkoutLocalization}
80
+ freePlan={plan!}
81
+ activeSubscription={activeSubscription!}
82
+ />
83
+ ) : (
84
+ <>
85
+ <PlanHeader allowChangePlan={allowChangePlan} onChangePlan={onChangePlan} />
86
+ {content}
87
+ </>
88
+ )}
66
89
  </>
67
90
  );
68
91
 
@@ -82,15 +105,18 @@ export function CheckoutContainer({ onCheckout, onCheckoutCompleted, onChangePla
82
105
  },
83
106
  }}>
84
107
  <CheckoutLayout className="stigg-checkout-layout">
85
- <CheckoutProgressBar />
108
+ {!isFreeDowngrade && <CheckoutProgressBar />}
86
109
  <CheckoutContent>
87
- <CheckoutPanel>
88
- {isLoadingCheckoutData && <ContentLoadingSkeleton />}
89
- {!isLoadingCheckoutData && checkoutContent}
90
- </CheckoutPanel>
91
- {isLoadingCheckoutData && <CheckoutSummarySkeleton />}
92
- {!isLoadingCheckoutData && (
93
- <CheckoutSummary onCheckout={onCheckout} onCheckoutCompleted={onCheckoutCompleted} />
110
+ <CheckoutPanel>{isLoadingCheckoutData ? <ContentLoadingSkeleton /> : checkoutContent}</CheckoutPanel>
111
+ {isLoadingCheckoutData ? (
112
+ <CheckoutSummarySkeleton />
113
+ ) : (
114
+ <CheckoutSummary
115
+ disablePromotionCode={disablePromotionCode}
116
+ onCheckout={onCheckout}
117
+ onCheckoutCompleted={onCheckoutCompleted}
118
+ isFreeDowngrade={isFreeDowngrade}
119
+ />
94
120
  )}
95
121
  </CheckoutContent>
96
122
  </CheckoutLayout>
@@ -1,8 +1,6 @@
1
1
  import { produce } from 'immer';
2
2
  import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
3
-
4
3
  import { BillableFeature, BillingPeriod, GetCheckoutStateResults } from '@stigg/js-client-sdk';
5
-
6
4
  import { CustomizedTheme, SdkThemeProvider, useStiggTheme } from '../../theme/Theme';
7
5
  import { DeepPartial } from '../../types';
8
6
  import { mapCheckoutConfiguration } from '../common/mapExternalTheme';
@@ -21,6 +19,7 @@ import {
21
19
  import { CheckoutLocalization, getResolvedCheckoutLocalize } from './textOverrides';
22
20
  import { CheckoutTheme, getResolvedCheckoutTheme } from './theme';
23
21
  import { StiggTheme } from '../../theme/types';
22
+ import { BillingInformation } from './types';
24
23
 
25
24
  export interface CheckoutContextState {
26
25
  checkout?: GetCheckoutStateResults | null;
@@ -80,6 +79,7 @@ export type CheckoutProviderProps = {
80
79
  preferredBillingPeriod?: BillingPeriod;
81
80
  billingCountryCode?: string;
82
81
  billableFeatures?: BillableFeature[];
82
+ billingInformation?: BillingInformation;
83
83
  };
84
84
 
85
85
  export function CheckoutProvider({
@@ -91,6 +91,7 @@ export function CheckoutProvider({
91
91
  resourceId,
92
92
  planId,
93
93
  billingCountryCode,
94
+ billingInformation,
94
95
  }: {
95
96
  children: React.ReactNode;
96
97
  } & CheckoutProviderProps) {
@@ -115,7 +116,10 @@ export function CheckoutProvider({
115
116
  billingPeriod: planStep.billingPeriod,
116
117
  activeSubscription: checkout?.activeSubscription,
117
118
  });
118
- const paymentStep = getPaymentStepInitialState({ customer: checkout?.customer });
119
+ const paymentStep = getPaymentStepInitialState({
120
+ customer: checkout?.customer,
121
+ taxPercentage: billingInformation?.taxDetails?.taxPercentage,
122
+ });
119
123
  const progressBar = getProgressBarInitialState({
120
124
  availableAddons: isLoading ? undefined : addonsStep.availableAddons,
121
125
  });
@@ -135,7 +139,7 @@ export function CheckoutProvider({
135
139
 
136
140
  return initialState;
137
141
  // eslint-disable-next-line react-hooks/exhaustive-deps
138
- }, [preferredBillingPeriod, billingCountryCode, checkout, isLoading]);
142
+ }, [preferredBillingPeriod, billingCountryCode, checkout, isLoading, billingInformation?.taxDetails?.taxPercentage]);
139
143
 
140
144
  return (
141
145
  <SdkThemeProvider key={checkout?.plan.id} componentTheme={configuration}>
@@ -0,0 +1,98 @@
1
+ import React from 'react';
2
+ import { StyledArrowRightIcon } from '../planHeader/PlanHeader.style';
3
+ import styled from '@emotion/styled/macro';
4
+ import { Alert, Box } from '@mui/material';
5
+ import { Typography } from '../../common/Typography';
6
+ import { CheckoutStatePlan, Subscription } from '@stigg/js-client-sdk';
7
+ import { Currency, BillingPeriod } from '@stigg/js-client-sdk';
8
+ import { currencyPriceFormatter } from '../../utils/currencyUtils';
9
+ import { CheckoutLocalization } from '../textOverrides';
10
+
11
+ const DowngradeToFreePlansContainer = styled(Box)`
12
+ display: flex;
13
+ `;
14
+
15
+ const DowngradeToFreeAlert = styled(Alert)`
16
+ margin-bottom: 16px;
17
+ `;
18
+
19
+ export const DowngradeToFreePlanBox = styled(Box)`
20
+ padding: 16px;
21
+ border-radius: 10px;
22
+ width: 100%;
23
+ border: ${({ theme }) => `1px solid ${theme.stigg.palette.outlinedBorder}`};
24
+ `;
25
+
26
+ export const DowngradeToFreeContent = ({
27
+ planName,
28
+ totalPrice,
29
+ billingPeriod,
30
+ }: {
31
+ planName: string;
32
+ totalPrice?: {
33
+ amount: number;
34
+ currency: Currency;
35
+ };
36
+ billingPeriod?: BillingPeriod;
37
+ }) => {
38
+ const priceText = totalPrice
39
+ ? currencyPriceFormatter({ amount: totalPrice.amount, currency: totalPrice.currency })
40
+ : 'Free';
41
+
42
+ const billingPeriodText = billingPeriod ? ` / Paid ${billingPeriod.toLowerCase()}` : '';
43
+ return (
44
+ <DowngradeToFreePlanBox className="stigg-checkout-free-downgrade-plan-box">
45
+ <Typography className="stigg-checkout-downgrade-to-free-text-plan" color="secondary">
46
+ {planName}
47
+ </Typography>
48
+ <div>
49
+ <Typography
50
+ className="stigg-checkout-downgrade-to-free-text-price"
51
+ span
52
+ bold={true}
53
+ variant="h3"
54
+ color="primary">
55
+ {priceText}
56
+ </Typography>
57
+ <Typography className="stigg-checkout-downgrade-to-free-text-billing-period" span color="secondary">
58
+ {billingPeriodText}
59
+ </Typography>
60
+ </div>
61
+ </DowngradeToFreePlanBox>
62
+ );
63
+ };
64
+
65
+ export const DowngradeToFreePlan = ({
66
+ checkoutLocalization,
67
+ activeSubscription,
68
+ freePlan,
69
+ }: {
70
+ checkoutLocalization: CheckoutLocalization;
71
+ activeSubscription: Subscription;
72
+ freePlan: CheckoutStatePlan;
73
+ }) => {
74
+ return (
75
+ <>
76
+ <DowngradeToFreeAlert className="stigg-checkout-downgrade-to-free-alert" severity="info">
77
+ <Typography color="secondary">
78
+ {checkoutLocalization.downgradeToFreeAlertText({ plan: activeSubscription.plan })}
79
+ </Typography>
80
+ </DowngradeToFreeAlert>
81
+
82
+ <DowngradeToFreePlansContainer className="stigg-checkout-downgrade-to-free-plans-container">
83
+ <DowngradeToFreeContent
84
+ planName={activeSubscription.plan.displayName}
85
+ totalPrice={activeSubscription.totalPrice?.total}
86
+ billingPeriod={activeSubscription.prices[0].billingPeriod}
87
+ />
88
+
89
+ <StyledArrowRightIcon
90
+ className="stigg-checkout-downgrade-to-free-arrow"
91
+ style={{ margin: 'auto 16px', minWidth: '16px' }}
92
+ />
93
+
94
+ <DowngradeToFreeContent planName={freePlan.displayName} />
95
+ </DowngradeToFreePlansContainer>
96
+ </>
97
+ );
98
+ };
@@ -1,18 +1,27 @@
1
- import { Customer } from '@stigg/js-client-sdk';
1
+ import { BillingAddress, Customer } from '@stigg/js-client-sdk';
2
2
 
3
3
  import { useCheckoutContext } from '../CheckoutProvider';
4
4
 
5
5
  export type PaymentStepState = {
6
6
  useNewPaymentMethod: boolean;
7
7
  errorMessage?: string;
8
+ billingAddress?: BillingAddress;
9
+ taxPercentage?: number;
8
10
  };
9
11
 
10
12
  type GetPaymentStepInitialStateProps = {
11
13
  customer?: Customer;
14
+ taxPercentage?: number;
12
15
  };
13
16
 
14
- export function getPaymentStepInitialState({ customer }: GetPaymentStepInitialStateProps): PaymentStepState {
15
- return { useNewPaymentMethod: !customer?.paymentMethodDetails };
17
+ export function getPaymentStepInitialState({
18
+ customer,
19
+ taxPercentage,
20
+ }: GetPaymentStepInitialStateProps): PaymentStepState {
21
+ return {
22
+ useNewPaymentMethod: !customer?.paymentMethodDetails,
23
+ taxPercentage,
24
+ };
16
25
  }
17
26
 
18
27
  function useSetUseNewPaymentMethod() {
@@ -33,6 +42,15 @@ function useSetErrorMessage() {
33
42
  });
34
43
  }
35
44
 
45
+ function useSetBillingAddress() {
46
+ const [, setState] = useCheckoutContext();
47
+
48
+ return (billingAddress?: BillingAddress) =>
49
+ setState((draft) => {
50
+ draft.paymentStep.billingAddress = billingAddress;
51
+ });
52
+ }
53
+
36
54
  function usePaymentState() {
37
55
  const [{ paymentStep }] = useCheckoutContext();
38
56
  return paymentStep;
@@ -45,5 +63,6 @@ export function usePaymentStepModel() {
45
63
  ...state,
46
64
  setUseNewPaymentMethod: useSetUseNewPaymentMethod(),
47
65
  setErrorMessage: useSetErrorMessage(),
66
+ setBillingAddress: useSetBillingAddress(),
48
67
  };
49
68
  }
@@ -88,11 +88,6 @@ function resolveBillingPeriod({
88
88
  const hasMonthlyPrices = plan?.pricePoints.some((pricePoint) => pricePoint.billingPeriod === BillingPeriod.Monthly);
89
89
  const hasAnnualPrices = plan?.pricePoints.some((pricePoint) => pricePoint.billingPeriod === BillingPeriod.Annually);
90
90
 
91
- const isUpdate = activeSubscription?.plan?.id === plan?.id;
92
- if (isUpdate) {
93
- return activeSubscription?.price?.billingPeriod || BillingPeriod.Monthly;
94
- }
95
-
96
91
  if (preferredBillingPeriod) {
97
92
  if (preferredBillingPeriod === BillingPeriod.Monthly && hasMonthlyPrices) {
98
93
  return BillingPeriod.Monthly;
@@ -102,6 +97,11 @@ function resolveBillingPeriod({
102
97
  }
103
98
  }
104
99
 
100
+ const isUpdate = activeSubscription?.plan?.id === plan?.id;
101
+ if (isUpdate) {
102
+ return activeSubscription?.price?.billingPeriod || BillingPeriod.Monthly;
103
+ }
104
+
105
105
  if (activeSubscription?.price?.billingPeriod) {
106
106
  return activeSubscription?.price?.billingPeriod;
107
107
  }
@@ -1,10 +1,29 @@
1
1
  import { useCallback, useEffect, useState } from 'react';
2
- import { PreviewSubscription, SubscriptionPreview } from '@stigg/js-client-sdk';
2
+ import { BillingAddress, PreviewSubscription, SubscriptionPreview } from '@stigg/js-client-sdk';
3
3
  import { useStiggContext } from '../../StiggProvider';
4
4
  import { useCheckoutContext } from '../CheckoutProvider';
5
5
  import { useCheckoutModel } from './useCheckoutModel';
6
6
  import { useSubscriptionModel } from './useSubscriptionModel';
7
7
 
8
+ function mapBillingInformation({
9
+ billingAddress,
10
+ taxPercentage,
11
+ }: {
12
+ billingAddress?: BillingAddress;
13
+ taxPercentage?: number;
14
+ }): Pick<PreviewSubscription, 'billingInformation'> {
15
+ if (!billingAddress && !taxPercentage) {
16
+ return {};
17
+ }
18
+
19
+ return {
20
+ billingInformation: {
21
+ ...(billingAddress ? { billingAddress } : {}),
22
+ ...(taxPercentage ? { taxPercentage } : {}),
23
+ },
24
+ };
25
+ }
26
+
8
27
  export const usePreviewSubscriptionAction = () => {
9
28
  const { stigg } = useStiggContext();
10
29
  const subscription = useSubscriptionModel();
@@ -18,6 +37,11 @@ export const usePreviewSubscriptionAction = () => {
18
37
  let subscriptionPreview: SubscriptionPreview | null = null;
19
38
  let errorMessage: string | null = null;
20
39
 
40
+ const isValid = !subscription.billableFeatures.some(({ quantity }) => quantity === null);
41
+ if (!isValid) {
42
+ return { subscriptionPreview };
43
+ }
44
+
21
45
  try {
22
46
  if (customer?.id && plan?.id) {
23
47
  const previewSubscriptionProps: PreviewSubscription = {
@@ -29,8 +53,10 @@ export const usePreviewSubscriptionAction = () => {
29
53
  promotionCode: promotionCode ?? subscription.promotionCode,
30
54
  resourceId,
31
55
  billingCountryCode: planStep.billingCountryCode,
32
- // TODO: Add billing information
33
- // billingInformation, // TaxId / Tax percentage
56
+ ...mapBillingInformation({
57
+ billingAddress: subscription.billingAddress,
58
+ taxPercentage: subscription.taxPercentage,
59
+ }),
34
60
  };
35
61
  subscriptionPreview = await stigg.previewSubscription(previewSubscriptionProps);
36
62
  }
@@ -51,6 +77,8 @@ export const usePreviewSubscriptionAction = () => {
51
77
  subscription.billingPeriod,
52
78
  subscription.billableFeatures,
53
79
  subscription.promotionCode,
80
+ subscription.billingAddress,
81
+ subscription.taxPercentage,
54
82
  planStep.billingCountryCode,
55
83
  ],
56
84
  );
@@ -69,7 +97,9 @@ export const usePreviewSubscription = () => {
69
97
  setIsFetchingSubscriptionPreview(true);
70
98
 
71
99
  const { subscriptionPreview } = await previewSubscriptionAction();
72
- setSubscriptionPreview(subscriptionPreview);
100
+ if (subscriptionPreview) {
101
+ setSubscriptionPreview(subscriptionPreview);
102
+ }
73
103
 
74
104
  setIsFetchingSubscriptionPreview(false);
75
105
  };
@@ -22,12 +22,14 @@ export type ProgressBarState = {
22
22
  activeStep: number;
23
23
  completedSteps: number[];
24
24
  steps: CheckoutStep[];
25
+ isDisabled: boolean;
25
26
  };
26
27
 
27
28
  const INITIAL_STATE: ProgressBarState = {
28
29
  activeStep: 0,
29
30
  completedSteps: [],
30
31
  steps: CHECKOUT_STEPS,
32
+ isDisabled: false,
31
33
  };
32
34
 
33
35
  export function getProgressBarInitialState({ availableAddons }: { availableAddons?: Addon[] }) {
@@ -66,6 +68,10 @@ function useGoNext() {
66
68
  const [, setState] = useCheckoutContext();
67
69
  return () =>
68
70
  setState(({ progressBar }) => {
71
+ if (progressBar.isDisabled) {
72
+ return;
73
+ }
74
+
69
75
  if (!progressBar.completedSteps.includes(progressBar.activeStep)) {
70
76
  progressBar.completedSteps.push(progressBar.activeStep);
71
77
  }
@@ -76,6 +82,14 @@ function useGoNext() {
76
82
  });
77
83
  }
78
84
 
85
+ function useSetIsDisabled() {
86
+ const [, setState] = useCheckoutContext();
87
+ return (isDisabled?: boolean) =>
88
+ setState(({ progressBar }) => {
89
+ progressBar.isDisabled = !!isDisabled;
90
+ });
91
+ }
92
+
79
93
  export function useProgressBarModel() {
80
94
  const progressBarState = useProgressBarState();
81
95
  const currentStep = progressBarState.steps[progressBarState.activeStep];
@@ -88,5 +102,6 @@ export function useProgressBarModel() {
88
102
  setActiveStep: useSetActiveStep(),
89
103
  markStepAsCompleted: useMarkStepAsCompleted(),
90
104
  goNext: useGoNext(),
105
+ setIsDisabled: useSetIsDisabled(),
91
106
  };
92
107
  }
@@ -1,16 +1,22 @@
1
1
  import { useCheckoutContext } from '../CheckoutProvider';
2
2
  import { AddonsStepState } from './useAddonsStepModel';
3
3
  import { PromotionCodeState } from './useCouponModel';
4
+ import { PaymentStepState } from './usePaymentStepModel';
4
5
  import { PlanStepState } from './usePlanStepModel';
5
6
 
6
- export type SubscriptionState = PlanStepState & PromotionCodeState & Pick<AddonsStepState, 'addons'>;
7
+ export type SubscriptionState = PlanStepState &
8
+ PromotionCodeState &
9
+ Pick<AddonsStepState, 'addons'> &
10
+ Pick<PaymentStepState, 'billingAddress' | 'taxPercentage'>;
7
11
 
8
12
  export function useSubscriptionModel(): SubscriptionState {
9
- const [{ planStep, addonsStep, promotionCode }] = useCheckoutContext();
13
+ const [{ planStep, addonsStep, promotionCode, paymentStep }] = useCheckoutContext();
10
14
 
11
15
  return {
12
16
  ...planStep,
13
17
  addons: addonsStep.addons,
14
18
  promotionCode,
19
+ billingAddress: paymentStep.billingAddress,
20
+ taxPercentage: paymentStep.taxPercentage,
15
21
  };
16
22
  }
@@ -11,7 +11,7 @@ export function useSubscriptionState(): ApplySubscription | undefined {
11
11
  const addons = subscription.addons.map(({ addon, quantity }) => ({ addonId: addon.id, quantity }));
12
12
 
13
13
  if (!plan?.id) {
14
- return;
14
+ return undefined;
15
15
  }
16
16
 
17
17
  return {
@@ -22,5 +22,6 @@ export function useSubscriptionState(): ApplySubscription | undefined {
22
22
  addons,
23
23
  promotionCode: subscription.promotionCode,
24
24
  billingCountryCode: subscription.billingCountryCode,
25
+ ...(subscription.taxPercentage ? { billingInformation: { taxPercentage: subscription.taxPercentage } } : {}),
25
26
  };
26
27
  }
@@ -2,3 +2,4 @@ export { CheckoutTheme } from './theme';
2
2
  export { Checkout, CheckoutProps } from './Checkout';
3
3
  export { OnCheckoutCompletedParams, OnCheckoutParams, CheckoutResult } from './CheckoutContainer';
4
4
  export { CheckoutLocalization } from './textOverrides';
5
+ export * from './types';
@@ -24,8 +24,9 @@ export const CheckoutProgressBar = () => {
24
24
 
25
25
  return (
26
26
  <Grid key={key} item display="flex" flexDirection="row" flex={1} justifyContent="flex-start">
27
- {isLoadingCheckoutData && <Skeleton width={120} height={20} style={{ marginTop: 8 }} />}
28
- {!isLoadingCheckoutData && (
27
+ {isLoadingCheckoutData ? (
28
+ <Skeleton width={120} height={20} style={{ marginTop: 8 }} />
29
+ ) : (
29
30
  <StyledStepButton onClick={() => setActiveStep(index)} fullWidth disabled={isDisabled}>
30
31
  <Grid item display="flex" flexDirection="row" alignItems="center" gap={1}>
31
32
  <StepIcon
@@ -13,6 +13,7 @@ import {
13
13
  } from './PaymentMethods.style';
14
14
  import { StripePaymentForm } from './stripe';
15
15
  import { CheckoutLocalization } from '../../textOverrides';
16
+ import { CheckoutContainerProps } from '../../CheckoutContainer';
16
17
 
17
18
  export type PaymentMethodLayoutProps = {
18
19
  checked: boolean;
@@ -28,13 +29,13 @@ export type PaymentMethodProps = Pick<Customer, 'paymentMethodDetails'> &
28
29
  export type NewPaymentMethodProps = Pick<PaymentMethodLayoutProps, 'checked' | 'readOnly'> & {
29
30
  onSelect: () => void;
30
31
  checkoutLocalization: CheckoutLocalization;
31
- };
32
+ } & Pick<CheckoutContainerProps, 'onBillingAddressChange'>;
32
33
 
33
34
  function PaymentMethodLayout({ checked, icon, text, subtitle, readOnly }: PaymentMethodLayoutProps) {
34
35
  return (
35
36
  <PaymentMethodLayoutContainer>
36
37
  <Radio checked={checked} disabled={readOnly} />
37
- <Icon icon={icon} style={{ display: 'contents' }} />
38
+ <Icon icon={icon} style={{ display: 'flex' }} />
38
39
  <PaymentMethodTextContainer container>
39
40
  <Grid item>{text}</Grid>
40
41
  {subtitle && <Grid item>{subtitle}</Grid>}
@@ -55,11 +56,11 @@ export function ExistingPaymentMethod({ checked, paymentMethodDetails, readOnly,
55
56
  checked={checked}
56
57
  readOnly={readOnly}
57
58
  icon="PaymentMethod"
58
- text={<Typography variant="h6">{`Ending in ${last4Digits}`}</Typography>}
59
+ text={<Typography variant="h6">{`Card ending in ${last4Digits}`}</Typography>}
59
60
  subtitle={
60
61
  !!expirationMonth &&
61
62
  !!expirationYear && (
62
- <Typography variant="body1">{`Exp. ${expirationMonth
63
+ <Typography variant="body1">{`Expires ${expirationMonth
63
64
  .toString()
64
65
  .padStart(2, '0')}/${expirationYear}`}</Typography>
65
66
  )
@@ -69,7 +70,13 @@ export function ExistingPaymentMethod({ checked, paymentMethodDetails, readOnly,
69
70
  );
70
71
  }
71
72
 
72
- export function NewPaymentMethod({ checked, onSelect, readOnly, checkoutLocalization }: NewPaymentMethodProps) {
73
+ export function NewPaymentMethod({
74
+ checked,
75
+ onSelect,
76
+ readOnly,
77
+ checkoutLocalization,
78
+ onBillingAddressChange,
79
+ }: NewPaymentMethodProps) {
73
80
  return (
74
81
  <NewPaymentMethodContainer item onClick={onSelect} $disabled={readOnly}>
75
82
  <PaymentMethodLayout
@@ -79,7 +86,7 @@ export function NewPaymentMethod({ checked, onSelect, readOnly, checkoutLocaliza
79
86
  text={<Typography variant="h6">{checkoutLocalization.newPaymentMethodText}</Typography>}
80
87
  />
81
88
  <Collapse in={checked}>
82
- <StripePaymentForm />
89
+ <StripePaymentForm onBillingAddressChange={onBillingAddressChange} />
83
90
  </Collapse>
84
91
  </NewPaymentMethodContainer>
85
92
  );