@stigg/react-sdk 4.4.0-beta.8 → 4.4.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 (95) hide show
  1. package/dist/components/checkout/Checkout.d.ts +3 -2
  2. package/dist/components/checkout/CheckoutContainer.d.ts +5 -2
  3. package/dist/components/checkout/CheckoutProvider.d.ts +3 -2
  4. package/dist/components/checkout/components/DowngradeToFreeContainer.d.ts +3 -7
  5. package/dist/components/checkout/components/StyledArrow.d.ts +5 -0
  6. package/dist/components/checkout/hooks/useCheckoutModel.d.ts +2 -0
  7. package/dist/components/checkout/hooks/useLoadCheckout.d.ts +3 -1
  8. package/dist/components/checkout/hooks/usePreviewSubscription.d.ts +17 -8
  9. package/dist/components/checkout/progressBar/CheckoutProgressBar.style.d.ts +1 -0
  10. package/dist/components/checkout/promotionCode/AddPromotionCode.d.ts +2 -4
  11. package/dist/components/checkout/promotionCode/AddPromotionCodeButton.d.ts +2 -1
  12. package/dist/components/checkout/promotionCode/PromotionCodeSection.d.ts +6 -2
  13. package/dist/components/checkout/steps/payment/stripe/useSubmit.d.ts +2 -2
  14. package/dist/components/checkout/steps/plan/BillingPeriodPicker.d.ts +1 -1
  15. package/dist/components/checkout/steps/plan/CheckoutPlanStep.d.ts +2 -1
  16. package/dist/components/checkout/summary/CheckoutSuccess.d.ts +4 -1
  17. package/dist/components/checkout/summary/CheckoutSummary.d.ts +1 -1
  18. package/dist/components/checkout/summary/components/CheckoutCaptions.d.ts +3 -2
  19. package/dist/components/checkout/summary/components/LineItems.d.ts +12 -8
  20. package/dist/components/checkout/textOverrides.d.ts +65 -21
  21. package/dist/components/checkout/theme.d.ts +0 -1
  22. package/dist/components/checkout/types.d.ts +7 -1
  23. package/dist/components/common/TiersSelectContainer.d.ts +1 -2
  24. package/dist/components/common/Typography.d.ts +2 -2
  25. package/dist/components/common/customIcons.d.ts +2 -0
  26. package/dist/components/customerPortal/subscriptionOverview/subscriptionView/SubscriptionView.style.d.ts +1 -1
  27. package/dist/components/paywall/PlanPrice.d.ts +1 -1
  28. package/dist/components/utils/currencyUtils.d.ts +2 -1
  29. package/dist/components/utils/getFeatureName.d.ts +1 -0
  30. package/dist/react-sdk.cjs.development.js +1153 -624
  31. package/dist/react-sdk.cjs.development.js.map +1 -1
  32. package/dist/react-sdk.cjs.production.min.js +1 -1
  33. package/dist/react-sdk.cjs.production.min.js.map +1 -1
  34. package/dist/react-sdk.esm.js +1176 -651
  35. package/dist/react-sdk.esm.js.map +1 -1
  36. package/dist/stories/mocks/checkout/consts.d.ts +11 -0
  37. package/dist/stories/mocks/checkout/mockCheckoutPreview.d.ts +2 -0
  38. package/dist/stories/mocks/checkout/mockCheckoutState.d.ts +2 -0
  39. package/dist/theme/getResolvedTheme.d.ts +1 -0
  40. package/dist/theme/types.d.ts +1 -0
  41. package/package.json +28 -20
  42. package/src/assets/coupon.svg +6 -0
  43. package/src/assets/pay-as-you-go-charge.svg +11 -0
  44. package/src/components/checkout/Checkout.tsx +5 -2
  45. package/src/components/checkout/CheckoutContainer.tsx +21 -12
  46. package/src/components/checkout/CheckoutProvider.tsx +5 -3
  47. package/src/components/checkout/components/DowngradeToFreeContainer.tsx +33 -36
  48. package/src/components/checkout/components/StyledArrow.tsx +9 -0
  49. package/src/components/checkout/hooks/useCheckoutModel.ts +12 -2
  50. package/src/components/checkout/hooks/useLoadCheckout.ts +10 -2
  51. package/src/components/checkout/hooks/usePlanStepModel.ts +22 -7
  52. package/src/components/checkout/hooks/usePreviewSubscription.ts +103 -50
  53. package/src/components/checkout/planHeader/PlanHeader.tsx +18 -25
  54. package/src/components/checkout/progressBar/CheckoutProgressBar.style.ts +11 -12
  55. package/src/components/checkout/progressBar/CheckoutProgressBar.tsx +7 -6
  56. package/src/components/checkout/promotionCode/AddPromotionCode.tsx +32 -9
  57. package/src/components/checkout/promotionCode/AddPromotionCodeButton.tsx +15 -11
  58. package/src/components/checkout/promotionCode/AppliedPromotionCode.tsx +4 -3
  59. package/src/components/checkout/promotionCode/PromotionCodeSection.tsx +21 -7
  60. package/src/components/checkout/steps/addons/CheckoutAddonsStep.style.tsx +0 -1
  61. package/src/components/checkout/steps/addons/CheckoutAddonsStep.tsx +7 -4
  62. package/src/components/checkout/steps/payment/PaymentMethods.style.ts +4 -1
  63. package/src/components/checkout/steps/payment/PaymentStep.tsx +0 -1
  64. package/src/components/checkout/steps/payment/stripe/useSubmit.ts +8 -4
  65. package/src/components/checkout/steps/plan/BillingPeriodPicker.style.tsx +3 -2
  66. package/src/components/checkout/steps/plan/BillingPeriodPicker.tsx +11 -8
  67. package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +35 -14
  68. package/src/components/checkout/steps/plan/CheckoutPlanStep.tsx +10 -5
  69. package/src/components/checkout/summary/CheckoutSuccess.tsx +52 -6
  70. package/src/components/checkout/summary/CheckoutSummary.tsx +166 -59
  71. package/src/components/checkout/summary/components/CheckoutCaptions.tsx +63 -39
  72. package/src/components/checkout/summary/components/LineItems.tsx +77 -28
  73. package/src/components/checkout/textOverrides.ts +112 -30
  74. package/src/components/checkout/theme.ts +0 -4
  75. package/src/components/checkout/types.ts +15 -1
  76. package/src/components/common/Icon.tsx +4 -6
  77. package/src/components/common/TiersSelectContainer.tsx +7 -8
  78. package/src/components/common/Typography.tsx +12 -3
  79. package/src/components/common/customIcons.ts +2 -0
  80. package/src/components/common/mapExternalTheme.ts +1 -2
  81. package/src/components/customerPortal/paywall/CustomerPortalPaywall.style.ts +4 -3
  82. package/src/components/paywall/PlanOfferingButton.tsx +6 -8
  83. package/src/components/paywall/PlanPrice.tsx +14 -17
  84. package/src/components/utils/currencyUtils.ts +4 -2
  85. package/src/components/utils/getFeatureName.ts +13 -5
  86. package/src/stories/Checkout.stories.tsx +37 -5
  87. package/src/stories/CustomerPortal.stories.tsx +2 -2
  88. package/src/stories/mocks/checkout/consts.ts +15 -0
  89. package/src/stories/mocks/checkout/mockCheckoutPreview.ts +138 -0
  90. package/src/stories/mocks/checkout/mockCheckoutState.ts +206 -0
  91. package/src/theme/Theme.tsx +10 -1
  92. package/src/theme/getResolvedTheme.ts +1 -0
  93. package/src/theme/types.ts +1 -0
  94. package/dist/components/checkout/planHeader/PlanHeader.style.d.ts +0 -25
  95. package/src/components/checkout/planHeader/PlanHeader.style.tsx +0 -23
