@stigg/react-sdk 5.3.0 → 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.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.3.0",
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",
@@ -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 { 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
+ };
@@ -43,7 +43,7 @@ export function getPaidPriceText({
43
43
  tiers = price.tiers;
44
44
  tierUnits = getPriceFeatureUnit(price);
45
45
 
46
- priceNumber += calculateTierPrice(price, selectedBillingPeriod, shouldShowMonthlyPriceAmount, quantity);
46
+ priceNumber += calculateTierPrice(price, quantity, selectedBillingPeriod, shouldShowMonthlyPriceAmount);
47
47
  }
48
48
  }
49
49
 
@@ -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
+ });
@@ -1,5 +1,6 @@
1
1
  import { BillingModel, TiersMode, BillingPeriod, Price, PriceTierFragment, Subscription } from '@stigg/js-client-sdk';
2
2
  import isNil from 'lodash/isNil';
3
+ import { sum } from 'lodash';
3
4
  import { PaywallPlan } from '../paywall';
4
5
  import { SelectDefaultTierIndexFn } from '../paywall/types';
5
6
 
@@ -30,7 +31,7 @@ function getAmount(amount: number, selectedBillingPeriod?: BillingPeriod, should
30
31
  return selectedBillingPeriod === BillingPeriod.Annually && shouldShowMonthlyPriceAmount ? amount / 12 : amount;
31
32
  }
32
33
 
33
- export function calculateTierPriceVolume(
34
+ function calculateTierPriceVolume(
34
35
  currentTier: PriceTierFragment,
35
36
  perUnitQuantity: number,
36
37
  selectedBillingPeriod?: BillingPeriod,
@@ -50,11 +51,55 @@ export function calculateTierPriceVolume(
50
51
  return amount;
51
52
  }
52
53
 
54
+ export function calculateTierPriceGraduated(
55
+ tiers: PriceTierFragment[],
56
+ unitQuantity: number,
57
+ selectedBillingPeriod?: BillingPeriod,
58
+ shouldShowMonthlyPriceAmount?: boolean,
59
+ ): { total: number; breakdown: Array<{ unitQuantity: number; amount: number }> } {
60
+ let remainingQuantity = unitQuantity;
61
+ let prevUpTo = 0;
62
+ let currentTierIndex = 0;
63
+
64
+ const breakdown: Array<{ unitQuantity: number; amount: number }> = [];
65
+
66
+ while (remainingQuantity > 0 && currentTierIndex < tiers.length) {
67
+ const tier = tiers[currentTierIndex];
68
+ const { upTo } = tier;
69
+
70
+ if (isNil(upTo)) {
71
+ breakdown.push({
72
+ unitQuantity: remainingQuantity,
73
+ amount: calculateTierPriceVolume(tier, remainingQuantity, selectedBillingPeriod, shouldShowMonthlyPriceAmount),
74
+ });
75
+ remainingQuantity = 0;
76
+ } else {
77
+ const unitsInTier = upTo - prevUpTo;
78
+ const consumed = Math.min(remainingQuantity, unitsInTier);
79
+ breakdown.push({
80
+ unitQuantity: consumed,
81
+ amount: calculateTierPriceVolume(tier, consumed, selectedBillingPeriod, shouldShowMonthlyPriceAmount),
82
+ });
83
+ remainingQuantity -= consumed;
84
+ prevUpTo = upTo;
85
+ }
86
+
87
+ currentTierIndex += 1;
88
+ }
89
+
90
+ const total = sum(breakdown.map(({ amount }) => amount));
91
+
92
+ return {
93
+ breakdown,
94
+ total,
95
+ };
96
+ }
97
+
53
98
  export function calculateTierPrice(
54
- price: Price,
55
- selectedBillingPeriod: BillingPeriod,
56
- shouldShowMonthlyPriceAmount: boolean,
99
+ price: Pick<Price, 'tiers' | 'tiersMode'>,
57
100
  unitQuantity: number,
101
+ selectedBillingPeriod?: BillingPeriod,
102
+ shouldShowMonthlyPriceAmount?: boolean,
58
103
  ): number {
59
104
  if (!price.tiers) {
60
105
  return 0;
@@ -65,6 +110,10 @@ export function calculateTierPrice(
65
110
  const currentTier = getTierByQuantity(price.tiers, unitQuantity);
66
111
  return calculateTierPriceVolume(currentTier, unitQuantity, selectedBillingPeriod, shouldShowMonthlyPriceAmount);
67
112
  }
113
+ case TiersMode.Graduated: {
114
+ return calculateTierPriceGraduated(price.tiers, unitQuantity, selectedBillingPeriod, shouldShowMonthlyPriceAmount)
115
+ .total;
116
+ }
68
117
  default:
69
118
  return 0;
70
119
  }