@stigg/react-sdk 4.2.2 → 4.3.0-beta.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 (165) hide show
  1. package/README.md +1 -1
  2. package/dist/components/checkout/Checkout.d.ts +5 -0
  3. package/dist/components/checkout/CheckoutContainer.d.ts +22 -0
  4. package/dist/components/checkout/CheckoutContainer.style.d.ts +29 -0
  5. package/dist/components/checkout/CheckoutProvider.d.ts +33 -0
  6. package/dist/components/checkout/CheckoutSummary.d.ts +9 -0
  7. package/dist/components/checkout/components/Button.d.ts +6 -0
  8. package/dist/components/checkout/components/InputField.d.ts +8 -0
  9. package/dist/components/checkout/components/index.d.ts +2 -0
  10. package/dist/components/checkout/formatting.d.ts +2 -0
  11. package/dist/components/checkout/hooks/index.d.ts +8 -0
  12. package/dist/components/checkout/hooks/useAddonsStepModel.d.ts +21 -0
  13. package/dist/components/checkout/hooks/useCheckoutModel.d.ts +9 -0
  14. package/dist/components/checkout/hooks/useCouponModel.d.ts +7 -0
  15. package/dist/components/checkout/hooks/useLoadCheckout.d.ts +13 -0
  16. package/dist/components/checkout/hooks/usePaymentStepModel.d.ts +16 -0
  17. package/dist/components/checkout/hooks/usePlanStepModel.d.ts +23 -0
  18. package/dist/components/checkout/hooks/usePreviewSubscription.d.ts +13 -0
  19. package/dist/components/checkout/hooks/useProgressBarModel.d.ts +26 -0
  20. package/dist/components/checkout/hooks/useSubscriptionModel.d.ts +5 -0
  21. package/dist/components/checkout/hooks/useSubscriptionState.d.ts +2 -0
  22. package/dist/components/checkout/index.d.ts +3 -0
  23. package/dist/components/checkout/planHeader/PlanHeader.d.ts +7 -0
  24. package/dist/components/checkout/planHeader/PlanHeader.style.d.ts +25 -0
  25. package/dist/components/checkout/planHeader/index.d.ts +1 -0
  26. package/dist/components/checkout/progressBar/CheckoutProgressBar.d.ts +2 -0
  27. package/dist/components/checkout/progressBar/CheckoutProgressBar.style.d.ts +45 -0
  28. package/dist/components/checkout/promotionCode/AddPromotionCode.d.ts +5 -0
  29. package/dist/components/checkout/promotionCode/AddPromotionCodeButton.d.ts +7 -0
  30. package/dist/components/checkout/promotionCode/AppliedPromotionCode.d.ts +6 -0
  31. package/dist/components/checkout/promotionCode/PromotionCodeSection.d.ts +5 -0
  32. package/dist/components/checkout/promotionCode/index.d.ts +1 -0
  33. package/dist/components/checkout/steps/addons/CheckoutAddonsStep.d.ts +2 -0
  34. package/dist/components/checkout/steps/addons/CheckoutAddonsStep.style.d.ts +93 -0
  35. package/dist/components/checkout/steps/addons/addon.utils.d.ts +15 -0
  36. package/dist/components/checkout/steps/addons/index.d.ts +1 -0
  37. package/dist/components/checkout/steps/payment/PaymentMethods.d.ts +19 -0
  38. package/dist/components/checkout/steps/payment/PaymentMethods.style.d.ts +113 -0
  39. package/dist/components/checkout/steps/payment/PaymentStep.d.ts +2 -0
  40. package/dist/components/checkout/steps/payment/index.d.ts +1 -0
  41. package/dist/components/checkout/steps/payment/stripe/StripePaymentForm.d.ts +2 -0
  42. package/dist/components/checkout/steps/payment/stripe/index.d.ts +3 -0
  43. package/dist/components/checkout/steps/payment/stripe/stripe.utils.d.ts +33 -0
  44. package/dist/components/checkout/steps/payment/stripe/useStripeIntegration.d.ts +5 -0
  45. package/dist/components/checkout/steps/payment/stripe/useSubmit.d.ts +10 -0
  46. package/dist/components/checkout/steps/plan/BillingPeriodPicker.d.ts +9 -0
  47. package/dist/components/checkout/steps/plan/BillingPeriodPicker.style.d.ts +52 -0
  48. package/dist/components/checkout/steps/plan/CheckoutChargeList.d.ts +16 -0
  49. package/dist/components/checkout/steps/plan/CheckoutPlanStep.d.ts +4 -0
  50. package/dist/components/checkout/steps/plan/CheckoutPlanStep.style.d.ts +12 -0
  51. package/dist/components/checkout/steps/plan/index.d.ts +1 -0
  52. package/dist/components/checkout/steps/surprise/SurpriseStep.d.ts +2 -0
  53. package/dist/components/checkout/textOverrides.d.ts +28 -0
  54. package/dist/components/checkout/theme.d.ts +8 -0
  55. package/dist/components/checkout/types.d.ts +7 -0
  56. package/dist/components/common/Icon.d.ts +3 -2
  57. package/dist/components/common/PoweredByStigg.d.ts +1 -1
  58. package/dist/components/{paywall/TiersLayout.d.ts → common/TiersSelectContainer.d.ts} +2 -3
  59. package/dist/components/common/customIcons.d.ts +17 -5
  60. package/dist/components/common/mapExternalTheme.d.ts +2 -0
  61. package/dist/components/customerPortal/subscriptionOverview/tabs/SubscriptionTabs.d.ts +1 -1
  62. package/dist/components/hooks/useChargeSort.d.ts +3 -0
  63. package/dist/components/utils/calculateDiscountRate.d.ts +1 -0
  64. package/dist/components/utils/currencyUtils.d.ts +1 -1
  65. package/dist/components/{paywall/planPriceTier.d.ts → utils/priceTierUtils.d.ts} +3 -1
  66. package/dist/components/utils/priceUtils.d.ts +2 -0
  67. package/dist/index.d.ts +1 -0
  68. package/dist/react-sdk.cjs.development.js +3472 -219
  69. package/dist/react-sdk.cjs.development.js.map +1 -1
  70. package/dist/react-sdk.cjs.production.min.js +1 -1
  71. package/dist/react-sdk.cjs.production.min.js.map +1 -1
  72. package/dist/react-sdk.esm.js +3615 -225
  73. package/dist/react-sdk.esm.js.map +1 -1
  74. package/dist/stories/Checkout.stories.d.ts +3 -0
  75. package/dist/theme/getResolvedTheme.d.ts +1 -0
  76. package/dist/theme/types.d.ts +1 -0
  77. package/package.json +7 -4
  78. package/src/assets/arrow-forward.svg +3 -0
  79. package/src/assets/arrow-right.svg +6 -0
  80. package/src/assets/close.svg +3 -0
  81. package/src/assets/nyancat.svg +634 -0
  82. package/src/assets/outlined-checked-circle.svg +6 -0
  83. package/src/assets/outlined-circle.svg +3 -0
  84. package/src/assets/payment-method.svg +11 -0
  85. package/src/assets/plus-icon.svg +6 -0
  86. package/src/assets/trash.svg +8 -0
  87. package/src/components/StiggProvider.tsx +5 -5
  88. package/src/components/checkout/Checkout.tsx +30 -0
  89. package/src/components/checkout/CheckoutContainer.style.ts +34 -0
  90. package/src/components/checkout/CheckoutContainer.tsx +92 -0
  91. package/src/components/checkout/CheckoutProvider.tsx +135 -0
  92. package/src/components/checkout/CheckoutSummary.tsx +361 -0
  93. package/src/components/checkout/components/Button.tsx +30 -0
  94. package/src/components/checkout/components/InputField.tsx +23 -0
  95. package/src/components/checkout/components/index.ts +2 -0
  96. package/src/components/checkout/formatting.ts +12 -0
  97. package/src/components/checkout/hooks/index.ts +8 -0
  98. package/src/components/checkout/hooks/useAddonsStepModel.ts +96 -0
  99. package/src/components/checkout/hooks/useCheckoutModel.ts +31 -0
  100. package/src/components/checkout/hooks/useCouponModel.ts +28 -0
  101. package/src/components/checkout/hooks/useLoadCheckout.ts +40 -0
  102. package/src/components/checkout/hooks/usePaymentStepModel.ts +49 -0
  103. package/src/components/checkout/hooks/usePlanStepModel.ts +170 -0
  104. package/src/components/checkout/hooks/usePreviewSubscription.ts +82 -0
  105. package/src/components/checkout/hooks/useProgressBarModel.ts +89 -0
  106. package/src/components/checkout/hooks/useSubscriptionModel.ts +16 -0
  107. package/src/components/checkout/hooks/useSubscriptionState.ts +26 -0
  108. package/src/components/checkout/index.ts +3 -0
  109. package/src/components/checkout/planHeader/PlanHeader.style.tsx +23 -0
  110. package/src/components/checkout/planHeader/PlanHeader.tsx +61 -0
  111. package/src/components/checkout/planHeader/index.ts +1 -0
  112. package/src/components/checkout/progressBar/CheckoutProgressBar.style.ts +29 -0
  113. package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +48 -0
  114. package/src/components/checkout/promotionCode/AddPromotionCode.tsx +85 -0
  115. package/src/components/checkout/promotionCode/AddPromotionCodeButton.tsx +39 -0
  116. package/src/components/checkout/promotionCode/AppliedPromotionCode.tsx +37 -0
  117. package/src/components/checkout/promotionCode/PromotionCodeSection.tsx +27 -0
  118. package/src/components/checkout/promotionCode/index.ts +1 -0
  119. package/src/components/checkout/steps/addons/CheckoutAddonsStep.style.tsx +24 -0
  120. package/src/components/checkout/steps/addons/CheckoutAddonsStep.tsx +125 -0
  121. package/src/components/checkout/steps/addons/addon.utils.ts +68 -0
  122. package/src/components/checkout/steps/addons/index.ts +1 -0
  123. package/src/components/checkout/steps/payment/PaymentMethods.style.ts +26 -0
  124. package/src/components/checkout/steps/payment/PaymentMethods.tsx +83 -0
  125. package/src/components/checkout/steps/payment/PaymentStep.tsx +41 -0
  126. package/src/components/checkout/steps/payment/index.ts +1 -0
  127. package/src/components/checkout/steps/payment/stripe/StripePaymentForm.tsx +43 -0
  128. package/src/components/checkout/steps/payment/stripe/index.ts +3 -0
  129. package/src/components/checkout/steps/payment/stripe/stripe.utils.ts +109 -0
  130. package/src/components/checkout/steps/payment/stripe/useStripeIntegration.ts +27 -0
  131. package/src/components/checkout/steps/payment/stripe/useSubmit.ts +100 -0
  132. package/src/components/checkout/steps/plan/BillingPeriodPicker.style.tsx +46 -0
  133. package/src/components/checkout/steps/plan/BillingPeriodPicker.tsx +63 -0
  134. package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +138 -0
  135. package/src/components/checkout/steps/plan/CheckoutPlanStep.style.tsx +6 -0
  136. package/src/components/checkout/steps/plan/CheckoutPlanStep.tsx +22 -0
  137. package/src/components/checkout/steps/plan/index.ts +1 -0
  138. package/src/components/checkout/steps/surprise/SurpriseStep.tsx +27 -0
  139. package/src/components/checkout/textOverrides.ts +58 -0
  140. package/src/components/checkout/theme.ts +26 -0
  141. package/src/components/checkout/types.ts +7 -0
  142. package/src/components/common/Icon.tsx +17 -22
  143. package/src/components/common/PoweredByStigg.tsx +1 -1
  144. package/src/components/{paywall/TiersLayout.tsx → common/TiersSelectContainer.tsx} +8 -7
  145. package/src/components/common/Typography.tsx +11 -1
  146. package/src/components/common/customIcons.ts +17 -28
  147. package/src/components/common/mapExternalTheme.ts +6 -0
  148. package/src/components/customerPortal/subscriptionOverview/tabs/SubscriptionTabs.tsx +6 -12
  149. package/src/components/hooks/useChargeSort.ts +17 -0
  150. package/src/components/paywall/BillingPeriodPicker.tsx +1 -1
  151. package/src/components/paywall/Paywall.tsx +1 -1
  152. package/src/components/paywall/PlanOffering.tsx +1 -1
  153. package/src/components/paywall/PlanOfferingButton.tsx +1 -1
  154. package/src/components/paywall/PlanPrice.tsx +3 -3
  155. package/src/components/paywall/utils/calculateUnitQuantityText.ts +9 -4
  156. package/src/components/utils/calculateDiscountRate.ts +26 -14
  157. package/src/components/utils/currencyUtils.ts +1 -1
  158. package/src/components/utils/getPaidPriceText.ts +2 -2
  159. package/src/components/{paywall/planPriceTier.ts → utils/priceTierUtils.ts} +25 -3
  160. package/src/components/utils/priceUtils.ts +10 -0
  161. package/src/index.ts +1 -0
  162. package/src/stories/Checkout.stories.tsx +59 -0
  163. package/src/theme/Theme.tsx +9 -8
  164. package/src/theme/getResolvedTheme.ts +1 -0
  165. package/src/theme/types.ts +1 -0
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import styled from '@emotion/styled/macro';
3
+ import { Grid } from '@mui/material';
4
+ import { Icon } from '../../common/Icon';
5
+ import { Typography } from '../../common/Typography';
6
+ import { Button } from '../components';
7
+
8
+ const AppliedCouponContainer = styled(Grid)`
9
+ width: auto;
10
+ display: inline-flex;
11
+ align-items: center;
12
+ flex-direction: row;
13
+ gap: 8px;
14
+ padding: 6px 16px;
15
+ border-radius: ${({ theme }) => theme.stigg.border.radius};
16
+ border: 1px solid ${({ theme }) => theme.stigg.palette.primary};
17
+ `;
18
+
19
+ export type AppliedPromotionCodeProps = {
20
+ promotionCode: string;
21
+ onClearPromotionCode: () => void;
22
+ };
23
+
24
+ export const AppliedPromotionCode = ({ promotionCode, onClearPromotionCode }: AppliedPromotionCodeProps) => (
25
+ <AppliedCouponContainer container>
26
+ <Grid item>
27
+ <Typography variant="body1" color="primary.main">
28
+ {promotionCode}
29
+ </Typography>
30
+ </Grid>
31
+ <Grid item>
32
+ <Button variant="text" sx={{ minWidth: 'unset', padding: 0 }} onClick={onClearPromotionCode}>
33
+ <Icon icon="Close" style={{ display: 'flex' }} />
34
+ </Button>
35
+ </Grid>
36
+ </AppliedCouponContainer>
37
+ );
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import styled from '@emotion/styled/macro';
3
+ import { usePromotionCodeModel } from '../hooks/useCouponModel';
4
+ import { AddPromotionCode } from './AddPromotionCode';
5
+ import { AppliedPromotionCode } from './AppliedPromotionCode';
6
+ import { CheckoutLocalization } from '../textOverrides';
7
+
8
+ const PromotionCodeSectionContainer = styled.div`
9
+ margin: 16px 0;
10
+ `;
11
+
12
+ export const PromotionCodeSection = ({ checkoutLocalization }: { checkoutLocalization: CheckoutLocalization }) => {
13
+ const { promotionCode, setPromotionCode } = usePromotionCodeModel();
14
+
15
+ const onClearPromotionCode = () => {
16
+ setPromotionCode('');
17
+ };
18
+
19
+ let content: React.ReactNode = null;
20
+ if (promotionCode) {
21
+ content = <AppliedPromotionCode promotionCode={promotionCode} onClearPromotionCode={onClearPromotionCode} />;
22
+ } else {
23
+ content = <AddPromotionCode checkoutLocalization={checkoutLocalization} />;
24
+ }
25
+
26
+ return <PromotionCodeSectionContainer>{content}</PromotionCodeSectionContainer>;
27
+ };
@@ -0,0 +1 @@
1
+ export * from './PromotionCodeSection';
@@ -0,0 +1,24 @@
1
+ import styled from '@emotion/styled/macro';
2
+ import { Grid } from '@mui/material';
3
+
4
+ import { Button } from '../../components';
5
+
6
+ export const CheckoutAddonsContainer = styled(Grid)`
7
+ width: 100%;
8
+ margin-top: 32px;
9
+ gap: 8px;
10
+ `;
11
+
12
+ export const AddonListItemContainer = styled(Grid)`
13
+ display: flex;
14
+ justify-content: space-between;
15
+ align-items: center;
16
+ height: 80px;
17
+ `;
18
+
19
+ export const TrashButton = styled(Button)`
20
+ min-width: unset;
21
+ width: 41px;
22
+ height: 41px;
23
+ padding: 0;
24
+ `;
@@ -0,0 +1,125 @@
1
+ import React from 'react';
2
+
3
+ import { Grid } from '@mui/material';
4
+ import { Addon, BillingPeriod, SubscriptionAddon } from '@stigg/js-client-sdk';
5
+
6
+ import { Icon } from '../../../common/Icon';
7
+ import { Typography } from '../../../common/Typography';
8
+ import { currencyPriceFormatter } from '../../../utils/currencyUtils';
9
+ import { Button, InputField } from '../../components';
10
+ import { useAddonsStepModel } from '../../hooks/useAddonsStepModel';
11
+ import { usePlanStepModel } from '../../hooks/usePlanStepModel';
12
+ import { AddonListItemContainer, CheckoutAddonsContainer, TrashButton } from './CheckoutAddonsStep.style';
13
+ import { CheckoutLocalization } from '../../textOverrides';
14
+ import { useCheckoutModel } from '../../hooks';
15
+
16
+ type UseAddonsStepModel = ReturnType<typeof useAddonsStepModel>;
17
+
18
+ type AddonListItemProps = {
19
+ addon: Addon;
20
+ addonState?: SubscriptionAddon;
21
+ initialAddonState?: SubscriptionAddon;
22
+ billingPeriod: BillingPeriod;
23
+ setAddon: UseAddonsStepModel['setAddon'];
24
+ removeAddon: UseAddonsStepModel['removeAddon'];
25
+ checkoutLocalization: CheckoutLocalization;
26
+ };
27
+
28
+ function AddonListItem({
29
+ addon,
30
+ billingPeriod,
31
+ addonState,
32
+ initialAddonState,
33
+ setAddon,
34
+ removeAddon,
35
+ checkoutLocalization,
36
+ }: AddonListItemProps) {
37
+ const addonPrice = addon.pricePoints.find((pricePoint) => pricePoint.billingPeriod === billingPeriod);
38
+ const isAdded = !!addonState;
39
+ const hasChanges =
40
+ (!!addonState && !!initialAddonState && addonState.quantity !== initialAddonState.quantity) ||
41
+ (!initialAddonState && !!addonState) ||
42
+ (!!initialAddonState && !addonState);
43
+
44
+ const handleQuantityChange = (quantity?: number) => {
45
+ setAddon(addon, quantity || 1);
46
+ };
47
+
48
+ const handleUndo = () => {
49
+ initialAddonState ? setAddon(addon, initialAddonState.quantity) : removeAddon(addon.id);
50
+ };
51
+
52
+ return (
53
+ <AddonListItemContainer container>
54
+ <Grid item>
55
+ <Typography variant="h6" lineHeight="24px">
56
+ {addon.displayName}
57
+ </Typography>
58
+ {!!addonPrice && (
59
+ <Typography variant="body1" lineHeight="20px" color="secondary">
60
+ {`${currencyPriceFormatter({
61
+ amount: addonPrice.amount!,
62
+ currency: addonPrice.currency,
63
+ })}/${billingPeriod === BillingPeriod.Annually ? 'year' : 'month'}`}
64
+ </Typography>
65
+ )}
66
+ </Grid>
67
+ <Grid item>
68
+ {hasChanges && (
69
+ <Button variant="text" size="small" sx={{ padding: '8px', minWidth: 'unset' }} onClick={handleUndo}>
70
+ <Typography color="primary.main" variant="body1">
71
+ Undo
72
+ </Typography>
73
+ </Button>
74
+ )}
75
+ {isAdded && (
76
+ <>
77
+ <InputField
78
+ id={`${addon.id}-input`}
79
+ type="number"
80
+ sx={{ width: 120, marginX: 2 }}
81
+ value={addonState?.quantity || 1}
82
+ onChange={(event) => handleQuantityChange(event?.target?.value ? Number(event?.target?.value) : 1)}
83
+ />
84
+ <TrashButton color="error" onClick={() => removeAddon(addon.id)}>
85
+ <Icon icon="Trash" style={{ display: 'flex' }} />
86
+ </TrashButton>
87
+ </>
88
+ )}
89
+ {!isAdded && (
90
+ <Button sx={{ paddingX: '22px', paddingY: '8px' }} onClick={() => handleQuantityChange()}>
91
+ {checkoutLocalization.addAddonText}
92
+ </Button>
93
+ )}
94
+ </Grid>
95
+ </AddonListItemContainer>
96
+ );
97
+ }
98
+
99
+ export function CheckoutAddonsStep() {
100
+ const { checkoutLocalization } = useCheckoutModel();
101
+ const { billingPeriod } = usePlanStepModel();
102
+ const { initialAddons, addons, availableAddons, setAddon, removeAddon } = useAddonsStepModel();
103
+
104
+ return (
105
+ <CheckoutAddonsContainer container>
106
+ {availableAddons?.map((addon) => {
107
+ const addonState = addons.find((x) => x.addon.id === addon.id);
108
+ const initialAddonState = initialAddons?.find((x) => x.addon.id === addon.id);
109
+
110
+ return (
111
+ <AddonListItem
112
+ key={addon.id}
113
+ addon={addon}
114
+ billingPeriod={billingPeriod}
115
+ addonState={addonState}
116
+ initialAddonState={initialAddonState}
117
+ setAddon={setAddon}
118
+ removeAddon={removeAddon}
119
+ checkoutLocalization={checkoutLocalization}
120
+ />
121
+ );
122
+ })}
123
+ </CheckoutAddonsContainer>
124
+ );
125
+ }
@@ -0,0 +1,68 @@
1
+ import sortBy from 'lodash/sortBy';
2
+ import { Addon, BillingPeriod, Currency, SubscriptionAddon } from '@stigg/js-client-sdk';
3
+
4
+ type filterAddonsBaseProps = {
5
+ billingPeriod: BillingPeriod;
6
+ currency: Currency;
7
+ billingCountryCode?: string;
8
+ };
9
+
10
+ export const sortAddons = (addons: Addon[]) => {
11
+ return sortBy(addons, 'displayName');
12
+ };
13
+
14
+ export const sortSubscriptionAddons = (addons: SubscriptionAddon[]) => {
15
+ return sortBy(addons, 'addon.displayName');
16
+ };
17
+
18
+ export function filterAddons({
19
+ addons,
20
+ billingPeriod,
21
+ billingCountryCode,
22
+ currency,
23
+ }: { addons?: Addon[] } & filterAddonsBaseProps): Addon[] {
24
+ return (
25
+ addons
26
+ ?.filter(addon => filterAddonPricePointsByBillingPeriod(addon, billingPeriod))
27
+ ?.map(addon => mapAddonPricePointsByBillingCountryCode({ addon, currency, billingCountryCode })) || []
28
+ );
29
+ }
30
+
31
+ export function filterSubscriptionAddons({
32
+ addons,
33
+ billingPeriod,
34
+ billingCountryCode,
35
+ currency,
36
+ }: { addons?: SubscriptionAddon[] } & filterAddonsBaseProps): SubscriptionAddon[] {
37
+ return (
38
+ addons
39
+ ?.filter(addon => filterAddonPricePointsByBillingPeriod(addon.addon, billingPeriod))
40
+ ?.map(addon => ({
41
+ ...addon,
42
+ addon: mapAddonPricePointsByBillingCountryCode({ addon: addon.addon, currency, billingCountryCode }),
43
+ })) || []
44
+ );
45
+ }
46
+
47
+ function filterAddonPricePointsByBillingPeriod(addon: Addon, billingPeriod: BillingPeriod) {
48
+ return addon.pricePoints.some(pricePoint => pricePoint.billingPeriod === billingPeriod);
49
+ }
50
+
51
+ function mapAddonPricePointsByBillingCountryCode({
52
+ addon,
53
+ currency,
54
+ billingCountryCode,
55
+ }: {
56
+ addon: Addon;
57
+ currency: Currency;
58
+ billingCountryCode?: string;
59
+ }) {
60
+ return {
61
+ ...addon,
62
+ pricePoints: addon.pricePoints.filter(
63
+ pricePoint =>
64
+ pricePoint.currency === currency &&
65
+ (billingCountryCode ? pricePoint.billingCountryCode === billingCountryCode : true),
66
+ ),
67
+ };
68
+ }
@@ -0,0 +1 @@
1
+ export * from './CheckoutAddonsStep';
@@ -0,0 +1,26 @@
1
+ import styled from '@emotion/styled';
2
+ import { Grid } from '@mui/material';
3
+
4
+ export const PaymentMethodContainer = styled(Grid)`
5
+ padding: 8px;
6
+ border-radius: 10px;
7
+ border: 1px solid ${({ theme }) => theme.stigg.palette.outlinedBorder};
8
+ cursor: pointer;
9
+ `;
10
+
11
+ export const NewPaymentMethodContainer = styled(PaymentMethodContainer)`
12
+ flex-direction: column;
13
+ align-items: unset;
14
+ `;
15
+
16
+ export const PaymentMethodLayoutContainer = styled(Grid)`
17
+ display: flex;
18
+ align-items: center;
19
+ flex: 1;
20
+ gap: 12px;
21
+ `;
22
+
23
+ export const PaymentMethodTextContainer = styled(Grid)`
24
+ display: flex;
25
+ flex-direction: column;
26
+ `;
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+
3
+ import { Collapse, Grid, Radio } from '@mui/material';
4
+ import { Customer } from '@stigg/js-client-sdk';
5
+
6
+ import { Icon, Icons } from '../../../common/Icon';
7
+ import { Typography } from '../../../common/Typography';
8
+ import {
9
+ NewPaymentMethodContainer,
10
+ PaymentMethodContainer,
11
+ PaymentMethodLayoutContainer,
12
+ PaymentMethodTextContainer,
13
+ } from './PaymentMethods.style';
14
+ import { StripePaymentForm } from './stripe';
15
+ import { CheckoutLocalization } from '../../textOverrides';
16
+
17
+ export type PaymentMethodLayoutProps = {
18
+ checked: boolean;
19
+ icon: Icons;
20
+ text: React.ReactNode;
21
+ subtitle?: React.ReactNode;
22
+ };
23
+
24
+ export type PaymentMethodProps = Pick<Customer, 'paymentMethodDetails'> &
25
+ Pick<PaymentMethodLayoutProps, 'checked'> & { onSelect: () => void };
26
+
27
+ export type NewPaymentMethodProps = Pick<PaymentMethodLayoutProps, 'checked'> & {
28
+ onSelect: () => void;
29
+ checkoutLocalization: CheckoutLocalization;
30
+ };
31
+
32
+ function PaymentMethodLayout({ checked, icon, text, subtitle }: PaymentMethodLayoutProps) {
33
+ return (
34
+ <PaymentMethodLayoutContainer>
35
+ <Radio checked={checked} />
36
+ <Icon icon={icon} style={{ display: 'contents' }} />
37
+ <PaymentMethodTextContainer container>
38
+ <Grid item>{text}</Grid>
39
+ {subtitle && <Grid item>{subtitle}</Grid>}
40
+ </PaymentMethodTextContainer>
41
+ </PaymentMethodLayoutContainer>
42
+ );
43
+ }
44
+
45
+ export function ExistingPaymentMethod({ checked, paymentMethodDetails, onSelect }: PaymentMethodProps) {
46
+ if (!paymentMethodDetails) {
47
+ return null;
48
+ }
49
+ const { last4Digits, expirationMonth, expirationYear } = paymentMethodDetails;
50
+
51
+ return (
52
+ <PaymentMethodContainer item onClick={onSelect}>
53
+ <PaymentMethodLayout
54
+ checked={checked}
55
+ icon="PaymentMethod"
56
+ text={<Typography variant="h6">{`Ending in ${last4Digits}`}</Typography>}
57
+ subtitle={
58
+ !!expirationMonth &&
59
+ !!expirationYear && (
60
+ <Typography variant="body1">{`Exp. ${expirationMonth
61
+ .toString()
62
+ .padStart(2, '0')}/${expirationYear}`}</Typography>
63
+ )
64
+ }
65
+ />
66
+ </PaymentMethodContainer>
67
+ );
68
+ }
69
+
70
+ export function NewPaymentMethod({ checked, onSelect, checkoutLocalization }: NewPaymentMethodProps) {
71
+ return (
72
+ <NewPaymentMethodContainer item onClick={onSelect}>
73
+ <PaymentMethodLayout
74
+ checked={checked}
75
+ icon="PaymentMethod"
76
+ text={<Typography variant="h6">{checkoutLocalization.newPaymentMethodText}</Typography>}
77
+ />
78
+ <Collapse in={checked}>
79
+ <StripePaymentForm />
80
+ </Collapse>
81
+ </NewPaymentMethodContainer>
82
+ );
83
+ }
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+
3
+ import styled from '@emotion/styled';
4
+ import { Alert, Grid } from '@mui/material';
5
+
6
+ import { useCheckoutModel, usePaymentStepModel } from '../../hooks';
7
+ import { ExistingPaymentMethod, NewPaymentMethod } from './PaymentMethods';
8
+ import { Typography } from '../../../common/Typography';
9
+
10
+ const PaymentContainer = styled(Grid)`
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: 24px;
14
+ margin: 32px 0;
15
+ `;
16
+
17
+ export function PaymentStep() {
18
+ const { checkoutState, checkoutLocalization } = useCheckoutModel();
19
+ const { customer } = checkoutState || {};
20
+ const { errorMessage, useNewPaymentMethod, setUseNewPaymentMethod } = usePaymentStepModel();
21
+
22
+ return (
23
+ <PaymentContainer container>
24
+ {!!errorMessage && (
25
+ <Alert severity="error" sx={{ alignItems: 'center' }}>
26
+ <Typography overrideColor="inherit">{errorMessage}</Typography>
27
+ </Alert>
28
+ )}
29
+ <ExistingPaymentMethod
30
+ checked={!useNewPaymentMethod}
31
+ onSelect={() => setUseNewPaymentMethod(false)}
32
+ paymentMethodDetails={customer?.paymentMethodDetails}
33
+ />
34
+ <NewPaymentMethod
35
+ checked={useNewPaymentMethod}
36
+ checkoutLocalization={checkoutLocalization}
37
+ onSelect={() => setUseNewPaymentMethod(true)}
38
+ ></NewPaymentMethod>
39
+ </PaymentContainer>
40
+ );
41
+ }
@@ -0,0 +1 @@
1
+ export * from './PaymentStep';
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import { Grid } from '@mui/material';
3
+ import { AddressElement, PaymentElement } from '@stripe/react-stripe-js';
4
+ import { Typography } from '../../../../common/Typography';
5
+ import { useCheckoutModel } from '../../../hooks';
6
+
7
+ export function StripePaymentForm() {
8
+ const { checkoutState, checkoutLocalization } = useCheckoutModel();
9
+ const { customer } = checkoutState || {};
10
+
11
+ return (
12
+ <Grid flexDirection="column" container gap={3} padding="16px">
13
+ <Grid flexDirection="column" container gap={2}>
14
+ <Typography variant="h6">{checkoutLocalization.newPaymentMethodBillingAddressTitle}</Typography>
15
+ <AddressElement
16
+ options={{
17
+ mode: 'billing',
18
+ fields: { phone: 'always' },
19
+ defaultValues: {
20
+ ...(customer?.name && { name: customer.name }),
21
+ },
22
+ }}
23
+ />
24
+ </Grid>
25
+ <Grid flexDirection="column" container gap={2}>
26
+ <Typography variant="h6">{checkoutLocalization.newPaymentMethodCardTitle}</Typography>
27
+ <PaymentElement
28
+ onChange={() => {}}
29
+ onReady={() => {}}
30
+ options={{
31
+ terms: { card: 'never' },
32
+ defaultValues: {
33
+ billingDetails: {
34
+ ...(customer?.name && { name: customer.name }),
35
+ ...(customer?.email && { email: customer.email }),
36
+ },
37
+ },
38
+ }}
39
+ />
40
+ </Grid>
41
+ </Grid>
42
+ );
43
+ }
@@ -0,0 +1,3 @@
1
+ export * from './StripePaymentForm';
2
+ export * from './useStripeIntegration';
3
+ export * from './useSubmit';
@@ -0,0 +1,109 @@
1
+ import { ApplySubscriptionResults, PaymentCollection } from '@stigg/js-client-sdk';
2
+ import { Stripe, StripeElements } from '@stripe/stripe-js';
3
+
4
+ type StripeElementsProps = {
5
+ stripe: Stripe | null;
6
+ elements: StripeElements | null;
7
+ };
8
+
9
+ export async function handleStripeFormValidations({ elements }: Pick<StripeElementsProps, 'elements'>) {
10
+ if (!elements) {
11
+ console.error('Stripe elements not initialized');
12
+ return { success: false };
13
+ }
14
+
15
+ const { error: elementsError } = await elements.submit();
16
+
17
+ if (elementsError) {
18
+ console.log(elementsError.message);
19
+ return { success: false };
20
+ }
21
+
22
+ return { success: true };
23
+ }
24
+
25
+ export async function handleNewPaymentMethod({
26
+ stripe,
27
+ elements,
28
+ setupIntentClientSecret,
29
+ }: {
30
+ stripe: Stripe | null;
31
+ elements: StripeElements | null;
32
+ setupIntentClientSecret?: string;
33
+ }) {
34
+ if (!stripe || !elements || !setupIntentClientSecret) {
35
+ return { success: false };
36
+ }
37
+
38
+ const { newPaymentMethodId, setupErrorMessage } = await handleStripeSetup({
39
+ stripe,
40
+ elements,
41
+ clientSecret: setupIntentClientSecret,
42
+ });
43
+
44
+ return { success: !!newPaymentMethodId, paymentMethodId: newPaymentMethodId, errorMessage: setupErrorMessage };
45
+ }
46
+
47
+ export async function handleStripeNextAction({
48
+ applySubscriptionResults,
49
+ stripe,
50
+ }: {
51
+ applySubscriptionResults: ApplySubscriptionResults;
52
+ stripe: Stripe | null;
53
+ }) {
54
+ const paymentIntentClientSecret = applySubscriptionResults?.subscription?.latestInvoice?.paymentSecret;
55
+ if (
56
+ stripe &&
57
+ paymentIntentClientSecret &&
58
+ applySubscriptionResults?.subscription?.paymentCollection === PaymentCollection.ActionRequired
59
+ ) {
60
+ const { error: NextActionError } = await stripe.handleNextAction({ clientSecret: paymentIntentClientSecret });
61
+ if (NextActionError) {
62
+ return { errorMessage: NextActionError.message || '' };
63
+ }
64
+ }
65
+
66
+ return { subscription: applySubscriptionResults?.subscription };
67
+ }
68
+
69
+ async function handleStripeSetup({
70
+ stripe,
71
+ elements,
72
+ clientSecret,
73
+ }: {
74
+ stripe: Stripe;
75
+ elements: StripeElements;
76
+ clientSecret: string;
77
+ }) {
78
+ let newPaymentMethodId: string | undefined;
79
+ let setupErrorMessage: string | undefined;
80
+
81
+ const { error: setupError } = await stripe.confirmSetup({
82
+ elements,
83
+ confirmParams: { return_url: window.location.href },
84
+ redirect: 'if_required',
85
+ });
86
+
87
+ if (setupError) {
88
+ if (setupError.type === 'card_error' || setupError.type === 'validation_error') {
89
+ if (setupError.type === 'card_error') {
90
+ // Set some error message
91
+ setupErrorMessage = setupError.message || '';
92
+ }
93
+ } else {
94
+ setupErrorMessage = 'An unexpected error occurred.';
95
+ }
96
+ } else {
97
+ try {
98
+ const { setupIntent } = await stripe.retrieveSetupIntent(clientSecret);
99
+ if (setupIntent?.payment_method) {
100
+ newPaymentMethodId = setupIntent.payment_method as string;
101
+ }
102
+ } catch (e) {
103
+ setupErrorMessage = 'An unexpected error occurred.';
104
+ console.error(e);
105
+ }
106
+ }
107
+
108
+ return { newPaymentMethodId, setupErrorMessage };
109
+ }
@@ -0,0 +1,27 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ import { BillingVendorIdentifier } from '@stigg/js-client-sdk';
4
+ import { loadStripe, Stripe } from '@stripe/stripe-js';
5
+
6
+ import { useCheckoutModel } from '../../../hooks';
7
+
8
+ export function useStripeIntegration() {
9
+ const [stripePromise, setStripePromise] = useState<Promise<Stripe | null> | null>(null);
10
+ const { checkoutState } = useCheckoutModel();
11
+ const { billingIntegration, setupSecret } = checkoutState || {};
12
+
13
+ useEffect(() => {
14
+ if (billingIntegration) {
15
+ const { billingIdentifier, credentials } = billingIntegration;
16
+ if (billingIdentifier !== BillingVendorIdentifier.Stripe) {
17
+ console.error('Currently only stripe integration is supported');
18
+ return;
19
+ }
20
+
21
+ const { accountId, publicKey } = credentials;
22
+ setStripePromise(loadStripe(publicKey, { stripeAccount: accountId }));
23
+ }
24
+ }, [billingIntegration]);
25
+
26
+ return { stripePromise, setupIntentClientSecret: setupSecret };
27
+ }