@@ -30,8 +30,9 @@ export const BillingPeriodButton = styled.button<{
30
30
  background: ${({ theme, $isActive, $isOnlyBillingPeriod }) => {
31
31
  if ($isOnlyBillingPeriod) {
32
32
  return 'transparent';
33
- } else if ($isActive) {
34
- return theme.stigg.palette.backgroundSection;
33
+ }
34
+ if ($isActive) {
35
+ return theme.stigg.palette.primaryLight;
35
36
  }
36
37
  return 'transparent';
37
38
  }};
@@ -49,13 +49,16 @@ type BillingPeriodPickerProps = {
49
49
  checkoutLocalization: CheckoutLocalization;
50
50
  };
51
51
 
52
- export const BillingPeriodPicker = ({ plan, checkoutLocalization }: BillingPeriodPickerProps) => {
52
+ export function BillingPeriodPicker({ plan, checkoutLocalization }: BillingPeriodPickerProps) {
53
53
  const [monthlyPrices, annualPrices] = partition(
54
54
  plan?.pricePoints,
55
55
  (price) => price.billingPeriod === BillingPeriod.Monthly,
56
56
  );
57
57
 
58
58
  const hasBothBillingPeriods = !!monthlyPrices?.length && !!annualPrices?.length;
59
+ if (!hasBothBillingPeriods) {
60
+ return null;
61
+ }
59
62
 
60
63
  return (
61
64
  <BillingPeriodPickerContainer>
@@ -64,21 +67,21 @@ export const BillingPeriodPicker = ({ plan, checkoutLocalization }: BillingPerio
64
67
  </Typography>
65
68
 
66
69
  <BillingPeriodOptions>
67
- {!!annualPrices?.length && (
70
+ {!!monthlyPrices?.length && (
68
71
  <BillingPeriodOption
69
- key={BillingPeriod.Annually}
70
- billingPeriod={BillingPeriod.Annually}
72
+ key={BillingPeriod.Monthly}
73
+ billingPeriod={BillingPeriod.Monthly}
71
74
  isOnlyBillingPeriod={!hasBothBillingPeriods}
72
75
  />
73
76
  )}
74
- {!!monthlyPrices?.length && (
77
+ {!!annualPrices?.length && (
75
78
  <BillingPeriodOption
76
- key={BillingPeriod.Monthly}
77
- billingPeriod={BillingPeriod.Monthly}
79
+ key={BillingPeriod.Annually}
80
+ billingPeriod={BillingPeriod.Annually}
78
81
  isOnlyBillingPeriod={!hasBothBillingPeriods}
79
82
  />
80
83
  )}
81
84
  </BillingPeriodOptions>
82
85
  </BillingPeriodPickerContainer>
83
86
  );
84
- };
87
+ }
@@ -10,10 +10,11 @@ import { useChargesSort } from '../../../hooks/useChargeSort';
10
10
  import { calculateUnitQuantityText } from '../../../paywall/utils/calculateUnitQuantityText';
11
11
  import { currencyPriceFormatter } from '../../../utils/currencyUtils';
12
12
  import { InputField } from '../../components';
13
- import { usePlanStepModel, useProgressBarModel } from '../../hooks';
13
+ import { useCheckoutModel, usePlanStepModel, useProgressBarModel } from '../../hooks';
14
14
  import { TiersSelectContainer } from '../../../common/TiersSelectContainer';
15
- import { getPriceFeatureUnit, getTierByQuantity } from '../../../utils/priceTierUtils';
15
+ import { getTierByQuantity } from '../../../utils/priceTierUtils';
16
16
  import { getValidPriceQuantity } from '../../../utils/priceUtils';
17
+ import { getFeatureDisplayNameText } from '../../../utils/getFeatureName';
17
18
 
18
19
  export type UsePlanStepModel = ReturnType<typeof usePlanStepModel>;
19
20
 
@@ -31,6 +32,19 @@ const StyledPlanCharge = styled.div`
31
32
  margin-top: 16px;
32
33
  `;
33
34
 
35
+ const getValidationText = (charge: Price, quantity?: number) => {
36
+ const { minUnitQuantity, maxUnitQuantity } = charge;
37
+ if (!quantity || quantity < (minUnitQuantity || 1)) {
38
+ return `Minimum ${minUnitQuantity || 1}`;
39
+ }
40
+
41
+ if (maxUnitQuantity && quantity > maxUnitQuantity) {
42
+ return `Maximum ${maxUnitQuantity}`;
43
+ }
44
+
45
+ return '';
46
+ };
47
+
34
48
  export function PlanCharge({
35
49
  charge,
36
50
  isValid,
@@ -47,14 +61,22 @@ export function PlanCharge({
47
61
  const featureId = charge.feature?.featureId;
48
62
  const isBaseCharge = !featureId;
49
63
  const isPayAsYouGo = charge.pricingModel === BillingModel.UsageBased;
50
- const displayName = isBaseCharge ? 'Base charge' : charge.feature?.displayName;
64
+ const displayName = isBaseCharge
65
+ ? 'Base charge'
66
+ : getFeatureDisplayNameText(charge.feature?.displayName || '', charge.feature?.units, charge.feature?.unitsPlural);
51
67
  const hasQuantityRestrictions = !!(charge?.minUnitQuantity || charge?.maxUnitQuantity);
52
68
 
53
69
  const handleQuantityChange = (event: React.ChangeEvent<HTMLInputElement>) => {
54
70
  if (isBaseCharge || !featureId) return;
55
71
 
56
- const value = event?.target?.value ? Number(event?.target?.value) : charge.minUnitQuantity;
57
- if (!value || value <= 0) {
72
+ const { minUnitQuantity, maxUnitQuantity } = charge;
73
+ const value = event?.target?.value ? Number(event?.target?.value) : null;
74
+ if (
75
+ !value ||
76
+ value <= 0 ||
77
+ (minUnitQuantity && value < minUnitQuantity) ||
78
+ (maxUnitQuantity && value > maxUnitQuantity)
79
+ ) {
58
80
  onValidationChange({ featureId, isValid: false });
59
81
  // Reset the input value to null
60
82
  // @ts-ignore
@@ -74,22 +96,22 @@ export function PlanCharge({
74
96
  amount: charge.amount!,
75
97
  currency: charge.currency,
76
98
  locale: 'en-us',
99
+ minimumFractionDigits: 2,
77
100
  });
78
101
 
79
102
  chargeRow = `${formattedAmount}`;
80
103
  if (isPayAsYouGo) {
81
104
  chargeRow += ' / unit';
82
105
  }
83
- } else if (charge.isTieredPrice) {
106
+ } else if (charge.isTieredPrice && featureId) {
84
107
  const tier = getTierByQuantity(charge.tiers!, billableFeature!.quantity || 1);
85
108
  chargeRow = (
86
109
  <TiersSelectContainer
87
110
  componentId={`${featureId}-tiers`}
88
111
  tiers={charge.tiers}
89
- tierUnits={getPriceFeatureUnit(charge)}
90
112
  selectedTier={tier}
91
113
  handleTierChange={(tier: PriceTierFragment) => {
92
- setBillableFeature(featureId!, tier.upTo);
114
+ setBillableFeature(featureId, tier.upTo);
93
115
  }}
94
116
  />
95
117
  );
@@ -99,13 +121,10 @@ export function PlanCharge({
99
121
  sx={{ width: 120 }}
100
122
  id={`${featureId}-input`}
101
123
  type="number"
102
- InputProps={
103
- hasQuantityRestrictions ? { inputProps: { min: charge.minUnitQuantity, max: charge.maxUnitQuantity } } : {}
104
- }
105
124
  error={!isValid}
106
- helperText={!isValid ? 'Minimum 1' : undefined}
125
+ helperText={!isValid ? getValidationText(charge, billableFeature?.quantity) : undefined}
107
126
  FormHelperTextProps={{ sx: { margin: '4px' } }}
108
- value={billableFeature?.quantity ?? charge.minUnitQuantity ?? ''}
127
+ value={billableFeature?.quantity ?? ''}
109
128
  onChange={handleQuantityChange}
110
129
  onWheel={(e: React.WheelEvent<HTMLInputElement>) => (e.target as HTMLElement).blur()}
111
130
  />
@@ -135,6 +154,7 @@ export function PlanCharge({
135
154
  export function CheckoutChargeList({ plan, billingPeriod }: CheckoutChargeListProps) {
136
155
  const { billableFeatures, setBillableFeature } = usePlanStepModel();
137
156
  const { setIsDisabled } = useProgressBarModel();
157
+ const { setIsValid } = useCheckoutModel();
138
158
  const planCharges = useChargesSort(plan?.pricePoints?.filter((p) => p.billingPeriod === billingPeriod) || []);
139
159
  const [chargesValidation, setChargesValidation] = useState(
140
160
  planCharges?.reduce<Record<string, boolean>>((acc, curr) => {
@@ -146,7 +166,8 @@ export function CheckoutChargeList({ plan, billingPeriod }: CheckoutChargeListPr
146
166
  useEffect(() => {
147
167
  const isDisabled = Object.values(chargesValidation).some((x) => !x);
148
168
  setIsDisabled(isDisabled);
149
- }, [chargesValidation, setIsDisabled]);
169
+ setIsValid(!isDisabled);
170
+ }, [chargesValidation, setIsDisabled, setIsValid]);
150
171
 
151
172
  return (
152
173
  <div>
@@ -1,20 +1,25 @@
1
1
  import React from 'react';
2
+ import { CheckoutContainerProps } from '../../CheckoutContainer';
2
3
 
3
4
  import { useCheckoutModel } from '../../hooks/useCheckoutModel';
4
5
  import { usePlanStepModel } from '../../hooks/usePlanStepModel';
6
+ import { PlanHeader } from '../../planHeader';
5
7
  import { BillingPeriodPicker } from './BillingPeriodPicker';
6
8
  import { CheckoutChargeList } from './CheckoutChargeList';
7
9
  import { CheckoutPlanContainer } from './CheckoutPlanStep.style';
8
10
 
9
- export const CheckoutPlanStep = () => {
11
+ export const CheckoutPlanStep = ({ onChangePlan }: Pick<CheckoutContainerProps, 'onChangePlan'>) => {
10
12
  const { checkoutState, checkoutLocalization } = useCheckoutModel();
11
13
  const { plan } = checkoutState || {};
12
14
  const { billingPeriod } = usePlanStepModel();
13
15
 
14
16
  return (
15
- <CheckoutPlanContainer>
16
- <BillingPeriodPicker plan={plan} checkoutLocalization={checkoutLocalization} />
17
- <CheckoutChargeList plan={plan} billingPeriod={billingPeriod} />
18
- </CheckoutPlanContainer>
17
+ <>
18
+ <PlanHeader allowChangePlan onChangePlan={onChangePlan} />
19
+ <CheckoutPlanContainer>
20
+ <BillingPeriodPicker plan={plan} checkoutLocalization={checkoutLocalization} />
21
+ <CheckoutChargeList plan={plan} billingPeriod={billingPeriod} />
22
+ </CheckoutPlanContainer>
23
+ </>
19
24
  );
20
25
  };
@@ -4,27 +4,73 @@ import Color from 'color';
4
4
  import React from 'react';
5
5
  import Lottie from 'react-lottie';
6
6
  import animationData from '../../../assets/lottie/checkout-success.json';
7
+ import { Typography } from '../../common/Typography';
8
+ import { CheckoutLocalization } from '../textOverrides';
7
9
 
8
10
  export const ANIMATION_DURATION = 5000;
9
11
 
10
- const BACKGROUND_COLOR = Color('#0b2f7a').alpha(0.3).toString();
11
-
12
12
  const CheckoutSuccessContainer = styled(Box)`
13
+ @keyframes blurFade {
14
+ 0% {
15
+ background-color: ${({ theme }) => Color(theme.stigg.palette.backgroundPaper).alpha(0).toString()};
16
+ backdrop-filter: blur(0px);
17
+ }
18
+ 100% {
19
+ background-color: ${({ theme }) => Color(theme.stigg.palette.backgroundPaper).alpha(0.9).toString()};
20
+ backdrop-filter: blur(6.5px);
21
+ }
22
+ }
23
+
13
24
  position: absolute;
14
25
  top: 0;
15
26
  left: 0;
16
27
  bottom: 0;
17
28
  right: 0;
18
- background-color: ${BACKGROUND_COLOR};
29
+ z-index: 5;
30
+ background-color: ${({ theme }) => Color(theme.stigg.palette.backgroundPaper).alpha(0.9).toString()};
31
+ animation: blurFade 2s ease-in forwards;
32
+ display: flex;
33
+ flex-direction: column;
34
+ justify-content: center;
35
+
19
36
  * rect {
20
37
  fill: transparent;
21
38
  }
39
+
40
+ & path {
41
+ stroke: ${({ theme }) => theme.stigg.palette.primary};
42
+ }
43
+ `;
44
+
45
+ const CheckoutSuccessText = styled(Typography)`
46
+ @keyframes fadeIn {
47
+ 0% {
48
+ opacity: 0;
49
+ }
50
+ 75% {
51
+ opacity: 0;
52
+ }
53
+ 100% {
54
+ opacity: 1;
55
+ }
56
+ }
57
+
58
+ align-self: center;
59
+ animation: fadeIn 5s ease-in forwards;
22
60
  `;
23
61
 
24
- export function CheckoutSuccess() {
62
+ export function CheckoutSuccess({ checkoutLocalization }: { checkoutLocalization: CheckoutLocalization }) {
25
63
  return (
26
- <CheckoutSuccessContainer>
27
- <Lottie width={350} isClickToPauseDisabled options={{ loop: false, autoplay: true, animationData }} />
64
+ <CheckoutSuccessContainer className="stigg-checkout-success-container">
65
+ <Lottie
66
+ width={350}
67
+ height="auto"
68
+ isClickToPauseDisabled
69
+ options={{ loop: false, autoplay: true, animationData }}
70
+ />
71
+ <CheckoutSuccessText variant="h1" color="primary.main">
72
+ {checkoutLocalization.summary.checkoutSuccessText}
73
+ </CheckoutSuccessText>
28
74
  </CheckoutSuccessContainer>
29
75
  );
30
76
  }
@@ -2,7 +2,7 @@ import React, { useState } from 'react';
2
2
  import partition from 'lodash/partition';
3
3
  import styled from '@emotion/styled/macro';
4
4
  import { Box, CircularProgress, Divider, Grid, Paper } from '@mui/material';
5
- import { BillingModel, SubscriptionPreview } from '@stigg/js-client-sdk';
5
+ import { BillingModel } from '@stigg/js-client-sdk';
6
6
  import { PoweredByStigg } from '../../common/PoweredByStigg';
7
7
  import { Typography } from '../../common/Typography';
8
8
  import { useChargesSort } from '../../hooks/useChargeSort';
@@ -23,6 +23,7 @@ import {
23
23
  AppliedCreditsLineItem,
24
24
  BilledPriceLineItem,
25
25
  DiscountLineItem,
26
+ FreeChargeLineItem,
26
27
  LineItemContainer,
27
28
  LineItemRow,
28
29
  TaxLineItem,
@@ -31,6 +32,7 @@ import { WithSkeleton } from './components/WithSkeleton';
31
32
  import { Icon } from '../../common/Icon';
32
33
  import { CheckoutLocalization } from '../textOverrides';
33
34
  import { CheckoutSuccess } from './CheckoutSuccess';
35
+ import { getFeatureDisplayNameText } from '../../utils/getFeatureName';
34
36
 
35
37
  export const SummaryCard = styled(Paper)`
36
38
  border-radius: 10px;
@@ -42,53 +44,58 @@ SummaryCard.defaultProps = {
42
44
  elevation: 0,
43
45
  };
44
46
 
45
- const PlanName = styled(Typography)`
47
+ const SummaryTitle = styled(Typography)`
46
48
  margin-bottom: 16px;
49
+ font-weight: 500;
47
50
  `;
48
51
 
49
52
  const StyledDivider = styled(Divider)`
50
53
  margin: 16px 0;
51
54
  `;
52
55
 
53
- const SubtotalText = styled(Typography)`
54
- margin-top: 24px;
55
- `;
56
-
57
56
  const TotalDueText = styled(Typography)`
58
57
  margin-bottom: 8px;
59
58
  `;
60
59
 
61
60
  function resolveCheckoutButtonText({
62
61
  isLastStep,
63
- subscriptionPreview,
62
+ checkoutHasChanges,
63
+ isFreeDowngrade,
64
64
  checkoutLocalization,
65
- isFirstSubscription,
65
+ isPlanUpdate,
66
66
  }: {
67
67
  isLastStep?: boolean;
68
- isFirstSubscription?: boolean;
69
- subscriptionPreview?: SubscriptionPreview | null;
68
+ checkoutHasChanges: boolean;
69
+ isFreeDowngrade: boolean;
70
70
  checkoutLocalization: CheckoutLocalization;
71
+ isPlanUpdate?: boolean;
71
72
  }) {
72
73
  if (!isLastStep) {
73
74
  return checkoutLocalization.checkoutButton.nextText;
74
75
  }
75
76
 
76
- if (subscriptionPreview?.isPlanDowngrade) {
77
- return checkoutLocalization.checkoutButton.downgradeText;
77
+ if (!checkoutHasChanges) {
78
+ return checkoutLocalization.checkoutButton.noChangesText;
79
+ }
80
+
81
+ if (isPlanUpdate) {
82
+ return checkoutLocalization.checkoutButton.updateText;
78
83
  }
79
84
 
80
- if (!isFirstSubscription) {
81
- return checkoutLocalization.checkoutButton.upgradeText;
85
+ if (isFreeDowngrade) {
86
+ return checkoutLocalization.checkoutButton.downgradeToFreeText;
82
87
  }
83
88
 
84
- return checkoutLocalization.checkoutButton.purchaseText;
89
+ return checkoutLocalization.checkoutButton.upgradeText;
85
90
  }
86
91
 
87
92
  export const CheckoutSummary = ({
88
93
  onCheckout,
89
94
  onCheckoutCompleted,
90
95
  disablePromotionCode,
96
+ disableSuccessAnimation,
91
97
  isFreeDowngrade,
98
+ onMockCheckoutPreview,
92
99
  }: CheckoutContainerProps & { isFreeDowngrade: boolean }) => {
93
100
  const [isCheckoutCompletedSuccessfully, setIsCheckoutCompletedSuccessfully] = useState(false);
94
101
  const { setErrorMessage } = usePaymentStepModel();
@@ -103,9 +110,10 @@ export const CheckoutSummary = ({
103
110
  const [baseCharge] = baseCharges || [];
104
111
  const isLastStep = isFreeDowngrade || (progressBar.isCheckoutComplete && progressBar.isLastStep);
105
112
 
106
- const { subscriptionPreview, isFetchingSubscriptionPreview } = usePreviewSubscription();
113
+ const { subscriptionPreview, isFetchingSubscriptionPreview } = usePreviewSubscription({ onMockCheckoutPreview });
107
114
 
108
115
  const { handleSubmit, isLoading } = useSubmit({
116
+ disableSuccessAnimation,
109
117
  onCheckout,
110
118
  onCheckoutCompleted,
111
119
  onSuccess: () => {
@@ -140,25 +148,47 @@ export const CheckoutSummary = ({
140
148
  }
141
149
  };
142
150
 
151
+ const checkoutHasChanges =
152
+ !!subscriptionPreview && (!!subscriptionPreview.proration || !!subscriptionPreview.hasScheduledUpdates);
153
+ const showPromotionCodeLine = !disablePromotionCode && !isFreeDowngrade;
154
+ const showDiscountLine =
155
+ !!subscriptionPreview?.recurringSubscription &&
156
+ !!subscriptionPreview?.recurringSubscription?.discountDetails &&
157
+ !isFreeDowngrade;
158
+ const hasDiscounts = showPromotionCodeLine || showDiscountLine;
159
+
160
+ const hasPayAsYouGoCharges = usageCharges.some((price) => price.pricingModel === BillingModel.UsageBased);
161
+ const onlyPayAsYouGoCharges =
162
+ hasPayAsYouGoCharges &&
163
+ !baseCharge &&
164
+ usageCharges.every((price) => price.pricingModel === BillingModel.UsageBased);
165
+
166
+ const baseChargeLabel =
167
+ typeof checkoutLocalization.summary.baseChargeText === 'function'
168
+ ? checkoutLocalization.summary.baseChargeText({ billingPeriod: subscription.billingPeriod })
169
+ : checkoutLocalization.summary.baseChargeText;
170
+
143
171
  return (
144
172
  <Box flex={1}>
145
173
  <SummaryCard>
146
- <PlanName variant="h6">{plan?.displayName}</PlanName>
174
+ <SummaryTitle variant="h6">{checkoutLocalization.summary.title}</SummaryTitle>
175
+
176
+ <Grid display="flex" flexDirection="row" alignItems="center" marginY={2}>
177
+ <Typography variant="body1" color="primary" style={{ paddingRight: '8px' }}>
178
+ {checkoutLocalization.summary.planName({ plan: plan! })}
179
+ </Typography>
180
+ <StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1, margin: '0 !important' }} />
181
+ </Grid>
147
182
 
148
183
  {baseCharge && (
149
- <LineItemContainer>
150
- <LineItemRow>
151
- <Typography variant="h6" color="primary">
152
- {typeof checkoutLocalization.baseChargeText === 'function'
153
- ? checkoutLocalization.baseChargeText({ billingPeriod: subscription.billingPeriod })
154
- : checkoutLocalization.baseChargeText}
155
- </Typography>
156
- <Typography variant="body1" color="secondary">
157
- {currencyPriceFormatter({ amount: baseCharge.amount!, currency: baseCharge.currency })}
158
- </Typography>
159
- </LineItemRow>
160
- </LineItemContainer>
184
+ <BilledPriceLineItem
185
+ checkoutLocalization={checkoutLocalization}
186
+ label={baseChargeLabel}
187
+ quantity={1}
188
+ price={baseCharge}
189
+ />
161
190
  )}
191
+ {!baseCharge && isFreeDowngrade ? <FreeChargeLineItem label={baseChargeLabel} /> : null}
162
192
 
163
193
  {usageCharges.map((price) => {
164
194
  const priceBillableFeature = subscription.billableFeatures?.find(
@@ -167,8 +197,13 @@ export const CheckoutSummary = ({
167
197
 
168
198
  return (
169
199
  <BilledPriceLineItem
200
+ checkoutLocalization={checkoutLocalization}
170
201
  key={price.feature?.featureId}
171
- label={price.feature?.displayName || ''}
202
+ label={getFeatureDisplayNameText(
203
+ price.feature?.displayName || '',
204
+ price.feature?.units,
205
+ price.feature?.unitsPlural,
206
+ )}
172
207
  quantity={priceBillableFeature?.quantity || 1}
173
208
  price={price}
174
209
  />
@@ -177,11 +212,11 @@ export const CheckoutSummary = ({
177
212
 
178
213
  {!!subscription.addons?.length && (
179
214
  <>
180
- <Grid display="flex" flexDirection="row" alignItems="center" marginY={1}>
215
+ <Grid display="flex" flexDirection="row" alignItems="center" marginY={2}>
181
216
  <Typography variant="body1" color="primary" style={{ paddingRight: '8px' }}>
182
- {checkoutLocalization.addonsSectionTitle}
217
+ {checkoutLocalization.summary.addonsSectionTitle}
183
218
  </Typography>
184
- <StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1 }} />
219
+ <StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1, margin: '0 !important' }} />
185
220
  </Grid>
186
221
  {subscription.addons.map((addon) => {
187
222
  const addonPrice = addon.addon.pricePoints?.find(
@@ -194,6 +229,7 @@ export const CheckoutSummary = ({
194
229
 
195
230
  return (
196
231
  <BilledPriceLineItem
232
+ checkoutLocalization={checkoutLocalization}
197
233
  key={addon?.addon?.id}
198
234
  label={addon.addon.displayName}
199
235
  quantity={addonQuantity}
@@ -204,36 +240,93 @@ export const CheckoutSummary = ({
204
240
  </>
205
241
  )}
206
242
 
207
- <LineItemRow>
208
- <SubtotalText variant="h6">{checkoutLocalization.subTotalText}</SubtotalText>
209
- <SubtotalText variant="h6">
210
- <WithSkeleton isLoading={isFetchingSubscriptionPreview}>
211
- {currencyPriceFormatter({ amount: 0, ...subscriptionPreview?.subTotal })}
212
- </WithSkeleton>
213
- </SubtotalText>
214
- </LineItemRow>
215
-
216
- <StyledDivider className="stigg-checkout-summary-divider" />
243
+ {!hasDiscounts && <StyledDivider className="stigg-checkout-summary-divider" />}
244
+ {hasDiscounts && (
245
+ <Grid display="flex" flexDirection="row" alignItems="center" marginY={2}>
246
+ <Typography variant="body1" color="primary" style={{ paddingRight: '8px' }}>
247
+ {checkoutLocalization.summary.discountsSectionTitle}
248
+ </Typography>
249
+ <StyledDivider className="stigg-checkout-summary-divider" sx={{ flex: 1, margin: '0 !important' }} />
250
+ </Grid>
251
+ )}
217
252
 
218
- {!disablePromotionCode && !isFreeDowngrade && (
219
- <>
220
- <PromotionCodeSection checkoutLocalization={checkoutLocalization} />
253
+ {showPromotionCodeLine && (
254
+ <PromotionCodeSection
255
+ disabled={isLoading || isFetchingSubscriptionPreview}
256
+ checkoutLocalization={checkoutLocalization}
257
+ onMockCheckoutPreview={onMockCheckoutPreview}
258
+ />
259
+ )}
221
260
 
222
- <StyledDivider className="stigg-checkout-summary-divider" />
223
- </>
261
+ {showDiscountLine && (
262
+ <DiscountLineItem
263
+ subscriptionPreview={subscriptionPreview!}
264
+ isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
265
+ checkoutLocalization={checkoutLocalization}
266
+ />
224
267
  )}
225
268
 
226
- <DiscountLineItem
227
- subscriptionPreview={subscriptionPreview}
228
- isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
229
- />
269
+ {hasDiscounts && <StyledDivider className="stigg-checkout-summary-divider" />}
230
270
 
231
271
  <TaxLineItem
232
- subscriptionPreview={subscriptionPreview}
272
+ tax={subscriptionPreview?.recurringSubscription?.tax}
273
+ taxDetails={subscriptionPreview?.recurringSubscription?.taxDetails}
233
274
  isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
234
275
  checkoutLocalization={checkoutLocalization}
235
276
  />
236
277
 
278
+ {!isFreeDowngrade ? (
279
+ <>
280
+ <LineItemRow style={{ marginTop: 16 }}>
281
+ <Grid display="flex" flexDirection="column" container>
282
+ <Grid item display="flex" justifyContent="space-between">
283
+ <Typography variant="h6">{checkoutLocalization.summary.totalText}</Typography>
284
+ <Typography variant="h6">
285
+ <WithSkeleton isLoading={isFetchingSubscriptionPreview}>
286
+ {onlyPayAsYouGoCharges ? checkoutLocalization.summary.onlyPayAsYouGoText : null}
287
+ {!onlyPayAsYouGoCharges && hasPayAsYouGoCharges
288
+ ? checkoutLocalization.summary.startsAtText
289
+ : null}
290
+ {!onlyPayAsYouGoCharges
291
+ ? currencyPriceFormatter({
292
+ amount: 0,
293
+ ...subscriptionPreview?.recurringSubscription?.total,
294
+ minimumFractionDigits: 2,
295
+ })
296
+ : null}
297
+ </WithSkeleton>
298
+ </Typography>
299
+ </Grid>
300
+ <Grid item>
301
+ <Typography variant="body1" color="secondary">
302
+ {checkoutLocalization.summary.totalBillingPeriodText({ billingPeriod: subscription.billingPeriod })}
303
+ </Typography>
304
+ </Grid>
305
+ </Grid>
306
+ </LineItemRow>
307
+ <StyledDivider className="stigg-checkout-summary-divider" />
308
+ </>
309
+ ) : null}
310
+
311
+ {subscriptionPreview?.subTotal && subscriptionPreview?.subTotal.amount > 0 ? (
312
+ <LineItemContainer>
313
+ <LineItemRow>
314
+ <Typography variant="body1" color="secondary">
315
+ {checkoutLocalization.summary.proratedTotalDueText}
316
+ </Typography>
317
+ <Typography variant="body1" color="secondary">
318
+ <WithSkeleton isLoading={isFetchingSubscriptionPreview}>
319
+ {currencyPriceFormatter({
320
+ amount: subscriptionPreview?.subTotal.amount + (subscriptionPreview?.tax?.amount || 0),
321
+ currency: subscriptionPreview?.subTotal.currency,
322
+ minimumFractionDigits: 2,
323
+ })}
324
+ </WithSkeleton>
325
+ </Typography>
326
+ </LineItemRow>
327
+ </LineItemContainer>
328
+ ) : null}
329
+
237
330
  <AppliedCreditsLineItem
238
331
  subscriptionPreview={subscriptionPreview}
239
332
  isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
@@ -242,10 +335,10 @@ export const CheckoutSummary = ({
242
335
 
243
336
  <LineItemContainer>
244
337
  <LineItemRow>
245
- <TotalDueText variant="h6">{checkoutLocalization.totalText}</TotalDueText>
338
+ <TotalDueText variant="h6">{checkoutLocalization.summary.totalDueText}</TotalDueText>
246
339
  <TotalDueText variant="h6">
247
340
  <WithSkeleton isLoading={isFetchingSubscriptionPreview}>
248
- {currencyPriceFormatter({ amount: 0, ...subscriptionPreview?.total })}
341
+ {currencyPriceFormatter({ amount: 0, ...subscriptionPreview?.total, minimumFractionDigits: 2 })}
249
342
  </WithSkeleton>
250
343
  </TotalDueText>
251
344
  </LineItemRow>
@@ -257,12 +350,18 @@ export const CheckoutSummary = ({
257
350
  subscriptionPreview={subscriptionPreview}
258
351
  isFetchingSubscriptionPreview={isFetchingSubscriptionPreview}
259
352
  checkoutLocalization={checkoutLocalization}
353
+ billingPeriod={subscription.billingPeriod}
260
354
  />
261
355
 
262
356
  <Button
263
357
  $success={isCheckoutCompletedSuccessfully}
264
- $error={isLastStep && !!subscriptionPreview?.isPlanDowngrade}
265
- disabled={isLoading || isFetchingSubscriptionPreview || progressBar.progressBarState.isDisabled}
358
+ $error={isLastStep && isFreeDowngrade}
359
+ disabled={
360
+ isLoading ||
361
+ isFetchingSubscriptionPreview ||
362
+ progressBar.progressBarState.isDisabled ||
363
+ (isLastStep && !checkoutHasChanges)
364
+ }
266
365
  className="stigg-checkout-summary-cta-button"
267
366
  sx={{ textTransform: 'none', borderRadius: '10px', marginTop: '24px', height: '36px' }}
268
367
  variant="contained"
@@ -277,7 +376,13 @@ export const CheckoutSummary = ({
277
376
  ) : isLoading || isFetchingSubscriptionPreview ? (
278
377
  <CircularProgress size={20} sx={{ color: 'white' }} />
279
378
  ) : (
280
- resolveCheckoutButtonText({ isLastStep, subscriptionPreview, checkoutLocalization })
379
+ resolveCheckoutButtonText({
380
+ isLastStep,
381
+ checkoutHasChanges,
382
+ isFreeDowngrade,
383
+ checkoutLocalization,
384
+ isPlanUpdate: !!activeSubscription && activeSubscription?.plan.id === plan?.id,
385
+ })
281
386
  )}
282
387
  </Typography>
283
388
  </Button>
@@ -287,7 +392,9 @@ export const CheckoutSummary = ({
287
392
  showWatermark
288
393
  style={{ marginTop: 8, display: 'flex', justifyContent: 'center' }}
289
394
  />
290
- {isCheckoutCompletedSuccessfully && <CheckoutSuccess />}
395
+ {!disableSuccessAnimation && isCheckoutCompletedSuccessfully && (
396
+ <CheckoutSuccess checkoutLocalization={checkoutLocalization} />
397
+ )}
291
398
  </Box>
292
399
  );
293
400
  };