Skip to content

Commit f6fd053

Browse files
committed
feat(js-api-client): pricesForUsageOnTier helper
1 parent 4aec1dc commit f6fd053

File tree

5 files changed

+187
-1
lines changed

5 files changed

+187
-1
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@crystallize/js-api-client",
33
"license": "MIT",
4-
"version": "1.3.1",
4+
"version": "1.4.0",
55
"author": "Crystallize <[email protected]> (https://crystallize.com)",
66
"contributors": [
77
"Sébastien Morel <[email protected]>"

src/core/pricing.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Prices, Tier } from '../types/pricing';
2+
3+
export function pricesForUsageOnTier(usage: number, tiers: Tier[], tierType: 'volume' | 'graduated'): Prices {
4+
const sortedTiers = [...tiers].sort((a: Tier, b: Tier) => a.threshold - b.threshold);
5+
6+
if (tierType === 'volume') {
7+
return volumeBasedPriceFor(usage, sortedTiers);
8+
}
9+
return graduatedBasedPriceFor(usage, sortedTiers);
10+
}
11+
12+
function volumeBasedPriceFor(usage: number, tiers: Tier[]): Prices {
13+
const tiersLength = tiers.length;
14+
15+
for (let i = tiersLength - 1; i >= 0; i--) {
16+
const tier: Tier = tiers[i];
17+
if (usage < tier.threshold && i > 0) {
18+
continue;
19+
}
20+
// manage also an inexistent tier (threshold = 0)
21+
return { [tier.currency]: (usage >= tier.threshold ? tier.price : 0) * usage };
22+
}
23+
return { USD: 0.0 };
24+
}
25+
26+
function graduatedBasedPriceFor(usage: number, tiers: Tier[]): Prices {
27+
let rest = usage;
28+
29+
// manage also an inexistent tier (threshold = 0)
30+
if (tiers[0].threshold > 0) {
31+
rest = Math.max(0, rest - (tiers[0].threshold - 1));
32+
}
33+
34+
const splitUsage: Array<Tier & { usage: number }> = tiers.map((tier: Tier, tierIndex: number) => {
35+
const limit = tiers[tierIndex + 1]?.threshold || Infinity;
36+
const tierUsage = rest > limit ? limit : rest;
37+
rest -= tierUsage;
38+
return {
39+
...tier,
40+
usage: tierUsage,
41+
};
42+
});
43+
44+
return splitUsage.reduce((memo: Prices, tier: Tier & { usage: number }) => {
45+
return {
46+
...memo,
47+
[tier.currency]: (memo[tier.currency] || 0.0) + tier.usage * tier.price,
48+
};
49+
}, {});
50+
}

src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export * from './core/order';
77
export * from './core/search';
88
export * from './core/subscription';
99
export * from './core/customer';
10+
export * from './core/pricing';
1011
export * from './types/product';
1112
export * from './types/order';
1213
export * from './types/payment';
@@ -16,6 +17,7 @@ export * from './types/subscription';
1617
export * from './types/address';
1718
export * from './types/customer';
1819
export * from './types/signature';
20+
export * from './types/pricing';
1921

2022
import { createClient } from './core/client';
2123
import { createNavigationFetcher } from './core/navigation';

src/types/pricing.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export type Tier = {
2+
threshold: number;
3+
price: number;
4+
currency: string;
5+
};
6+
7+
export type Prices = Record<string, number>;

