@blocklet/payment-react 1.13.113
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/LICENSE +13 -0
- package/README.md +29 -0
- package/babel.config.es.js +8 -0
- package/build.config.ts +29 -0
- package/es/api.d.ts +2 -0
- package/es/api.js +18 -0
- package/es/checkout/index.d.ts +15 -0
- package/es/checkout/index.js +61 -0
- package/es/components/input.d.ts +23 -0
- package/es/components/input.js +44 -0
- package/es/components/livemode.d.ts +2 -0
- package/es/components/livemode.js +24 -0
- package/es/components/pricing-table.d.ts +18 -0
- package/es/components/pricing-table.js +175 -0
- package/es/components/status.d.ts +3 -0
- package/es/components/status.js +20 -0
- package/es/components/switch.d.ts +6 -0
- package/es/components/switch.js +42 -0
- package/es/contexts/payment.d.ts +29 -0
- package/es/contexts/payment.js +45 -0
- package/es/dayjs.d.ts +2 -0
- package/es/dayjs.js +14 -0
- package/es/index.d.ts +16 -0
- package/es/index.js +29 -0
- package/es/locales/en.d.ts +2 -0
- package/es/locales/en.js +213 -0
- package/es/locales/index.d.ts +10 -0
- package/es/locales/index.js +20 -0
- package/es/locales/zh.d.ts +2 -0
- package/es/locales/zh.js +213 -0
- package/es/payment/amount.d.ts +12 -0
- package/es/payment/amount.js +22 -0
- package/es/payment/error.d.ts +13 -0
- package/es/payment/error.js +12 -0
- package/es/payment/footer.d.ts +4 -0
- package/es/payment/footer.js +9 -0
- package/es/payment/form/addon.d.ts +2 -0
- package/es/payment/form/addon.js +14 -0
- package/es/payment/form/address.d.ts +7 -0
- package/es/payment/form/address.js +119 -0
- package/es/payment/form/index.d.ts +9 -0
- package/es/payment/form/index.js +337 -0
- package/es/payment/form/phone.d.ts +4 -0
- package/es/payment/form/phone.js +97 -0
- package/es/payment/form/stripe.d.ts +13 -0
- package/es/payment/form/stripe.js +158 -0
- package/es/payment/header.d.ts +7 -0
- package/es/payment/header.js +29 -0
- package/es/payment/index.d.ts +28 -0
- package/es/payment/index.js +327 -0
- package/es/payment/product-card.d.ts +21 -0
- package/es/payment/product-card.js +34 -0
- package/es/payment/product-item.d.ts +19 -0
- package/es/payment/product-item.js +107 -0
- package/es/payment/product-skeleton.d.ts +4 -0
- package/es/payment/product-skeleton.js +34 -0
- package/es/payment/skeleton/overview.d.ts +2 -0
- package/es/payment/skeleton/overview.js +13 -0
- package/es/payment/skeleton/payment.d.ts +2 -0
- package/es/payment/skeleton/payment.js +19 -0
- package/es/payment/success.d.ts +8 -0
- package/es/payment/success.js +164 -0
- package/es/payment/summary.d.ts +12 -0
- package/es/payment/summary.js +178 -0
- package/es/theme.d.ts +1 -0
- package/es/theme.js +17 -0
- package/es/types/index.d.ts +19 -0
- package/es/types/index.js +0 -0
- package/es/types/shims.d.ts +18 -0
- package/es/util.d.ts +52 -0
- package/es/util.js +390 -0
- package/lib/api.d.ts +2 -0
- package/lib/api.js +26 -0
- package/lib/checkout/index.d.ts +15 -0
- package/lib/checkout/index.js +83 -0
- package/lib/components/input.d.ts +23 -0
- package/lib/components/input.js +72 -0
- package/lib/components/livemode.d.ts +2 -0
- package/lib/components/livemode.js +29 -0
- package/lib/components/pricing-table.d.ts +18 -0
- package/lib/components/pricing-table.js +232 -0
- package/lib/components/status.d.ts +3 -0
- package/lib/components/status.js +23 -0
- package/lib/components/switch.d.ts +6 -0
- package/lib/components/switch.js +51 -0
- package/lib/contexts/payment.d.ts +29 -0
- package/lib/contexts/payment.js +73 -0
- package/lib/dayjs.d.ts +2 -0
- package/lib/dayjs.js +21 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.js +143 -0
- package/lib/locales/en.d.ts +2 -0
- package/lib/locales/en.js +220 -0
- package/lib/locales/index.d.ts +10 -0
- package/lib/locales/index.js +33 -0
- package/lib/locales/zh.d.ts +2 -0
- package/lib/locales/zh.js +220 -0
- package/lib/payment/amount.d.ts +12 -0
- package/lib/payment/amount.js +28 -0
- package/lib/payment/error.d.ts +13 -0
- package/lib/payment/error.js +52 -0
- package/lib/payment/footer.d.ts +4 -0
- package/lib/payment/footer.js +25 -0
- package/lib/payment/form/addon.d.ts +2 -0
- package/lib/payment/form/addon.js +37 -0
- package/lib/payment/form/address.d.ts +7 -0
- package/lib/payment/form/address.js +152 -0
- package/lib/payment/form/index.d.ts +9 -0
- package/lib/payment/form/index.js +464 -0
- package/lib/payment/form/phone.d.ts +4 -0
- package/lib/payment/form/phone.js +133 -0
- package/lib/payment/form/stripe.d.ts +13 -0
- package/lib/payment/form/stripe.js +213 -0
- package/lib/payment/header.d.ts +7 -0
- package/lib/payment/header.js +58 -0
- package/lib/payment/index.d.ts +28 -0
- package/lib/payment/index.js +382 -0
- package/lib/payment/product-card.d.ts +21 -0
- package/lib/payment/product-card.js +81 -0
- package/lib/payment/product-item.d.ts +19 -0
- package/lib/payment/product-item.js +160 -0
- package/lib/payment/product-skeleton.d.ts +4 -0
- package/lib/payment/product-skeleton.js +71 -0
- package/lib/payment/skeleton/overview.d.ts +2 -0
- package/lib/payment/skeleton/overview.js +48 -0
- package/lib/payment/skeleton/payment.d.ts +2 -0
- package/lib/payment/skeleton/payment.js +54 -0
- package/lib/payment/success.d.ts +8 -0
- package/lib/payment/success.js +215 -0
- package/lib/payment/summary.d.ts +12 -0
- package/lib/payment/summary.js +225 -0
- package/lib/theme.d.ts +1 -0
- package/lib/theme.js +19 -0
- package/lib/types/index.d.ts +19 -0
- package/lib/types/index.js +1 -0
- package/lib/types/shims.d.ts +18 -0
- package/lib/util.d.ts +52 -0
- package/lib/util.js +487 -0
- package/package.json +104 -0
- package/src/api.ts +24 -0
- package/src/checkout/index.tsx +74 -0
- package/src/components/input.tsx +58 -0
- package/src/components/livemode.tsx +23 -0
- package/src/components/pricing-table.tsx +207 -0
- package/src/components/status.tsx +19 -0
- package/src/components/switch.tsx +48 -0
- package/src/contexts/payment.tsx +74 -0
- package/src/dayjs.ts +17 -0
- package/src/index.ts +32 -0
- package/src/locales/en.tsx +218 -0
- package/src/locales/index.tsx +30 -0
- package/src/locales/zh.tsx +214 -0
- package/src/payment/amount.tsx +24 -0
- package/src/payment/error.tsx +29 -0
- package/src/payment/footer.tsx +12 -0
- package/src/payment/form/addon.tsx +24 -0
- package/src/payment/form/address.tsx +119 -0
- package/src/payment/form/index.tsx +401 -0
- package/src/payment/form/phone.tsx +103 -0
- package/src/payment/form/stripe.tsx +195 -0
- package/src/payment/header.tsx +40 -0
- package/src/payment/index.tsx +367 -0
- package/src/payment/product-card.tsx +55 -0
- package/src/payment/product-item.tsx +121 -0
- package/src/payment/product-skeleton.tsx +39 -0
- package/src/payment/skeleton/overview.tsx +21 -0
- package/src/payment/skeleton/payment.tsx +35 -0
- package/src/payment/success.tsx +186 -0
- package/src/payment/summary.tsx +198 -0
- package/src/theme.ts +18 -0
- package/src/types/index.ts +29 -0
- package/src/types/shims.d.ts +18 -0
- package/src/util.ts +543 -0
package/src/util.ts
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/* eslint-disable no-nested-ternary */
|
|
2
|
+
/* eslint-disable @typescript-eslint/indent */
|
|
3
|
+
import type {
|
|
4
|
+
PriceCurrency,
|
|
5
|
+
PriceRecurring,
|
|
6
|
+
TCheckoutSessionExpanded,
|
|
7
|
+
TLineItemExpanded,
|
|
8
|
+
TPaymentCurrency,
|
|
9
|
+
TPaymentMethodExpanded,
|
|
10
|
+
TPrice,
|
|
11
|
+
TSubscriptionExpanded,
|
|
12
|
+
} from '@blocklet/payment-types';
|
|
13
|
+
import { BN, fromUnitToToken } from '@ocap/util';
|
|
14
|
+
import { defaultCountries } from 'react-international-phone';
|
|
15
|
+
|
|
16
|
+
import dayjs from './dayjs';
|
|
17
|
+
import { t } from './locales/index';
|
|
18
|
+
|
|
19
|
+
export const PAYMENT_KIT_DID = 'z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk';
|
|
20
|
+
|
|
21
|
+
export const getPrefix = () => {
|
|
22
|
+
const componentId = (window.blocklet.componentId || '').split('/').pop();
|
|
23
|
+
if (componentId === PAYMENT_KIT_DID) {
|
|
24
|
+
return window.blocklet.prefix;
|
|
25
|
+
}
|
|
26
|
+
const component = (window.blocklet.componentMountPoints || []).find((x: any) => x.did === PAYMENT_KIT_DID);
|
|
27
|
+
if (component) {
|
|
28
|
+
return component.mountPoint;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return window.blocklet.prefix;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function formatToDate(date: Date | string | number, locale = 'en') {
|
|
35
|
+
if (!date) {
|
|
36
|
+
return '-';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return dayjs(date).locale(formatLocale(locale)).format('YYYY-MM-DD HH:mm:ss');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formatToDatetime(date: Date | string | number, locale = 'en') {
|
|
43
|
+
if (!date) {
|
|
44
|
+
return '-';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return dayjs(date).locale(formatLocale(locale)).format('lll');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function formatTime(date: Date | string | number, format = 'YYYY-MM-DD HH:mm:ss', locale = 'en') {
|
|
51
|
+
if (!date) {
|
|
52
|
+
return '-';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return dayjs(date).locale(formatLocale(locale)).format(format);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function formatDateTime(date: Date | string | number, locale = 'en') {
|
|
59
|
+
return dayjs(date).locale(formatLocale(locale)).format('YYYY-MM-DD HH:mm');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const formatLocale = (locale = 'en') => {
|
|
63
|
+
if (locale === 'tw') {
|
|
64
|
+
return 'zh';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return locale;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const formatPrettyMsLocale = (locale: string) => (locale === 'zh' ? 'zh_CN' : 'en_US');
|
|
71
|
+
|
|
72
|
+
export const formatError = (err: any) => {
|
|
73
|
+
const { details, errors, response } = err;
|
|
74
|
+
|
|
75
|
+
// graphql error
|
|
76
|
+
if (Array.isArray(errors)) {
|
|
77
|
+
return errors.map((x) => x.message).join('\n');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// joi validate error
|
|
81
|
+
if (Array.isArray(details)) {
|
|
82
|
+
const formatted = details.map((e) => {
|
|
83
|
+
const errorMessage = e.message.replace(/["]/g, "'");
|
|
84
|
+
const errorPath = e.path.join('.');
|
|
85
|
+
return `${errorPath}: ${errorMessage}`;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return `Validate failed: ${formatted.join(';')}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// axios error
|
|
92
|
+
if (response) {
|
|
93
|
+
return response.data.error || `${err.message}: ${JSON.stringify(response.data)}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return err.message;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const formatPrice = (
|
|
100
|
+
price: TPrice,
|
|
101
|
+
currency: TPaymentCurrency,
|
|
102
|
+
unit_label?: string,
|
|
103
|
+
quantity: number = 1,
|
|
104
|
+
bn: boolean = true,
|
|
105
|
+
locale: string = 'en'
|
|
106
|
+
) => {
|
|
107
|
+
const unit = getPriceUintAmountByCurrency(price, currency);
|
|
108
|
+
const amount = bn
|
|
109
|
+
? fromUnitToToken(new BN(unit).mul(new BN(quantity)), currency.decimal).toString()
|
|
110
|
+
: +unit * quantity;
|
|
111
|
+
if (price?.type === 'recurring' && price.recurring) {
|
|
112
|
+
const recurring = formatRecurring(price.recurring, false, 'slash', locale);
|
|
113
|
+
|
|
114
|
+
if (unit_label) {
|
|
115
|
+
return `${amount} ${currency.symbol} / ${unit_label} ${recurring}`;
|
|
116
|
+
}
|
|
117
|
+
if (price.recurring.usage_type === 'metered') {
|
|
118
|
+
return `${amount} ${currency.symbol} / unit ${recurring}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return `${amount} ${currency.symbol} ${recurring}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return `${amount} ${currency.symbol}`;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const formatPriceAmount = (
|
|
128
|
+
price: TPrice,
|
|
129
|
+
currency: TPaymentCurrency,
|
|
130
|
+
unit_label?: string,
|
|
131
|
+
quantity: number = 1,
|
|
132
|
+
bn: boolean = true
|
|
133
|
+
) => {
|
|
134
|
+
const unit = getPriceUintAmountByCurrency(price, currency);
|
|
135
|
+
const amount = bn
|
|
136
|
+
? fromUnitToToken(new BN(unit).mul(new BN(quantity)), currency.decimal).toString()
|
|
137
|
+
: +unit * quantity;
|
|
138
|
+
if (price?.type === 'recurring' && price.recurring) {
|
|
139
|
+
if (unit_label) {
|
|
140
|
+
return `${amount} ${currency.symbol} / ${unit_label}`;
|
|
141
|
+
}
|
|
142
|
+
if (price.recurring.usage_type === 'metered') {
|
|
143
|
+
return `${amount} ${currency.symbol} / unit`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return `${amount} ${currency.symbol}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return `${amount} ${currency.symbol}`;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export function getStatementDescriptor(items: any[]) {
|
|
153
|
+
for (const item of items) {
|
|
154
|
+
if (item.price?.product?.statement_descriptor) {
|
|
155
|
+
return item.price?.product?.statement_descriptor;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return window.blocklet.appName;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function formatRecurring(
|
|
163
|
+
recurring: PriceRecurring,
|
|
164
|
+
translate: boolean = true,
|
|
165
|
+
separator: string = 'per',
|
|
166
|
+
locale: string = 'en'
|
|
167
|
+
) {
|
|
168
|
+
const intervals = {
|
|
169
|
+
hour: 'hourly',
|
|
170
|
+
day: 'daily',
|
|
171
|
+
week: 'weekly',
|
|
172
|
+
month: 'monthly',
|
|
173
|
+
year: 'yearly',
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
if (+recurring.interval_count === 1) {
|
|
177
|
+
const interval = t(`common.${recurring.interval}`, locale);
|
|
178
|
+
// @ts-ignore
|
|
179
|
+
return translate ? t(`common.${intervals[recurring.interval]}`, locale) : separator ? t(`common.${separator}`, locale, { interval }) : interval; // prettier-ignore
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return t('common.recurring', locale, {
|
|
183
|
+
count: recurring.interval_count,
|
|
184
|
+
interval: t(`common.${recurring.interval}s`, locale),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function getPriceUintAmountByCurrency(price: TPrice, currency: TPaymentCurrency) {
|
|
189
|
+
const options = getPriceCurrencyOptions(price);
|
|
190
|
+
const option = options.find((x) => x.currency_id === currency.id);
|
|
191
|
+
if (option) {
|
|
192
|
+
return option.unit_amount;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return price.unit_amount;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function getPriceCurrencyOptions(price: TPrice): PriceCurrency[] {
|
|
199
|
+
if (Array.isArray(price.currency_options)) {
|
|
200
|
+
return price.currency_options;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return [{ currency_id: price.currency_id, unit_amount: price.unit_amount, tiers: null, custom_unit_amount: null }];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function formatLineItemPricing(
|
|
207
|
+
item: TLineItemExpanded,
|
|
208
|
+
currency: TPaymentCurrency,
|
|
209
|
+
trial: number,
|
|
210
|
+
locale: string = 'en'
|
|
211
|
+
): { primary: string; secondary?: string; quantity: string } {
|
|
212
|
+
const price = item.upsell_price || item.price;
|
|
213
|
+
|
|
214
|
+
let quantity = t('common.qty', locale, { count: item.quantity });
|
|
215
|
+
if (price.recurring?.usage_type === 'metered' || +item.quantity === 1) {
|
|
216
|
+
quantity = '';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const total = `${fromUnitToToken(
|
|
220
|
+
new BN(getPriceUintAmountByCurrency(price, currency)).mul(new BN(item.quantity)),
|
|
221
|
+
currency.decimal
|
|
222
|
+
)} ${currency.symbol}`;
|
|
223
|
+
const unit = `${fromUnitToToken(getPriceUintAmountByCurrency(price, currency))} ${currency.symbol}`;
|
|
224
|
+
|
|
225
|
+
const appendUnit = (v: string, alt: string) => {
|
|
226
|
+
if (price.product.unit_label) {
|
|
227
|
+
return `${v}/${price.product.unit_label}`;
|
|
228
|
+
}
|
|
229
|
+
if (price.recurring?.usage_type === 'metered' || item.quantity === 1) {
|
|
230
|
+
return alt;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return quantity ? t('common.each', locale, { unit }) : '';
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
if (price.type === 'recurring' && price.recurring) {
|
|
237
|
+
if (trial > 0) {
|
|
238
|
+
return {
|
|
239
|
+
primary: t('common.trial', locale, { count: trial }),
|
|
240
|
+
secondary: `${appendUnit(total, total)} ${formatRecurring(price.recurring, false, 'slash', locale)}`,
|
|
241
|
+
quantity,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
primary: total,
|
|
247
|
+
secondary: appendUnit(total, ''),
|
|
248
|
+
quantity,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
primary: total,
|
|
254
|
+
secondary: appendUnit(total, ''),
|
|
255
|
+
quantity,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function getCheckoutAmount(
|
|
260
|
+
items: TLineItemExpanded[],
|
|
261
|
+
currency: TPaymentCurrency,
|
|
262
|
+
includeFreeTrial = false,
|
|
263
|
+
upsell = true
|
|
264
|
+
) {
|
|
265
|
+
let renew = new BN(0);
|
|
266
|
+
|
|
267
|
+
const total = items
|
|
268
|
+
.reduce((acc, x) => {
|
|
269
|
+
const price = upsell ? x.upsell_price || x.price : x.price;
|
|
270
|
+
if (price.type === 'recurring') {
|
|
271
|
+
renew = renew.add(new BN(getPriceUintAmountByCurrency(price, currency)).mul(new BN(x.quantity)));
|
|
272
|
+
|
|
273
|
+
if (includeFreeTrial) {
|
|
274
|
+
return acc;
|
|
275
|
+
}
|
|
276
|
+
if (price.recurring?.usage_type === 'metered') {
|
|
277
|
+
return acc;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return acc.add(new BN(getPriceUintAmountByCurrency(price, currency)).mul(new BN(x.quantity)));
|
|
281
|
+
}, new BN(0))
|
|
282
|
+
.toString();
|
|
283
|
+
|
|
284
|
+
return { subtotal: total, total, renew: renew.toString(), discount: '0', shipping: '0', tax: '0' };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function getRecurringPeriod(recurring: PriceRecurring) {
|
|
288
|
+
const { interval } = recurring;
|
|
289
|
+
const count = +recurring.interval_count || 1;
|
|
290
|
+
const dayInMs = 24 * 60 * 60 * 1000;
|
|
291
|
+
|
|
292
|
+
switch (interval) {
|
|
293
|
+
case 'hour':
|
|
294
|
+
return 60 * 60 * 1000;
|
|
295
|
+
case 'day':
|
|
296
|
+
return count * dayInMs;
|
|
297
|
+
case 'week':
|
|
298
|
+
return count * 7 * dayInMs;
|
|
299
|
+
case 'month':
|
|
300
|
+
return count * 30 * dayInMs;
|
|
301
|
+
case 'year':
|
|
302
|
+
return count * 365 * dayInMs;
|
|
303
|
+
default:
|
|
304
|
+
throw new Error(`Unsupported recurring interval: ${interval}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function formatUpsellSaving(session: TCheckoutSessionExpanded, currency: TPaymentCurrency) {
|
|
309
|
+
const items = session.line_items as TLineItemExpanded[];
|
|
310
|
+
if (items[0]?.upsell_price_id) {
|
|
311
|
+
return '0';
|
|
312
|
+
}
|
|
313
|
+
if (!items[0]?.price.upsell?.upsells_to) {
|
|
314
|
+
return '0';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const from = getCheckoutAmount(items, currency, false, false);
|
|
318
|
+
const to = getCheckoutAmount(
|
|
319
|
+
items.map((x) => ({
|
|
320
|
+
...x,
|
|
321
|
+
upsell_price_id: x.price.upsell?.upsells_to_id,
|
|
322
|
+
upsell_price: x.price.upsell?.upsells_to,
|
|
323
|
+
})) as TLineItemExpanded[],
|
|
324
|
+
currency,
|
|
325
|
+
false,
|
|
326
|
+
true
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const fromRecurring = items[0].price?.recurring as PriceRecurring;
|
|
330
|
+
const toRecurring = items[0].price?.upsell?.upsells_to?.recurring as PriceRecurring;
|
|
331
|
+
if (!fromRecurring || !toRecurring) {
|
|
332
|
+
return '0';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const factor = Math.floor(getRecurringPeriod(toRecurring) / getRecurringPeriod(fromRecurring));
|
|
336
|
+
|
|
337
|
+
const before = new BN(from.total).mul(new BN(factor));
|
|
338
|
+
const after = new BN(to.total);
|
|
339
|
+
|
|
340
|
+
return Number(before.sub(after).mul(new BN(100)).div(before).toString()).toFixed(0);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export function formatCheckoutHeadlines(
|
|
344
|
+
session: TCheckoutSessionExpanded,
|
|
345
|
+
currency: TPaymentCurrency,
|
|
346
|
+
locale: string = 'en'
|
|
347
|
+
): {
|
|
348
|
+
action: string;
|
|
349
|
+
amount: string;
|
|
350
|
+
then?: string;
|
|
351
|
+
secondary?: string;
|
|
352
|
+
} {
|
|
353
|
+
const items = session.line_items as TLineItemExpanded[];
|
|
354
|
+
const trial = session.subscription_data?.trial_period_days || 0;
|
|
355
|
+
|
|
356
|
+
const brand = getStatementDescriptor(items);
|
|
357
|
+
const { total } = getCheckoutAmount(items, currency, !!trial);
|
|
358
|
+
const amount = `${fromUnitToToken(total, currency.decimal)} ${currency.symbol}`;
|
|
359
|
+
|
|
360
|
+
// empty
|
|
361
|
+
if (items.length === 0) {
|
|
362
|
+
return {
|
|
363
|
+
action: t('payment.checkout.empty', locale),
|
|
364
|
+
amount: '0',
|
|
365
|
+
then: '',
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const { name } = items[0]?.price.product || { name: '' };
|
|
370
|
+
|
|
371
|
+
// all one time
|
|
372
|
+
if (items.every((x) => x.price.type === 'one_time')) {
|
|
373
|
+
const action = t('payment.checkout.pay', locale, { payee: brand });
|
|
374
|
+
if (items.length > 1) {
|
|
375
|
+
return { action, amount };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return { action, amount, then: '' };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const item = items.find((x) => x.price.type === 'recurring');
|
|
382
|
+
const recurring = formatRecurring(
|
|
383
|
+
(item?.upsell_price || item?.price)?.recurring as PriceRecurring,
|
|
384
|
+
false,
|
|
385
|
+
'per',
|
|
386
|
+
locale
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
// all recurring
|
|
390
|
+
if (items.every((x) => x.price.type === 'recurring')) {
|
|
391
|
+
const hasMetered = items.some((x) => x.price.type === 'recurring' && x.price.recurring?.usage_type === 'metered');
|
|
392
|
+
const subscription = [
|
|
393
|
+
hasMetered ? t('payment.checkout.least', locale) : '',
|
|
394
|
+
fromUnitToToken(
|
|
395
|
+
items.reduce((acc, x) => {
|
|
396
|
+
if (x.price.recurring?.usage_type === 'metered') {
|
|
397
|
+
return acc;
|
|
398
|
+
}
|
|
399
|
+
return acc.add(new BN(getPriceUintAmountByCurrency(x.price, currency)).mul(new BN(x.quantity)));
|
|
400
|
+
}, new BN(0)),
|
|
401
|
+
currency.decimal
|
|
402
|
+
),
|
|
403
|
+
currency.symbol,
|
|
404
|
+
]
|
|
405
|
+
.filter(Boolean)
|
|
406
|
+
.join(' ');
|
|
407
|
+
if (items.length > 1) {
|
|
408
|
+
if (trial > 0) {
|
|
409
|
+
return {
|
|
410
|
+
action: t('payment.checkout.try2', locale, { name, count: items.length - 1 }),
|
|
411
|
+
amount: t('payment.checkout.free', locale, { count: trial }),
|
|
412
|
+
then: t('payment.checkout.then', locale, { subscription, recurring }),
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
action: t('payment.checkout.sub2', locale, { name, count: items.length - 1 }),
|
|
418
|
+
amount,
|
|
419
|
+
then: recurring,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (trial > 0) {
|
|
424
|
+
return {
|
|
425
|
+
action: t('payment.checkout.try1', locale, { name }),
|
|
426
|
+
amount: t('payment.checkout.free', locale, { count: trial }),
|
|
427
|
+
then: t('payment.checkout.then', locale, { subscription, recurring }),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
action: t('payment.checkout.sub1', locale, { name }),
|
|
433
|
+
amount,
|
|
434
|
+
then: recurring,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// mixed
|
|
439
|
+
const subscription = fromUnitToToken(
|
|
440
|
+
items
|
|
441
|
+
.filter((x) => x.price.type === 'recurring')
|
|
442
|
+
.reduce((acc, x) => {
|
|
443
|
+
if (x.price.recurring?.usage_type === 'metered') {
|
|
444
|
+
return acc;
|
|
445
|
+
}
|
|
446
|
+
return acc.add(new BN(getPriceUintAmountByCurrency(x.price, currency)).mul(new BN(x.quantity)));
|
|
447
|
+
}, new BN(0)),
|
|
448
|
+
currency.decimal
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
action: t('payment.checkout.pay', locale, { payee: brand }),
|
|
453
|
+
amount,
|
|
454
|
+
then: t('payment.checkout.then', locale, { subscription: `${subscription} ${currency.symbol}`, recurring }),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export function formatAmount(amount: string, decimals: number) {
|
|
459
|
+
return fromUnitToToken(amount, decimals);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function findCurrency(methods: TPaymentMethodExpanded[], currencyId: string) {
|
|
463
|
+
for (const method of methods) {
|
|
464
|
+
for (const currency of method.payment_currencies) {
|
|
465
|
+
if (currency.id === currencyId) {
|
|
466
|
+
return currency;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export function isValidCountry(code: string) {
|
|
475
|
+
return defaultCountries.some((x) => x[1] === code);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export function stopEvent(e: React.SyntheticEvent<any>) {
|
|
479
|
+
try {
|
|
480
|
+
e.stopPropagation();
|
|
481
|
+
e.preventDefault();
|
|
482
|
+
// eslint-disable-next-line no-empty
|
|
483
|
+
} catch {
|
|
484
|
+
// Do nothing
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export function sleep(ms: number) {
|
|
489
|
+
return new Promise((resolve) => {
|
|
490
|
+
setTimeout(resolve, ms);
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export const getSubscriptionTimeSummary = (subscription: TSubscriptionExpanded) => {
|
|
495
|
+
const lines = [`Started on ${formatToDate(subscription.start_date * 1000)}`];
|
|
496
|
+
if (subscription.status === 'active' || subscription.status === 'trialing') {
|
|
497
|
+
if (subscription.cancel_at) {
|
|
498
|
+
lines.push(`will cancel on ${formatToDate(subscription.cancel_at * 1000)}`);
|
|
499
|
+
} else if (subscription.cancel_at_period_end) {
|
|
500
|
+
lines.push(`will cancel on ${formatToDate(subscription.current_period_end * 1000)}`);
|
|
501
|
+
} else {
|
|
502
|
+
lines.push(`will renew on ${formatToDate(subscription.current_period_end * 1000)}`);
|
|
503
|
+
}
|
|
504
|
+
} else if (subscription.status === 'past_due') {
|
|
505
|
+
lines.push(`will cancel on ${formatToDate(subscription.current_period_end * 1000)}`);
|
|
506
|
+
} else if (subscription.status === 'canceled') {
|
|
507
|
+
lines.push(`canceled on ${formatToDate(subscription.canceled_at * 1000)}`);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return lines.join(', ');
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
export const getSubscriptionAction = (subscription: TSubscriptionExpanded) => {
|
|
514
|
+
if (subscription.status !== 'canceled' && subscription.cancel_at_period_end) {
|
|
515
|
+
return { action: 'recover', variant: 'contained', color: 'primary' };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (subscription.status === 'active' || subscription.status === 'trialing') {
|
|
519
|
+
if (subscription.cancel_at) {
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
if (subscription.cancel_at_period_end) {
|
|
523
|
+
return { action: 'recover', variant: 'contained', color: 'primary' };
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
return { action: 'cancel', variant: 'outlined', color: 'inherit' };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (subscription.status === 'past_due') {
|
|
530
|
+
return { action: 'pastDue', variant: 'contained', color: 'primary' };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return null;
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
export const mergeExtraParams = (extra: Record<string, any> = {}) => {
|
|
537
|
+
const params = new URLSearchParams(window.location.search);
|
|
538
|
+
Object.keys(extra).forEach((key) => {
|
|
539
|
+
params.set(key, extra[key]);
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
return params.toString();
|
|
543
|
+
};
|