@stigg/react-sdk 5.2.1 → 5.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 (33) hide show
  1. package/dist/components/common/TiersSelectContainer.d.ts +2 -4
  2. package/dist/components/common/VolumeBulkSelect.d.ts +1 -1
  3. package/dist/components/common/VolumePerUnitInput.d.ts +1 -1
  4. package/dist/components/paywall/PlanOfferingButton.d.ts +2 -3
  5. package/dist/components/paywall/PlanPrice.d.ts +2 -4
  6. package/dist/components/utils/fixtures/price.fixtures.d.ts +7 -0
  7. package/dist/components/utils/getPaidPriceText.d.ts +2 -3
  8. package/dist/components/utils/getPlanPrice.d.ts +1 -1
  9. package/dist/components/utils/priceTierUtils.d.ts +11 -6
  10. package/dist/components/utils/priceTierUtils.spec.d.ts +1 -0
  11. package/dist/react-sdk.cjs.development.js +137 -188
  12. package/dist/react-sdk.cjs.development.js.map +1 -1
  13. package/dist/react-sdk.cjs.production.min.js +1 -1
  14. package/dist/react-sdk.cjs.production.min.js.map +1 -1
  15. package/dist/react-sdk.esm.js +140 -191
  16. package/dist/react-sdk.esm.js.map +1 -1
  17. package/package.json +2 -2
  18. package/src/components/checkout/hooks/usePlanStepModel.ts +1 -1
  19. package/src/components/checkout/steps/plan/CheckoutChargeList.tsx +4 -11
  20. package/src/components/checkout/summary/components/LineItems.tsx +5 -8
  21. package/src/components/common/TiersSelectContainer.tsx +5 -16
  22. package/src/components/common/VolumeBulkSelect.tsx +7 -4
  23. package/src/components/common/VolumePerUnitInput.tsx +3 -5
  24. package/src/components/paywall/Paywall.tsx +2 -2
  25. package/src/components/paywall/PlanOffering.tsx +6 -27
  26. package/src/components/paywall/PlanOfferingButton.tsx +2 -8
  27. package/src/components/paywall/PlanPrice.tsx +4 -23
  28. package/src/components/utils/calculateDiscountRate.ts +2 -2
  29. package/src/components/utils/fixtures/price.fixtures.ts +42 -0
  30. package/src/components/utils/getPaidPriceText.ts +6 -24
  31. package/src/components/utils/getPlanPrice.ts +0 -2
  32. package/src/components/utils/priceTierUtils.spec.ts +84 -0
  33. package/src/components/utils/priceTierUtils.ts +78 -73
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.2.1",
2
+ "version": "5.4.0",
3
3
  "license": "MIT",
4
4
  "main": "dist/index.js",
5
5
  "typings": "dist/index.d.ts",
