@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
@@ -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
 
@@ -11,9 +12,7 @@ export function getPriceFeatureUnit(price: Price) {
11
12
  return (price.feature.unitQuantity !== 1 ? price.feature.unitsPlural : price.feature.units) || '';
12
13
  }
13
14
 
14
- export function getTierByQuantity(tiers: PriceTierFragment[], quantity: number) {
15
- if (!tiers) return undefined;
16
-
15
+ export function getTierByQuantity(tiers: PriceTierFragment[], quantity: number): PriceTierFragment {
17
16
  const ascendingTiers = [...tiers];
18
17
  // Sort tiers by upTo value, ascending, if upTo is not set,
19
18
  // put it at the end as it represent the last infinity tier
@@ -32,7 +31,7 @@ function getAmount(amount: number, selectedBillingPeriod?: BillingPeriod, should
32
31
  return selectedBillingPeriod === BillingPeriod.Annually && shouldShowMonthlyPriceAmount ? amount / 12 : amount;
33
32
  }
34
33
 
35
- export function calculateTierPriceVolume(
34
+ function calculateTierPriceVolume(
36
35
  currentTier: PriceTierFragment,
37
36
  perUnitQuantity: number,
38
37
  selectedBillingPeriod?: BillingPeriod,
@@ -52,66 +51,84 @@ export function calculateTierPriceVolume(
52
51
  return amount;
53
52
  }
54
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
+
55
98
  export function calculateTierPrice(
56
- price: Price,
57
- currentTier: PriceTierFragment,
58
- selectedBillingPeriod: BillingPeriod,
59
- shouldShowMonthlyPriceAmount: boolean,
60
- perUnitQuantity: number,
99
+ price: Pick<Price, 'tiers' | 'tiersMode'>,
100
+ unitQuantity: number,
101
+ selectedBillingPeriod?: BillingPeriod,
102
+ shouldShowMonthlyPriceAmount?: boolean,
61
103
  ): number {
104
+ if (!price.tiers) {
105
+ return 0;
106
+ }
107
+
62
108
  switch (price.tiersMode) {
63
109
  case TiersMode.Volume: {
64
- return calculateTierPriceVolume(
65
- currentTier,
66
- perUnitQuantity,
67
- selectedBillingPeriod,
68
- shouldShowMonthlyPriceAmount,
69
- );
110
+ const currentTier = getTierByQuantity(price.tiers, unitQuantity);
111
+ return calculateTierPriceVolume(currentTier, unitQuantity, selectedBillingPeriod, shouldShowMonthlyPriceAmount);
112
+ }
113
+ case TiersMode.Graduated: {
114
+ return calculateTierPriceGraduated(price.tiers, unitQuantity, selectedBillingPeriod, shouldShowMonthlyPriceAmount)
115
+ .total;
70
116
  }
71
117
  default:
72
118
  return 0;
73
119
  }
74
120
  }
75
121
 
76
- export function getSelectedTierQuantityBeFeature(
77
- plan: PaywallPlan,
78
- billingPeriod: BillingPeriod,
79
- currentSubscription: Subscription | null,
80
- ) {
81
- const result: Record<string, number> = {};
82
- const planTierPrices = plan.pricePoints.filter(
83
- (price) => price.billingPeriod === billingPeriod && price.isTieredPrice,
84
- );
85
- if (planTierPrices.length === 1) {
86
- const [price] = planTierPrices;
87
- const featureId = price?.feature!.featureId;
88
- if (currentSubscription && currentSubscription.plan.id === plan.id) {
89
- const tieredPrice = currentSubscription?.prices.find(
90
- (subscriptionPrice) =>
91
- subscriptionPrice.pricingModel === BillingModel.PerUnit &&
92
- subscriptionPrice.tiersMode &&
93
- subscriptionPrice.feature?.featureId === featureId,
94
- );
95
- if (tieredPrice) {
96
- result[featureId] = tieredPrice.feature?.unitQuantity || 1;
97
- }
98
- }
99
- }
100
-
101
- return result;
102
- }
103
-
104
122
  export function hasTierWithUnitPrice(tiers: PriceTierFragment[] | null | undefined) {
105
123
  return tiers?.some(({ unitPrice, upTo }) => unitPrice && !isNil(upTo));
106
124
  }
107
125
 
108
- export function getSelectedTier(
126
+ export function getTiersPerUnitQuantities(
109
127
  plan: PaywallPlan,
110
128
  billingPeriod: BillingPeriod,
111
129
  currentSubscription: Subscription | null,
112
- selectedTierByFeature: Record<string, PriceTierFragment>,
113
130
  selectDefaultTierIndex?: SelectDefaultTierIndexFn,
114
- ): Record<string, PriceTierFragment> {
131
+ ): Record<string, number> {
115
132
  const planTierPrices = plan.pricePoints.filter(
116
133
  (price) => price.billingPeriod === billingPeriod && price.isTieredPrice,
117
134
  );
@@ -120,11 +137,10 @@ export function getSelectedTier(
120
137
  const [price] = planTierPrices;
121
138
  const { featureId } = price.feature!;
122
139
  const selectedDefaultTierIndex = selectDefaultTierIndex ? selectDefaultTierIndex({ plan }) : 0;
123
- let currentTier = price.tiers![selectedDefaultTierIndex];
140
+ const currentTier = price.tiers![selectedDefaultTierIndex];
141
+ let quantity = hasTierWithUnitPrice(price.tiers) ? 1 : currentTier.upTo || 1;
124
142
 
125
- if (selectedTierByFeature[featureId]) {
126
- currentTier = price.tiers?.find((tier) => tier.upTo === selectedTierByFeature[featureId].upTo) || currentTier;
127
- } else if (currentSubscription && currentSubscription.plan.id === plan.id) {
143
+ if (currentSubscription && currentSubscription.plan.id === plan.id) {
128
144
  const tieredPrice = currentSubscription.prices.find(
129
145
  (subscriptionPrice) =>
130
146
  subscriptionPrice.pricingModel === BillingModel.PerUnit &&
@@ -133,12 +149,12 @@ export function getSelectedTier(
133
149
  );
134
150
 
135
151
  if (tieredPrice) {
136
- currentTier = getTierByQuantity(price.tiers!, tieredPrice.feature!.unitQuantity || 1)!;
152
+ quantity = tieredPrice.feature!.unitQuantity || 1;
137
153
  }
138
154
  }
139
155
 
140
- const result: Record<string, PriceTierFragment> = {};
141
- result[featureId] = currentTier;
156
+ const result: Record<string, number> = {};
157
+ result[featureId] = quantity;
142
158
 
143
159
  return result;
144
160
  }
@@ -153,11 +169,10 @@ export enum PriceTierComparison {
153
169
  }
154
170
 
155
171
  export function compareSelectedTierToCurrentTier(
156
- selectedTierByFeature: Record<string, PriceTierFragment>,
157
172
  perUnitQuantityByFeature: Record<string, number>,
158
173
  currentSubscription: Subscription | null,
159
174
  ): PriceTierComparison {
160
- if (!currentSubscription || !selectedTierByFeature) {
175
+ if (!currentSubscription) {
161
176
  return PriceTierComparison.Equal;
162
177
  }
163
178
 
@@ -168,29 +183,19 @@ export function compareSelectedTierToCurrentTier(
168
183
  return PriceTierComparison.Equal;
169
184
  }
170
185
 
171
- const { featureId, unitQuantity } = currentTierPrice.feature!;
172
- const { tiers } = currentTierPrice;
186
+ const { featureId, unitQuantity: oldQuantity } = currentTierPrice.feature!;
173
187
 
174
- if (!unitQuantity) {
188
+ if (isNil(oldQuantity)) {
175
189
  return PriceTierComparison.Equal;
176
190
  }
177
191
 
178
- const selectedTier = selectedTierByFeature[featureId];
179
- const selectedQuantity = perUnitQuantityByFeature[featureId];
180
- if (!selectedTier) {
181
- return PriceTierComparison.Equal;
182
- }
192
+ const newQuantity = perUnitQuantityByFeature[featureId];
183
193
 
184
- const effectiveQuantity = hasTierWithUnitPrice(tiers) ? selectedQuantity : selectedTier.upTo;
185
-
186
- if (!isNil(effectiveQuantity)) {
187
- if (effectiveQuantity < unitQuantity) {
188
- return PriceTierComparison.Lower;
189
- }
190
- if (effectiveQuantity > unitQuantity) {
191
- return PriceTierComparison.Higher;
192
- }
194
+ if (newQuantity < oldQuantity) {
195
+ return PriceTierComparison.Lower;
196
+ }
197
+ if (newQuantity > oldQuantity) {
198
+ return PriceTierComparison.Higher;
193
199
  }
194
-
195
200
  return PriceTierComparison.Equal;
196
201
  }