tests/pricesForUsageOnTier.test.js

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
const { pricesForUsageOnTier } = require('../dist/index.js');
2+
3+
const tiers1 = [
4+
{
5+
threshold: 0,
6+
price: 0,
7+
currency: 'EUR',
8+
},
9+
{
10+
threshold: 2,
11+
price: 250,
12+
currency: 'EUR',
13+
},
14+
{
15+
threshold: 20,
16+
price: 20,
17+
currency: 'EUR',
18+
},
19+
];
20+
21+
const tiers2 = [
22+
{
23+
threshold: 15,
24+
price: 500,
25+
currency: 'EUR',
26+
},
27+
{
28+
threshold: 40,
29+
price: 100,
30+
currency: 'EUR',
31+
},
32+
{
33+
threshold: 30,
34+
price: 300,
35+
currency: 'EUR',
36+
},
37+
];
38+
39+
const tiers3 = [
40+
{
41+
threshold: 0,
42+
price: 5,
43+
currency: 'EUR',
44+
},
45+
{
46+
threshold: 3,
47+
price: 4,
48+
currency: 'EUR',
49+
},
50+
{
51+
threshold: 15,
52+
price: 3,
53+
currency: 'EUR',
54+
},
55+
{
56+
threshold: 25,
57+
price: 2,
58+
currency: 'EUR',
59+
},
60+
{
61+
threshold: 50,
62+
price: 1,
63+
currency: 'EUR',
64+
},
65+
];
66+
67+
const tiers4 = [
68+
{
69+
threshold: 15,
70+
price: 3,
71+
currency: 'EUR',
72+
},
73+
{
74+
threshold: 25,
75+
price: 2,
76+
currency: 'EUR',
77+
},
78+
{
79+
threshold: 50,
80+
price: 1,
81+
currency: 'EUR',
82+
},
83+
];
84+
85+
const tiers5 = [
86+
{
87+
threshold: 75,
88+
price: 3,
89+
currency: 'EUR',
90+
},
91+
{
92+
threshold: 25,
93+
price: 2,
94+
currency: 'EUR',
95+
},
96+
{
97+
threshold: 50,
98+
price: 1,
99+
currency: 'EUR',
100+
},
101+
];
102+
103+
test('Price For Volume', () => {
104+
expect(pricesForUsageOnTier(0, tiers1, 'volume')).toEqual({ EUR: 0 });
105+
expect(pricesForUsageOnTier(2, tiers1, 'volume')).toEqual({ EUR: 500 });
106+
expect(pricesForUsageOnTier(3, tiers1, 'volume')).toEqual({ EUR: 750 });
107+
expect(pricesForUsageOnTier(20, tiers1, 'volume')).toEqual({ EUR: 400 });
108+
expect(pricesForUsageOnTier(40, tiers1, 'volume')).toEqual({ EUR: 800 });
109+
110+
expect(pricesForUsageOnTier(3, tiers2, 'volume')).toEqual({ EUR: 0 });
111+
expect(pricesForUsageOnTier(14, tiers2, 'volume')).toEqual({ EUR: 0 });
112+
expect(pricesForUsageOnTier(15, tiers2, 'volume')).toEqual({ EUR: 7500 });
113+
expect(pricesForUsageOnTier(16, tiers2, 'volume')).toEqual({ EUR: 8000 });
114+
expect(pricesForUsageOnTier(30, tiers2, 'volume')).toEqual({ EUR: 9000 });
115+
expect(pricesForUsageOnTier(31, tiers2, 'volume')).toEqual({ EUR: 9300 });
116+
expect(pricesForUsageOnTier(40, tiers2, 'volume')).toEqual({ EUR: 4000 });
117+
expect(pricesForUsageOnTier(41, tiers2, 'volume')).toEqual({ EUR: 4100 });
118+
expect(pricesForUsageOnTier(131, tiers2, 'volume')).toEqual({ EUR: 13100 });
119+
});
120+
121+
test('Price For Graduated', () => {
122+
expect(pricesForUsageOnTier(4, tiers3, 'graduated')).toEqual({ EUR: 19 });
123+
expect(pricesForUsageOnTier(211, tiers3, 'graduated')).toEqual({ EUR: 368 });
124+
expect(pricesForUsageOnTier(14, tiers4, 'graduated')).toEqual({ EUR: 0 });
125+
expect(pricesForUsageOnTier(15, tiers5, 'graduated')).toEqual({ EUR: 0 });
126+
expect(pricesForUsageOnTier(25, tiers5, 'graduated')).toEqual({ EUR: 2 });
127+
});

0 commit comments

Comments
 (0)