@@ -13,7 +13,7 @@
13
13
  "scripts": {
14
14
  "start": "tsdx watch",
15
15
  "build": "tsdx build",
16
- "test": "tsdx test --passWithNoTests",
16
+ "test": "tsdx test",
17
17
  "lint": "eslint src --ext .ts,.tsx --max-warnings=0",
18
18
  "lint-fix": "yarn run lint --fix",
19
19
  "size": "size-limit",
@@ -75,7 +75,7 @@ function getBillableFeatures(
75
75
 
76
76
  if (price.isTieredPrice && !hasTierWithUnitPrice(price.tiers)) {
77
77
  const tier = getTierByQuantity(price.tiers!, quantity);
78
- quantity = tier!.upTo!;
78
+ quantity = tier.upTo!;
79
79
  }
80
80
 
81
81
  return {
@@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
3
3
  import styled from '@emotion/styled';
4
4
  import Box from '@mui/material/Box';
5
5
  import { BillableFeatureInput } from '@stigg/api-client-js/src/generated/sdk';
6
- import { BillingModel, BillingPeriod, Plan, Price, PriceTierFragment } from '@stigg/js-client-sdk';
6
+ import { BillingModel, BillingPeriod, Plan, Price } from '@stigg/js-client-sdk';
7
7
 
8
8
  import { Typography } from '../../../common/Typography';
9
9
  import { useChargesSort } from '../../../hooks/useChargeSort';
@@ -12,7 +12,6 @@ import { currencyPriceFormatter } from '../../../utils/currencyUtils';
12
12
  import { InputField } from '../../components';
13
13
  import { useCheckoutModel, usePlanStepModel, useProgressBarModel } from '../../hooks';
14
14
  import { TiersSelectContainer } from '../../../common/TiersSelectContainer';
15
- import { getTierByQuantity } from '../../../utils/priceTierUtils';
16
15
  import { getValidPriceQuantity } from '../../../utils/priceUtils';
17
16
  import { getFeatureDisplayNameText } from '../../../utils/getFeatureName';
18
17
 
@@ -104,20 +103,14 @@ export function PlanCharge({
104
103
  chargeRow += ' / unit';
105
104
  }
106
105
  } else if (charge.isTieredPrice && charge.tiersMode && featureId) {
107
- const tier = getTierByQuantity(charge.tiers!, billableFeature!.quantity || 1);
106
+ const quantity = billableFeature?.quantity || 1;
108
107
  chargeRow = (
109
108
  <TiersSelectContainer
110
109
  componentId={`${featureId}-tiers`}
111
110
  tiers={charge.tiers}
112
- selectedTier={tier}
113
- handleTierChange={(tier: PriceTierFragment) => {
114
- if (tier.upTo) {
115
- setBillableFeature(featureId, tier.upTo);
116
- }
117
- }}
118
111
  tiersMode={charge.tiersMode}
119
- perUnitQuantity={billableFeature?.quantity}
120
- handlePerUnitQuantityChange={(quantity: number) => {
112
+ value={quantity}
113
+ handleChange={(quantity: number) => {
121
114
  setBillableFeature(featureId, quantity);
122
115
  }}
123
116
  width={120}
@@ -10,7 +10,7 @@ import {
10
10
  } from '@stigg/js-client-sdk';
11
11
  import { Typography } from '../../../common/Typography';
12
12
  import { currencyPriceFormatter } from '../../../utils/currencyUtils';
13
- import { calculateTierPriceVolume, getTierByQuantity } from '../../../utils/priceTierUtils';
13
+ import { calculateTierPrice } from '../../../utils/priceTierUtils';
14
14
  import { WithSkeleton } from './WithSkeleton';
15
15
  import { Skeleton } from '../../components/Skeletons.style';
16
16
  import { CheckoutLocalization } from '../../configurations/textOverrides';
@@ -74,14 +74,11 @@ export const BilledPriceLineItem = ({
74
74
 
75
75
  let amount;
76
76
  if (price.isTieredPrice) {
77
- const tier = getTierByQuantity(price.tiers!, quantity);
78
- amount = calculateTierPriceVolume(tier!, quantity);
77
+ amount = calculateTierPrice(price, quantity);
79
78
  } else {
80
- amount = price.amount!;
79
+ amount = price.amount! * quantity;
81
80
  }
82
81
 
83
- const totalLineAmount = price.isTieredPrice ? amount : amount * quantity;
84
-
85
82
  return (
86
83
  <LineItemContainer>
87
84
  <LineItemRow style={{ alignItems: 'flex-start' }}>
@@ -91,7 +88,7 @@ export const BilledPriceLineItem = ({
91
88
  </Typography>
92
89
  {(quantity > 1 || billingPeriod === BillingPeriod.Annually) && (
93
90
  <Typography variant="body1" color="secondary">
94
- {getPriceString({ amount: totalLineAmount, price, quantity })}
91
+ {getPriceString({ amount, price, quantity })}
95
92
  </Typography>
96
93
  )}
97
94
  </Grid>
@@ -99,7 +96,7 @@ export const BilledPriceLineItem = ({
99
96
  {isPayAsYouGo && <PayAsYouGoPriceTooltip checkoutLocalization={checkoutLocalization} />}
100
97
  <Typography variant="body1" color="secondary" style={{ wordBreak: 'break-word' }}>
101
98
  {currencyPriceFormatter({
102
- amount: totalLineAmount,
99
+ amount,
103
100
  currency: price.currency,
104
101
  minimumFractionDigits: 2,
105
102
  })}
@@ -1,7 +1,7 @@
1
1
  import { PriceTierFragment, TiersMode } from '@stigg/js-client-sdk';
2
2
  import React from 'react';
3
3
  import styled from '@emotion/styled/macro';
4
- import { getTierByQuantity, hasTierWithUnitPrice } from '../utils/priceTierUtils';
4
+ import { hasTierWithUnitPrice } from '../utils/priceTierUtils';
5
5
  import { Typography } from './Typography';
6
6
  import { VolumePerUnitInput } from './VolumePerUnitInput';
7
7
  import { VolumeBulkSelect } from './VolumeBulkSelect';
@@ -10,11 +10,9 @@ export type TiersSelectContainerProps = {
10
10
  componentId: string;
11
11
  tiers?: PriceTierFragment[] | null;
12
12
  tierUnits?: string;
13
- selectedTier?: PriceTierFragment;
14
- handleTierChange: (tier: PriceTierFragment) => void;
15
13
  tiersMode?: TiersMode | null;
16
- handlePerUnitQuantityChange: (quantity: number) => void;
17
- perUnitQuantity?: number;
14
+ handleChange: (quantity: number) => void;
15
+ value: number;
18
16
  width?: number;
19
17
  };
20
18
 
@@ -23,20 +21,11 @@ const TiersSelectLayout = styled(Typography)`
23
21
  `;
24
22
 
25
23
  export function TiersSelectContainer(props: TiersSelectContainerProps) {
26
- const { tiers, handleTierChange } = props;
27
- const handleChange = (quantity: number) => {
28
- if (!tiers) return;
29
-
30
- handleTierChange(getTierByQuantity(tiers, quantity)!);
31
- };
24
+ const { tiers } = props;
32
25
 
33
26
  return (
34
27
  <TiersSelectLayout as="div" className="stigg-price-tier-select">
35
- {hasTierWithUnitPrice(tiers) ? (
36
- <VolumePerUnitInput handleChange={handleChange} {...props} />
37
- ) : (
38
- <VolumeBulkSelect handleChange={handleChange} {...props} />
39
- )}
28
+ {hasTierWithUnitPrice(tiers) ? <VolumePerUnitInput {...props} /> : <VolumeBulkSelect {...props} />}
40
29
  </TiersSelectLayout>
41
30
  );
42
31
  }
@@ -29,12 +29,12 @@ const TierInput = styled(OutlinedInput)`
29
29
  `;
30
30
 
31
31
  export function VolumeBulkSelect({
32
- selectedTier,
32
+ value,
33
33
  componentId,
34
34
  tierUnits,
35
35
  tiers,
36
36
  handleChange,
37
- }: Pick<TiersSelectContainerProps, 'selectedTier' | 'componentId' | 'tiers' | 'tierUnits'> & {
37
+ }: Pick<TiersSelectContainerProps, 'value' | 'componentId' | 'tiers' | 'tierUnits'> & {
38
38
  handleChange: (quantity: number) => void;
39
39
  }) {
40
40
  if (!tiers) {
@@ -43,10 +43,13 @@ export function VolumeBulkSelect({
43
43
 
44
44
  return (
45
45
  <TierSelect
46
- value={selectedTier ? selectedTier.upTo?.toString() : tiers[0].upTo?.toString()}
46
+ value={value.toString()}
47
47
  fullWidth
48
48
  onChange={(event) => {
49
- handleChange(event.target.value as number);
49
+ const quantity = event?.target?.value ? Number(event?.target?.value) : null;
50
+ if (quantity) {
51
+ handleChange(quantity);
52
+ }
50
53
  }}
51
54
  id={componentId}
52
55
  input={<TierInput />}
@@ -7,10 +7,9 @@ import { TiersSelectContainerProps } from './TiersSelectContainer';
7
7
  export function VolumePerUnitInput({
8
8
  width,
9
9
  tierUnits,
10
- perUnitQuantity,
11
- handlePerUnitQuantityChange,
10
+ value,
12
11
  handleChange,
13
- }: Pick<TiersSelectContainerProps, 'width' | 'tierUnits' | 'perUnitQuantity' | 'handlePerUnitQuantityChange'> & {
12
+ }: Pick<TiersSelectContainerProps, 'width' | 'tierUnits' | 'value'> & {
14
13
  handleChange: (quantity: number) => void;
15
14
  }) {
16
15
  return (
@@ -27,12 +26,11 @@ export function VolumePerUnitInput({
27
26
  </InputAdornment>
28
27
  ),
29
28
  }}
30
- value={perUnitQuantity || 1}
29
+ value={value}
31
30
  onChange={(event) => {
32
31
  const quantity = event?.target?.value ? Number(event?.target?.value) : null;
33
32
  if (quantity) {
34
33
  handleChange(quantity);
35
- handlePerUnitQuantityChange(quantity);
36
34
  }
37
35
  }}
38
36
  />
@@ -17,7 +17,7 @@ import { useStiggContext } from '../..';
17
17
  import { hasPricePointsForPlans } from './utils/hasPricePoints';
18
18
  import { getPlansToDisplay } from './utils/getPlansToDisplay';
19
19
  import { getPlanPrice } from '../utils/getPlanPrice';
20
- import { getSelectedTier } from '../utils/priceTierUtils';
20
+ import { getTiersPerUnitQuantities } from '../utils/priceTierUtils';
21
21
 
22
22
  const PaywallPlansContainer = styled.div`
23
23
  color: ${({ theme }) => theme.stigg.palette.text.primary};
@@ -123,7 +123,7 @@ export const Paywall = ({
123
123
  return (
124
124
  !isCustomerInCustomPlan &&
125
125
  plansToShow.some((plan) => {
126
- const tiers = getSelectedTier(plan, selectedBillingPeriod, currentSubscription, {});
126
+ const tiers = getTiersPerUnitQuantities(plan, selectedBillingPeriod, currentSubscription);
127
127
  return Object.values(tiers).length > 0;
128
128
  })
129
129
  );
@@ -1,12 +1,5 @@
1
- import React, { useEffect, useState } from 'react';
2
- import {
3
- BillableFeature,
4
- BillingPeriod,
5
- Customer,
6
- PriceTierFragment,
7
- PricingType,
8
- Subscription,
9
- } from '@stigg/js-client-sdk';
1
+ import React, { useState } from 'react';
2
+ import { BillableFeature, BillingPeriod, Customer, PricingType, Subscription } from '@stigg/js-client-sdk';
10
3
  import styled from '@emotion/styled/macro';
11
4
  import classNames from 'classnames';
12
5
  import Grid from '@mui/material/Grid';
@@ -18,7 +11,7 @@ import { flexLayoutMapper } from '../../theme/getResolvedTheme';
18
11
  import { Typography } from '../common/Typography';
19
12
  import MiniSchedule from '../../assets/mini-schedule.svg';
20
13
  import { PlanPrice } from './PlanPrice';
21
- import { getSelectedTier, getSelectedTierQuantityBeFeature } from '../utils/priceTierUtils';
14
+ import { getTiersPerUnitQuantities } from '../utils/priceTierUtils';
22
15
 
23
16
  const PlanOfferingButtonHeight = '66px';
24
17
 
@@ -167,28 +160,17 @@ export function PlanOffering({
167
160
  );
168
161
  }
169
162
 
170
- const [perUnitQuantityByFeature, setPerUnitQuantityByFeature] = useState<Record<string, number>>({});
171
- const [selectedTierByFeature, setSelectedTierByFeature] = useState<Record<string, PriceTierFragment>>(
172
- getSelectedTier(plan, billingPeriod, currentSubscription, {}, selectDefaultTierIndex),
163
+ const [perUnitQuantityByFeature, setPerUnitQuantityByFeature] = useState<Record<string, number>>(
164
+ getTiersPerUnitQuantities(plan, billingPeriod, currentSubscription, selectDefaultTierIndex),
173
165
  );
174
166
 
175
- useEffect(() => {
176
- setSelectedTierByFeature(getSelectedTier(plan, billingPeriod, currentSubscription, selectedTierByFeature));
177
- setPerUnitQuantityByFeature(getSelectedTierQuantityBeFeature(plan, billingPeriod, currentSubscription));
178
- // eslint-disable-next-line react-hooks/exhaustive-deps
179
- }, [billingPeriod, currentSubscription, plan]);
180
-
181
167
  const onPlanButtonClick = (intentionType: SubscribeIntentionType) => {
182
- const tierBillableFeatures: BillableFeature[] = Object.keys(selectedTierByFeature).map((featureId) => ({
183
- featureId,
184
- quantity: selectedTierByFeature[featureId].upTo || 0,
185
- }));
186
168
  const perUnitBillableFeatures: BillableFeature[] = Object.keys(perUnitQuantityByFeature).map((featureId) => ({
187
169
  featureId,
188
170
  quantity: perUnitQuantityByFeature[featureId],
189
171
  }));
190
172
 
191
- return onPlanSelected(intentionType, { ...tierBillableFeatures, ...perUnitBillableFeatures });
173
+ return onPlanSelected(intentionType, perUnitBillableFeatures);
192
174
  };
193
175
 
194
176
  return (
@@ -216,8 +198,6 @@ export function PlanOffering({
216
198
  withUnitPriceRow={withUnitPriceRow}
217
199
  withStartingAtRow={withStartingAtRow}
218
200
  withTiersRow={withTiersRow}
219
- selectedTierByFeature={selectedTierByFeature}
220
- setSelectedTierByFeature={setSelectedTierByFeature}
221
201
  plan={plan}
222
202
  billingPeriod={billingPeriod}
223
203
  paywallLocale={paywallLocale}
@@ -239,7 +219,6 @@ export function PlanOffering({
239
219
  onPlanSelected={onPlanButtonClick}
240
220
  paywallLocale={paywallLocale}
241
221
  withTrialLeftRow={withTrialLeftRow}
242
- selectedTierByFeature={selectedTierByFeature}
243
222
  perUnitQuantityByFeature={perUnitQuantityByFeature}
244
223
  />
245
224
  ) : (
@@ -1,5 +1,5 @@
1
1
  import React, { useState } from 'react';
2
- import { BillingPeriod, Customer, PriceTierFragment, PricingType, Subscription } from '@stigg/js-client-sdk';
2
+ import { BillingPeriod, Customer, PricingType, Subscription } from '@stigg/js-client-sdk';
3
3
  import { isFunction } from 'lodash';
4
4
  import ClipLoader from 'react-spinners/ClipLoader';
5
5
  import styled from '@emotion/styled/macro';
@@ -82,7 +82,6 @@ type PlanOfferingButtonProps = {
82
82
  paywallLocale: PaywallLocalization;
83
83
  onPlanSelected: (intentionType: SubscribeIntentionType) => void | Promise<void>;
84
84
  withTrialLeftRow: boolean;
85
- selectedTierByFeature: Record<string, PriceTierFragment>;
86
85
  perUnitQuantityByFeature: Record<string, number>;
87
86
  };
88
87
 
@@ -96,7 +95,6 @@ export function PlanOfferingButton({
96
95
  paywallLocale,
97
96
  withTrialLeftRow,
98
97
  currentSubscription,
99
- selectedTierByFeature,
100
98
  perUnitQuantityByFeature,
101
99
  }: PlanOfferingButtonProps) {
102
100
  const theme = useTheme();
@@ -144,11 +142,7 @@ export function PlanOfferingButton({
144
142
  isSameBillingPeriod ||
145
143
  (plan.pricingType && [PricingType.Free, PricingType.Custom].includes(plan.pricingType))
146
144
  ) {
147
- const planComparison = compareSelectedTierToCurrentTier(
148
- selectedTierByFeature,
149
- perUnitQuantityByFeature,
150
- currentSubscription,
151
- );
145
+ const planComparison = compareSelectedTierToCurrentTier(perUnitQuantityByFeature, currentSubscription);
152
146
  switch (planComparison) {
153
147
  case PriceTierComparison.Lower:
154
148
  buttonProps.intentionType = SubscribeIntentionType.CHANGE_UNIT_QUANTITY;
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import styled from '@emotion/styled/macro';
3
- import { BillingPeriod, PriceTierFragment } from '@stigg/js-client-sdk';
3
+ import { BillingPeriod } from '@stigg/js-client-sdk';
4
4
  import { PaywallPlan } from './types';
5
5
  import { PaywallLocalization } from './paywallTextOverrides';
6
6
  import { getPlanPrice } from '../utils/getPlanPrice';
@@ -57,8 +57,6 @@ export const PlanPrice = ({
57
57
  withUnitPriceRow,
58
58
  withStartingAtRow,
59
59
  withTiersRow,
60
- selectedTierByFeature,
61
- setSelectedTierByFeature,
62
60
  plan,
63
61
  billingPeriod,
64
62
  paywallLocale,
@@ -72,8 +70,6 @@ export const PlanPrice = ({
72
70
  withUnitPriceRow: boolean;
73
71
  withStartingAtRow: boolean;
74
72
  withTiersRow: boolean;
75
- selectedTierByFeature: Record<string, PriceTierFragment>;
76
- setSelectedTierByFeature: React.Dispatch<React.SetStateAction<Record<string, PriceTierFragment>>>;
77
73
  perUnitQuantityByFeature: Record<string, number>;
78
74
  setPerUnitQuantityByFeature: React.Dispatch<React.SetStateAction<Record<string, number>>>;
79
75
  plan: PaywallPlan;
@@ -89,7 +85,6 @@ export const PlanPrice = ({
89
85
  paywallLocale,
90
86
  locale,
91
87
  hasMonthlyPrice,
92
- selectedTierByFeature,
93
88
  perUnitQuantityByFeature,
94
89
  );
95
90
 
@@ -98,19 +93,7 @@ export const PlanPrice = ({
98
93
  return planPrice.billingPeriod === billingPeriod && planPrice.isTieredPrice;
99
94
  });
100
95
  const featureId = tieredPrice ? tieredPrice.feature!.featureId : undefined;
101
- const selectedTier = featureId ? selectedTierByFeature[featureId] : undefined;
102
- const perUnitQuantity = featureId ? perUnitQuantityByFeature[featureId] : undefined;
103
-
104
- const handleTierChange = (tier: PriceTierFragment) => {
105
- if (!featureId) {
106
- return;
107
- }
108
-
109
- setSelectedTierByFeature((prevState) => ({
110
- ...prevState,
111
- [featureId]: tier,
112
- }));
113
- };
96
+ const perUnitQuantity = perUnitQuantityByFeature[featureId || ''] ?? 1;
114
97
 
115
98
  const handlePerUnitQuantityChange = (quantity: number) => {
116
99
  if (!featureId) {
@@ -161,11 +144,9 @@ export const PlanPrice = ({
161
144
  componentId={`${plan.id}_${featureId}_tier`}
162
145
  tiers={tiers}
163
146
  tierUnits={tierUnits}
164
- selectedTier={selectedTier}
165
- handleTierChange={handleTierChange}
166
147
  tiersMode={tieredPrice?.tiersMode}
167
- handlePerUnitQuantityChange={handlePerUnitQuantityChange}
168
- perUnitQuantity={perUnitQuantity}
148
+ handleChange={handlePerUnitQuantityChange}
149
+ value={perUnitQuantity}
169
150
  />
170
151
  ) : null}
171
152
  </>
@@ -1,7 +1,7 @@
1
1
  import { BillingPeriod, PaywallCalculatedPricePoint, Price } from '@stigg/js-client-sdk';
2
2
  import isNil from 'lodash/isNil';
3
3
  import { PaywallPlan } from '../paywall';
4
- import { calculateTierPriceVolume } from './priceTierUtils';
4
+ import { calculateTierPrice } from './priceTierUtils';
5
5
 
6
6
  export function calculateDiscountRate(monthlyPrice?: number | null, annuallyPrice?: number | null) {
7
7
  if (!isNil(monthlyPrice) && !isNil(annuallyPrice)) {
@@ -27,7 +27,7 @@ function getPlanBillingPeriodAmount(plan: PaywallPlan, billingPeriod: BillingPer
27
27
  });
28
28
 
29
29
  if (tieredPrice) {
30
- return calculateTierPriceVolume(tieredPrice.tiers![0], 1);
30
+ return calculateTierPrice(tieredPrice, 1);
31
31
  }
32
32
  }
33
33
 
@@ -0,0 +1,42 @@
1
+ import { Currency, Price, TiersMode } from '@stigg/js-client-sdk';
2
+
3
+ export const money = (amount: number, currency: Currency) => ({ amount, currency });
4
+
5
+ export const priceTiersFlat = (tiersMode: TiersMode, currency: Currency): Pick<Price, 'tiers' | 'tiersMode'> => {
6
+ return {
7
+ tiersMode,
8
+ tiers: [
9
+ {
10
+ upTo: 10,
11
+ flatPrice: money(100, currency),
12
+ },
13
+ {
14
+ upTo: 20,
15
+ flatPrice: money(180, currency),
16
+ },
17
+ {
18
+ upTo: 30,
19
+ flatPrice: money(240, currency),
20
+ },
21
+ ],
22
+ };
23
+ };
24
+
25
+ export const priceTiersUnit = (tiersMode: TiersMode, currency: Currency): Pick<Price, 'tiers' | 'tiersMode'> => {
26
+ return {
27
+ tiersMode,
28
+ tiers: [
29
+ {
30
+ upTo: 10,
31
+ unitPrice: money(10, currency),
32
+ },
33
+ {
34
+ upTo: 20,
35
+ unitPrice: money(9, currency),
36
+ },
37
+ {
38
+ unitPrice: money(8, currency),
39
+ },
40
+ ],
41
+ };
42
+ };
@@ -1,10 +1,4 @@
1
- import {
2
- BillingModel,
3
- BillingPeriod,
4
- PaywallCalculatedPricePoint,
5
- Price,
6
- PriceTierFragment,
7
- } from '@stigg/js-client-sdk';
1
+ import { BillingModel, BillingPeriod, PaywallCalculatedPricePoint, Price } from '@stigg/js-client-sdk';
8
2
  import { currencyPriceFormatter } from './currencyUtils';
9
3
  import { PlanPriceText } from './getPlanPrice';
10
4
  import { calculateTierPrice, getPriceFeatureUnit } from './priceTierUtils';
@@ -16,7 +10,6 @@ type GetPaidPriceTextParams = {
16
10
  selectedBillingPeriod: BillingPeriod;
17
11
  locale: string;
18
12
  shouldShowMonthlyPriceAmount: boolean;
19
- selectedTierByFeature: Record<string, PriceTierFragment>;
20
13
  paywallLocale: PaywallLocalization;
21
14
  perUnitQuantityByFeature?: Record<string, number>;
22
15
  };
@@ -27,7 +20,6 @@ export function getPaidPriceText({
27
20
  selectedBillingPeriod,
28
21
  locale,
29
22
  shouldShowMonthlyPriceAmount,
30
- selectedTierByFeature,
31
23
  paywallLocale,
32
24
  perUnitQuantityByFeature,
33
25
  }: GetPaidPriceTextParams): PlanPriceText {
@@ -46,22 +38,12 @@ export function getPaidPriceText({
46
38
  let unit = pricePeriod;
47
39
 
48
40
  for (const price of planPrices) {
49
- if (price.isTieredPrice) {
50
- let currentTier = price.tiers![0];
51
- if (selectedTierByFeature[price.feature!.featureId]) {
52
- currentTier = selectedTierByFeature[price.feature!.featureId] || currentTier;
41
+ if (price.isTieredPrice && price.tiers) {
42
+ const quantity = perUnitQuantityByFeature?.[price.feature!.featureId] || 1;
43
+ tiers = price.tiers;
44
+ tierUnits = getPriceFeatureUnit(price);
53
45
 
54
- tiers = price.tiers;
55
- tierUnits = getPriceFeatureUnit(price);
56
- }
57
-
58
- priceNumber += calculateTierPrice(
59
- price,
60
- currentTier,
61
- selectedBillingPeriod,
62
- shouldShowMonthlyPriceAmount,
63
- perUnitQuantityByFeature?.[price.feature!.featureId] || 1,
64
- );
46
+ priceNumber += calculateTierPrice(price, quantity, selectedBillingPeriod, shouldShowMonthlyPriceAmount);
65
47
  }
66
48
  }
67
49
 
@@ -15,7 +15,6 @@ export function getPlanPrice(
15
15
  paywallLocale: PaywallLocalization,
16
16
  locale: string,
17
17
  shouldShowMonthlyPriceAmount: boolean,
18
- selectedTierByFeature?: Record<string, PriceTierFragment>,
19
18
  perUnitQuantityByFeature?: Record<string, number>,
20
19
  ): PlanPriceText {
21
20
  switch (plan.pricingType) {
@@ -48,7 +47,6 @@ export function getPlanPrice(
48
47
  planPrices,
49
48
  paywallCalculatedPrice,
50
49
  selectedBillingPeriod: billingPeriod,
51
- selectedTierByFeature: selectedTierByFeature || {},
52
50
  perUnitQuantityByFeature: perUnitQuantityByFeature || {},
53
51
  };
54
52
 
@@ -0,0 +1,84 @@
1
+ import { Currency, TiersMode } from '@stigg/js-client-sdk';
2
+ import { priceTiersFlat, priceTiersUnit } from './fixtures/price.fixtures';
3
+ import { calculateTierPrice, calculateTierPriceGraduated } from './priceTierUtils';
4
+
5
+ describe('getPrice tests', () => {
6
+ describe('volume flat price', () => {
7
+ test('should get total price', () => {
8
+ const price = priceTiersFlat(TiersMode.Volume, Currency.Usd);
9
+ expect(calculateTierPrice(price, 1)).toEqual(100);
10
+ expect(calculateTierPrice(price, 3)).toEqual(100);
11
+ expect(calculateTierPrice(price, 7)).toEqual(100);
12
+ expect(calculateTierPrice(price, 10)).toEqual(100);
13
+ expect(calculateTierPrice(price, 11)).toEqual(180);
14
+ expect(calculateTierPrice(price, 14)).toEqual(180);
15
+ expect(calculateTierPrice(price, 20)).toEqual(180);
16
+ expect(calculateTierPrice(price, 21)).toEqual(240);
17
+ expect(calculateTierPrice(price, 25)).toEqual(240);
18
+ expect(calculateTierPrice(price, 30)).toEqual(240);
19
+
20
+ // questionable behaviour, but that's the current implementation :)
21
+ expect(calculateTierPrice(price, 35)).toEqual(240);
22
+ expect(calculateTierPrice(price, 250)).toEqual(240);
23
+ });
24
+ });
25
+
26
+ describe('volume per unit', () => {
27
+ test('should get total price', () => {
28
+ const price = priceTiersUnit(TiersMode.Volume, Currency.Usd);
29
+ expect(calculateTierPrice(price, 1)).toEqual(10);
30
+ expect(calculateTierPrice(price, 3)).toEqual(30);
31
+ expect(calculateTierPrice(price, 7)).toEqual(70);
32
+ expect(calculateTierPrice(price, 10)).toEqual(100);
33
+ expect(calculateTierPrice(price, 11)).toEqual(99);
34
+ expect(calculateTierPrice(price, 14)).toEqual(126);
35
+ expect(calculateTierPrice(price, 20)).toEqual(180);
36
+ expect(calculateTierPrice(price, 21)).toEqual(168);
37
+ expect(calculateTierPrice(price, 25)).toEqual(200);
38
+ expect(calculateTierPrice(price, 30)).toEqual(240);
39
+ expect(calculateTierPrice(price, 35)).toEqual(280);
40
+ expect(calculateTierPrice(price, 240)).toEqual(1_920);
41
+ });
42
+ });
43
+
44
+ describe('graduated', () => {
45
+ test('should get total price', () => {
46
+ const price = priceTiersUnit(TiersMode.Graduated, Currency.Usd);
47
+ expect(calculateTierPrice(price, 1)).toEqual(10);
48
+ expect(calculateTierPrice(price, 3)).toEqual(30);
49
+ expect(calculateTierPrice(price, 7)).toEqual(70);
50
+ expect(calculateTierPrice(price, 10)).toEqual(100);
51
+ expect(calculateTierPrice(price, 11)).toEqual(109);
52
+ expect(calculateTierPrice(price, 14)).toEqual(136);
53
+ expect(calculateTierPrice(price, 20)).toEqual(190);
54
+ expect(calculateTierPrice(price, 21)).toEqual(198);
55
+ expect(calculateTierPrice(price, 25)).toEqual(230);
56
+ expect(calculateTierPrice(price, 30)).toEqual(270);
57
+ expect(calculateTierPrice(price, 35)).toEqual(310);
58
+ expect(calculateTierPrice(price, 240)).toEqual(1_950);
59
+ });
60
+
61
+ test('should return nested breakdown', () => {
62
+ const price = priceTiersUnit(TiersMode.Graduated, Currency.Usd);
63
+
64
+ const result1 = calculateTierPriceGraduated(price.tiers!, 10);
65
+ expect(result1.total).toEqual(100);
66
+ expect(result1.breakdown).toEqual([{ unitQuantity: 10, amount: 100 }]);
67
+
68
+ const result2 = calculateTierPriceGraduated(price.tiers!, 13);
69
+ expect(result2.total).toEqual(127);
70
+ expect(result2.breakdown).toEqual([
71
+ { unitQuantity: 10, amount: 100 },
72
+ { unitQuantity: 3, amount: 27 },
73
+ ]);
74
+
75
+ const result3 = calculateTierPriceGraduated(price.tiers!, 35);
76
+ expect(result3.total).toEqual(310);
77
+ expect(result3.breakdown).toEqual([
78
+ { unitQuantity: 10, amount: 100 },
79
+ { unitQuantity: 10, amount: 90 },
80
+ { unitQuantity: 15, amount: 120 },
81
+ ]);
82
+ });
83
+ });
84
+ });