payment-kit 1.13.15

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 (222) hide show
  1. package/.eslintrc.js +15 -0
  2. package/README.md +3 -0
  3. package/api/dev.ts +6 -0
  4. package/api/hooks/pre-start.js +12 -0
  5. package/api/src/hooks/pre-start.ts +21 -0
  6. package/api/src/index.ts +92 -0
  7. package/api/src/jobs/event.ts +72 -0
  8. package/api/src/jobs/invoice.ts +148 -0
  9. package/api/src/jobs/payment.ts +208 -0
  10. package/api/src/jobs/subscription.ts +301 -0
  11. package/api/src/jobs/webhook.ts +113 -0
  12. package/api/src/libs/audit.ts +73 -0
  13. package/api/src/libs/auth.ts +40 -0
  14. package/api/src/libs/chain/arcblock.ts +13 -0
  15. package/api/src/libs/dayjs.ts +17 -0
  16. package/api/src/libs/env.ts +5 -0
  17. package/api/src/libs/hooks.ts +42 -0
  18. package/api/src/libs/logger.ts +27 -0
  19. package/api/src/libs/middleware.ts +12 -0
  20. package/api/src/libs/payment.ts +53 -0
  21. package/api/src/libs/queue/index.ts +263 -0
  22. package/api/src/libs/queue/store.ts +47 -0
  23. package/api/src/libs/security.ts +95 -0
  24. package/api/src/libs/session.ts +164 -0
  25. package/api/src/libs/util.ts +93 -0
  26. package/api/src/locales/en.ts +3 -0
  27. package/api/src/locales/index.ts +37 -0
  28. package/api/src/locales/zh.ts +3 -0
  29. package/api/src/routes/checkout-sessions.ts +536 -0
  30. package/api/src/routes/connect/collect.ts +109 -0
  31. package/api/src/routes/connect/pay.ts +116 -0
  32. package/api/src/routes/connect/setup.ts +121 -0
  33. package/api/src/routes/connect/shared.ts +410 -0
  34. package/api/src/routes/connect/subscribe.ts +128 -0
  35. package/api/src/routes/customers.ts +70 -0
  36. package/api/src/routes/events.ts +76 -0
  37. package/api/src/routes/index.ts +59 -0
  38. package/api/src/routes/invoices.ts +126 -0
  39. package/api/src/routes/payment-currencies.ts +38 -0
  40. package/api/src/routes/payment-intents.ts +122 -0
  41. package/api/src/routes/payment-links.ts +221 -0
  42. package/api/src/routes/payment-methods.ts +39 -0
  43. package/api/src/routes/prices.ts +134 -0
  44. package/api/src/routes/products.ts +191 -0
  45. package/api/src/routes/settings.ts +33 -0
  46. package/api/src/routes/subscription-items.ts +148 -0
  47. package/api/src/routes/subscriptions.ts +254 -0
  48. package/api/src/routes/usage-records.ts +120 -0
  49. package/api/src/routes/webhook-attempts.ts +57 -0
  50. package/api/src/routes/webhook-endpoints.ts +105 -0
  51. package/api/src/store/migrate.ts +16 -0
  52. package/api/src/store/migrations/20230905-genesis.ts +52 -0
  53. package/api/src/store/migrations/20230911-seeding.ts +145 -0
  54. package/api/src/store/models/checkout-session.ts +395 -0
  55. package/api/src/store/models/coupon.ts +137 -0
  56. package/api/src/store/models/customer.ts +199 -0
  57. package/api/src/store/models/discount.ts +116 -0
  58. package/api/src/store/models/event.ts +111 -0
  59. package/api/src/store/models/index.ts +165 -0
  60. package/api/src/store/models/invoice-item.ts +185 -0
  61. package/api/src/store/models/invoice.ts +492 -0
  62. package/api/src/store/models/job.ts +75 -0
  63. package/api/src/store/models/payment-currency.ts +139 -0
  64. package/api/src/store/models/payment-intent.ts +282 -0
  65. package/api/src/store/models/payment-link.ts +219 -0
  66. package/api/src/store/models/payment-method.ts +169 -0
  67. package/api/src/store/models/price.ts +266 -0
  68. package/api/src/store/models/product.ts +162 -0
  69. package/api/src/store/models/promotion-code.ts +112 -0
  70. package/api/src/store/models/setup-intent.ts +206 -0
  71. package/api/src/store/models/subscription-item.ts +103 -0
  72. package/api/src/store/models/subscription-schedule.ts +157 -0
  73. package/api/src/store/models/subscription.ts +307 -0
  74. package/api/src/store/models/types.ts +406 -0
  75. package/api/src/store/models/usage-record.ts +132 -0
  76. package/api/src/store/models/webhook-attempt.ts +96 -0
  77. package/api/src/store/models/webhook-endpoint.ts +96 -0
  78. package/api/src/store/sequelize.ts +15 -0
  79. package/api/third.d.ts +28 -0
  80. package/blocklet.md +3 -0
  81. package/blocklet.yml +89 -0
  82. package/index.html +14 -0
  83. package/logo.png +0 -0
  84. package/package.json +133 -0
  85. package/public/.gitkeep +0 -0
  86. package/screenshots/.gitkeep +0 -0
  87. package/screenshots/1-subscription.png +0 -0
  88. package/screenshots/2-customer-1.png +0 -0
  89. package/screenshots/3-customer-2.png +0 -0
  90. package/screenshots/4-admin-3.png +0 -0
  91. package/screenshots/5-admin-4.png +0 -0
  92. package/scripts/build-clean.js +6 -0
  93. package/scripts/bump-version.mjs +35 -0
  94. package/src/app.tsx +68 -0
  95. package/src/components/actions.tsx +85 -0
  96. package/src/components/blockchain/tx.tsx +29 -0
  97. package/src/components/checkout/amount.tsx +24 -0
  98. package/src/components/checkout/error.tsx +30 -0
  99. package/src/components/checkout/footer.tsx +12 -0
  100. package/src/components/checkout/form/address.tsx +38 -0
  101. package/src/components/checkout/form/index.tsx +295 -0
  102. package/src/components/checkout/header.tsx +23 -0
  103. package/src/components/checkout/pay.tsx +222 -0
  104. package/src/components/checkout/product-card.tsx +56 -0
  105. package/src/components/checkout/product-item.tsx +37 -0
  106. package/src/components/checkout/skeleton/overview.tsx +21 -0
  107. package/src/components/checkout/skeleton/payment.tsx +35 -0
  108. package/src/components/checkout/success.tsx +183 -0
  109. package/src/components/checkout/summary.tsx +34 -0
  110. package/src/components/collapse.tsx +50 -0
  111. package/src/components/confirm.tsx +55 -0
  112. package/src/components/copyable.tsx +38 -0
  113. package/src/components/currency.tsx +15 -0
  114. package/src/components/customer/actions.tsx +73 -0
  115. package/src/components/data.tsx +20 -0
  116. package/src/components/drawer-form.tsx +77 -0
  117. package/src/components/error-fallback.tsx +7 -0
  118. package/src/components/error.tsx +39 -0
  119. package/src/components/event/list.tsx +217 -0
  120. package/src/components/info-card.tsx +40 -0
  121. package/src/components/info-metric.tsx +35 -0
  122. package/src/components/info-row.tsx +28 -0
  123. package/src/components/input.tsx +40 -0
  124. package/src/components/invoice/action.tsx +94 -0
  125. package/src/components/invoice/list.tsx +225 -0
  126. package/src/components/invoice/table.tsx +110 -0
  127. package/src/components/layout.tsx +70 -0
  128. package/src/components/livemode.tsx +23 -0
  129. package/src/components/metadata/editor.tsx +57 -0
  130. package/src/components/metadata/form.tsx +45 -0
  131. package/src/components/payment-intent/actions.tsx +81 -0
  132. package/src/components/payment-intent/list.tsx +204 -0
  133. package/src/components/payment-link/actions.tsx +114 -0
  134. package/src/components/payment-link/after-pay.tsx +87 -0
  135. package/src/components/payment-link/before-pay.tsx +175 -0
  136. package/src/components/payment-link/item.tsx +135 -0
  137. package/src/components/payment-link/product-select.tsx +66 -0
  138. package/src/components/payment-link/rename.tsx +64 -0
  139. package/src/components/portal/invoice/list.tsx +110 -0
  140. package/src/components/portal/subscription/cancel.tsx +83 -0
  141. package/src/components/portal/subscription/list.tsx +232 -0
  142. package/src/components/price/actions.tsx +21 -0
  143. package/src/components/price/form.tsx +292 -0
  144. package/src/components/product/actions.tsx +125 -0
  145. package/src/components/product/add-price.tsx +59 -0
  146. package/src/components/product/create.tsx +97 -0
  147. package/src/components/product/edit-price.tsx +75 -0
  148. package/src/components/product/edit.tsx +67 -0
  149. package/src/components/product/features.tsx +32 -0
  150. package/src/components/product/form.tsx +76 -0
  151. package/src/components/relative-time.tsx +41 -0
  152. package/src/components/section/header.tsx +29 -0
  153. package/src/components/status.tsx +12 -0
  154. package/src/components/subscription/actions/cancel.tsx +66 -0
  155. package/src/components/subscription/actions/index.tsx +172 -0
  156. package/src/components/subscription/actions/pause.tsx +83 -0
  157. package/src/components/subscription/items/actions.tsx +31 -0
  158. package/src/components/subscription/items/index.tsx +107 -0
  159. package/src/components/subscription/list.tsx +200 -0
  160. package/src/components/switch.tsx +48 -0
  161. package/src/components/table.tsx +66 -0
  162. package/src/components/uploader.tsx +81 -0
  163. package/src/components/webhook/attempts.tsx +149 -0
  164. package/src/contexts/products.tsx +42 -0
  165. package/src/contexts/session.ts +10 -0
  166. package/src/contexts/settings.tsx +54 -0
  167. package/src/env.d.ts +17 -0
  168. package/src/global.css +97 -0
  169. package/src/hooks/mobile.ts +15 -0
  170. package/src/index.tsx +6 -0
  171. package/src/libs/api.ts +19 -0
  172. package/src/libs/dayjs.ts +17 -0
  173. package/src/libs/util.ts +474 -0
  174. package/src/locales/en.tsx +395 -0
  175. package/src/locales/index.tsx +8 -0
  176. package/src/locales/zh.tsx +389 -0
  177. package/src/pages/admin/billing/index.tsx +56 -0
  178. package/src/pages/admin/billing/invoices/detail.tsx +215 -0
  179. package/src/pages/admin/billing/invoices/index.tsx +5 -0
  180. package/src/pages/admin/billing/subscriptions/detail.tsx +237 -0
  181. package/src/pages/admin/billing/subscriptions/index.tsx +5 -0
  182. package/src/pages/admin/customers/customers/detail.tsx +209 -0
  183. package/src/pages/admin/customers/customers/index.tsx +109 -0
  184. package/src/pages/admin/customers/index.tsx +47 -0
  185. package/src/pages/admin/developers/events/detail.tsx +77 -0
  186. package/src/pages/admin/developers/events/index.tsx +5 -0
  187. package/src/pages/admin/developers/index.tsx +60 -0
  188. package/src/pages/admin/developers/logs.tsx +3 -0
  189. package/src/pages/admin/developers/overview.tsx +3 -0
  190. package/src/pages/admin/developers/webhooks/detail.tsx +109 -0
  191. package/src/pages/admin/developers/webhooks/index.tsx +102 -0
  192. package/src/pages/admin/index.tsx +120 -0
  193. package/src/pages/admin/overview.tsx +3 -0
  194. package/src/pages/admin/payments/index.tsx +65 -0
  195. package/src/pages/admin/payments/intents/detail.tsx +205 -0
  196. package/src/pages/admin/payments/intents/index.tsx +5 -0
  197. package/src/pages/admin/payments/links/create.tsx +141 -0
  198. package/src/pages/admin/payments/links/detail.tsx +318 -0
  199. package/src/pages/admin/payments/links/index.tsx +167 -0
  200. package/src/pages/admin/products/coupons/index.tsx +3 -0
  201. package/src/pages/admin/products/index.tsx +81 -0
  202. package/src/pages/admin/products/prices/actions.tsx +151 -0
  203. package/src/pages/admin/products/prices/detail.tsx +203 -0
  204. package/src/pages/admin/products/prices/list.tsx +95 -0
  205. package/src/pages/admin/products/pricing-tables.tsx +3 -0
  206. package/src/pages/admin/products/products/create.tsx +105 -0
  207. package/src/pages/admin/products/products/detail.tsx +246 -0
  208. package/src/pages/admin/products/products/index.tsx +154 -0
  209. package/src/pages/admin/settings/branding.tsx +3 -0
  210. package/src/pages/admin/settings/business.tsx +3 -0
  211. package/src/pages/admin/settings/index.tsx +47 -0
  212. package/src/pages/admin/settings/payment-methods.tsx +80 -0
  213. package/src/pages/checkout/index.tsx +38 -0
  214. package/src/pages/checkout/pay.tsx +89 -0
  215. package/src/pages/customer/index.tsx +93 -0
  216. package/src/pages/customer/invoice.tsx +147 -0
  217. package/src/pages/home.tsx +9 -0
  218. package/tsconfig.api.json +9 -0
  219. package/tsconfig.eslint.json +7 -0
  220. package/tsconfig.json +99 -0
  221. package/tsconfig.types.json +11 -0
  222. package/vite.config.ts +19 -0
@@ -0,0 +1,474 @@
1
+ import type {
2
+ LineItem,
3
+ PriceRecurring,
4
+ TCheckoutSessionExpanded,
5
+ TLineItemExpanded,
6
+ TPaymentCurrency,
7
+ TPaymentLinkExpanded,
8
+ TPrice,
9
+ TProductExpanded,
10
+ TSubscriptionItemExpanded,
11
+ } from '@did-pay/types';
12
+ import { BN, fromUnitToToken } from '@ocap/util';
13
+ import cloneDeep from 'lodash/cloneDeep';
14
+
15
+ import dayjs from './dayjs';
16
+
17
+ export function getExplorerLink(chainHost: string, did: string, type: string) {
18
+ if (!chainHost) return undefined;
19
+ try {
20
+ const chainUrl = new URL(chainHost);
21
+ switch (type) {
22
+ case 'asset':
23
+ chainUrl.pathname = `/explorer/assets/${did}`;
24
+ break;
25
+ case 'account':
26
+ chainUrl.pathname = `/explorer/accounts/${did}`;
27
+ break;
28
+ case 'tx':
29
+ chainUrl.pathname = `/explorer/txs/${did}`;
30
+ break;
31
+ case 'token':
32
+ chainUrl.pathname = `/explorer/tokens/${did}`;
33
+ break;
34
+ case 'factory':
35
+ chainUrl.pathname = `/explorer/factories/${did}`;
36
+ break;
37
+ case 'bridge':
38
+ chainUrl.pathname = `/explorer/bridges/${did}`;
39
+ break;
40
+ default:
41
+ chainUrl.pathname = '/';
42
+ }
43
+
44
+ return chainUrl.href;
45
+ } catch {
46
+ return undefined;
47
+ }
48
+ }
49
+
50
+ export function formatToDate(date: Date | string | number, locale = 'en') {
51
+ if (!date) {
52
+ return '-';
53
+ }
54
+
55
+ return dayjs(date).locale(formatLocale(locale)).format('ll');
56
+ }
57
+
58
+ export function formatToDatetime(date: Date | string | number, locale = 'en') {
59
+ if (!date) {
60
+ return '-';
61
+ }
62
+
63
+ return dayjs(date).locale(formatLocale(locale)).format('lll');
64
+ }
65
+
66
+ export function formatTime(date: Date | string | number, format = 'lll', locale = 'en') {
67
+ if (!date) {
68
+ return '-';
69
+ }
70
+
71
+ return dayjs(date).locale(formatLocale(locale)).format(format);
72
+ }
73
+
74
+ export function formatDateTime(date: Date | string | number, locale = 'en') {
75
+ return dayjs(date).locale(formatLocale(locale)).format('YYYY-MM-DD HH:mm');
76
+ }
77
+
78
+ export const formatLocale = (locale = 'en') => {
79
+ if (locale === 'tw') {
80
+ return 'zh';
81
+ }
82
+
83
+ return locale;
84
+ };
85
+
86
+ export const formatPrettyMsLocale = (locale: string) => (locale === 'zh' ? 'zh_CN' : 'en_US');
87
+
88
+ export const formatError = (err: any) => {
89
+ const { details, errors, response } = err;
90
+
91
+ // graphql error
92
+ if (Array.isArray(errors)) {
93
+ return errors.map((x) => x.message).join('\n');
94
+ }
95
+
96
+ // joi validate error
97
+ if (Array.isArray(details)) {
98
+ const formatted = details.map((e) => {
99
+ const errorMessage = e.message.replace(/["]/g, "'");
100
+ const errorPath = e.path.join('.');
101
+ return `${errorPath}: ${errorMessage}`;
102
+ });
103
+
104
+ return `Validate failed: ${formatted.join(';')}`;
105
+ }
106
+
107
+ // axios error
108
+ if (response) {
109
+ return response.data.error || `${err.message}: ${JSON.stringify(response.data)}`;
110
+ }
111
+
112
+ return err.message;
113
+ };
114
+
115
+ export const formatProductPrice = (
116
+ { prices, unit_label }: { prices: TPrice[]; unit_label: string },
117
+ currency: TPaymentCurrency
118
+ ): string => {
119
+ if (prices.length > 1) {
120
+ return `${prices.length} prices`;
121
+ }
122
+
123
+ if (prices.length === 1) {
124
+ return formatPrice(prices[0] as TPrice, currency, unit_label);
125
+ }
126
+
127
+ return 'No price';
128
+ };
129
+
130
+ export const formatPrice = (price: TPrice, currency: TPaymentCurrency, unit_label?: string, quantity: number = 1) => {
131
+ const amount = fromUnitToToken(new BN(price.unit_amount).mul(new BN(quantity)), currency.decimal).toString();
132
+ if (price?.type === 'recurring' && price.recurring) {
133
+ const recurring = formatRecurring(price.recurring, false, '/');
134
+
135
+ if (unit_label) {
136
+ return `${amount} ${currency.symbol} / ${unit_label} ${recurring}`;
137
+ }
138
+ if (price.recurring.usage_type === 'metered') {
139
+ return `${amount} ${currency.symbol} / unit ${recurring}`;
140
+ }
141
+
142
+ return `${amount} ${currency.symbol} ${recurring}`;
143
+ }
144
+
145
+ return `${amount} ${currency.symbol}`;
146
+ };
147
+
148
+ export function getPricingModel(price: TPrice) {
149
+ if (price.billing_scheme === 'tiered') {
150
+ return price.tiers_mode;
151
+ }
152
+
153
+ if (price.transform_quantity) {
154
+ return 'package';
155
+ }
156
+
157
+ return 'standard';
158
+ }
159
+
160
+ export function getProductByPriceId(products: TProductExpanded[], priceId: string) {
161
+ const product = cloneDeep(products.find((x) => x.prices.some((p) => p.id === priceId)));
162
+ if (product) {
163
+ product.prices = product.prices.filter((x) => x.id === priceId);
164
+ }
165
+ return product;
166
+ }
167
+
168
+ export function getStatementDescriptor(items: any[]) {
169
+ for (const item of items) {
170
+ if (item.price?.product?.statement_descriptor) {
171
+ return item.price?.product?.statement_descriptor;
172
+ }
173
+ }
174
+
175
+ return window.blocklet.appName;
176
+ }
177
+
178
+ export function formatRecurring(recurring: PriceRecurring, translate: boolean = true, separator = 'per') {
179
+ const intervals = {
180
+ hour: 'hourly',
181
+ day: 'daily',
182
+ week: 'weekly',
183
+ month: 'monthly',
184
+ year: 'yearly',
185
+ };
186
+
187
+ if (+recurring.interval_count === 1) {
188
+ // @ts-ignore
189
+ return translate ? intervals[recurring.interval] : `${separator} ${recurring.interval}`;
190
+ }
191
+
192
+ return `every ${recurring.interval_count} ${recurring.interval}s`;
193
+ }
194
+
195
+ export function formatLineItemPricing(
196
+ item: TLineItemExpanded,
197
+ currency: TPaymentCurrency,
198
+ trial: number
199
+ ): { primary: string; secondary?: string } {
200
+ const amount = fromUnitToToken(new BN(item.price.unit_amount).mul(new BN(item.quantity))).toString();
201
+
202
+ if (item.price.type === 'recurring' && item.price.recurring) {
203
+ if (trial > 0) {
204
+ const secondary = item.price.product.unit_label
205
+ ? `${amount} ${currency.symbol} / ${item.price.product.unit_label}`
206
+ : `${amount} ${currency.symbol}`;
207
+ return {
208
+ primary: `Free for ${trial} days`,
209
+ secondary: `${secondary} ${formatRecurring(item.price.recurring, false, '/')}`,
210
+ };
211
+ }
212
+
213
+ return {
214
+ primary: `${amount} ${currency.symbol}`,
215
+ secondary: item.price.product.unit_label ? `${amount} ${currency.symbol} / ${item.price.product.unit_label}` : '',
216
+ };
217
+ }
218
+
219
+ return {
220
+ primary: `${amount} ${currency.symbol}`,
221
+ secondary: item.price.product.unit_label ? `${amount} ${currency.symbol} / ${item.price.product.unit_label}` : '',
222
+ };
223
+ }
224
+
225
+ export function getCheckoutAmount(items: TLineItemExpanded[] = [], includeFreeTrial = false) {
226
+ const subtotal = items
227
+ .reduce((acc, x) => {
228
+ if (x.price.type === 'recurring') {
229
+ if (includeFreeTrial) {
230
+ return acc;
231
+ }
232
+ if (x.price.recurring?.usage_type === 'metered') {
233
+ return acc;
234
+ }
235
+ }
236
+ return acc.add(new BN(x.price.unit_amount).mul(new BN(x.quantity)));
237
+ }, new BN(0))
238
+ .toString();
239
+
240
+ const total = items
241
+ .reduce((acc, x) => {
242
+ if (x.price.type === 'recurring') {
243
+ if (includeFreeTrial) {
244
+ return acc;
245
+ }
246
+ if (x.price.recurring?.usage_type === 'metered') {
247
+ return acc;
248
+ }
249
+ }
250
+ return acc.add(new BN(x.price.unit_amount).mul(new BN(x.quantity)));
251
+ }, new BN(0))
252
+ .toString();
253
+
254
+ return { subtotal, total, discount: '0', shipping: '0', tax: '0' };
255
+ }
256
+
257
+ export function formatPaymentLinkPricing(link: TPaymentLinkExpanded, currency: TPaymentCurrency) {
258
+ const amount = getCheckoutAmount(link.line_items, !!link.subscription_data?.trial_period_days);
259
+ return formatCheckoutHeadlines({
260
+ mode: 'payment',
261
+ status: 'open',
262
+ payment_status: 'unpaid',
263
+ currency,
264
+ amount_total: amount.total,
265
+ amount_subtotal: amount.subtotal,
266
+ expires_at: dayjs().add(30, 'days').unix(),
267
+ ...link,
268
+ });
269
+ }
270
+
271
+ export function formatCheckoutHeadlines(session: TCheckoutSessionExpanded): {
272
+ action: string;
273
+ amount: string;
274
+ then?: string;
275
+ secondary?: string;
276
+ } {
277
+ const items = session.line_items as TLineItemExpanded[];
278
+ const total = session.amount_total;
279
+ const trial = session.subscription_data?.trial_period_days || 0;
280
+ const currency = session.currency as TPaymentCurrency;
281
+
282
+ const brand = getStatementDescriptor(items);
283
+ const amount = `${fromUnitToToken(total, currency.decimal)} ${currency.symbol}`;
284
+
285
+ // empty
286
+ if (items.length === 0) {
287
+ throw new Error('No line items for the checkout session');
288
+ }
289
+
290
+ const { name } = items[0]?.price.product || { name: '' };
291
+
292
+ // all one time
293
+ if (items.every((x) => x.price.type === 'one_time')) {
294
+ if (items.length > 1) {
295
+ return { action: `Pay ${brand}`, amount };
296
+ }
297
+
298
+ return { action: `Pay ${brand}`, amount, then: '' };
299
+ }
300
+
301
+ const recurring = formatRecurring(
302
+ items.find((x) => x.price.type === 'recurring')?.price.recurring as PriceRecurring,
303
+ false
304
+ );
305
+
306
+ // all recurring
307
+ if (items.every((x) => x.price.type === 'recurring')) {
308
+ const hasMetered = items.some((x) => x.price.type === 'recurring' && x.price.recurring?.usage_type === 'metered');
309
+ const subscription = [
310
+ hasMetered ? 'continue with at least' : '',
311
+ fromUnitToToken(
312
+ items.reduce((acc, x) => {
313
+ if (x.price.recurring?.usage_type === 'metered') {
314
+ return acc;
315
+ }
316
+ return acc.add(new BN(x.price.unit_amount).mul(new BN(x.quantity)));
317
+ }, new BN(0)),
318
+ currency.decimal
319
+ ),
320
+ currency.symbol,
321
+ ]
322
+ .filter(Boolean)
323
+ .join(' ');
324
+ if (items.length > 1) {
325
+ if (trial > 0) {
326
+ return {
327
+ action: `Try ${name} and ${items.length - 1} more`,
328
+ amount: `Free for ${trial} days`,
329
+ then: `Then ${subscription} ${recurring}`,
330
+ };
331
+ }
332
+
333
+ return {
334
+ action: `Subscribe to ${name} and ${items.length - 1} more`,
335
+ amount,
336
+ then: recurring,
337
+ };
338
+ }
339
+
340
+ if (trial > 0) {
341
+ return {
342
+ action: `Try ${name}`,
343
+ amount: `${trial} days free`,
344
+ then: `Then ${subscription} ${recurring}`,
345
+ };
346
+ }
347
+
348
+ return {
349
+ action: `Subscribe to ${name}`,
350
+ amount,
351
+ then: recurring,
352
+ };
353
+ }
354
+
355
+ // mixed
356
+ const subscription = fromUnitToToken(
357
+ items
358
+ .filter((x) => x.price.type === 'recurring')
359
+ .reduce((acc, x) => {
360
+ if (x.price.recurring?.usage_type === 'metered') {
361
+ return acc;
362
+ }
363
+ return acc.add(new BN(x.price.unit_amount).mul(new BN(x.quantity)));
364
+ }, new BN(0)),
365
+ currency.decimal
366
+ );
367
+
368
+ return {
369
+ action: `Pay ${brand}`,
370
+ amount,
371
+ then: `Then ${subscription} ${currency.symbol} ${recurring}`,
372
+ };
373
+ }
374
+
375
+ export function getSubscriptionStatusColor(status: string) {
376
+ switch (status) {
377
+ case 'active':
378
+ return 'success';
379
+ case 'trialing':
380
+ return 'primary';
381
+ case 'incomplete':
382
+ case 'incomplete_expired':
383
+ case 'paused':
384
+ return 'warning';
385
+ case 'past_due':
386
+ case 'unpaid':
387
+ return 'error';
388
+ case 'canceled':
389
+ default:
390
+ return 'default';
391
+ }
392
+ }
393
+
394
+ export function getPaymentIntentStatusColor(status: string) {
395
+ switch (status) {
396
+ case 'succeeded':
397
+ return 'success';
398
+ case 'requires_payment_method':
399
+ case 'requires_confirmation':
400
+ case 'requires_action':
401
+ case 'requires_capture':
402
+ return 'warning';
403
+ case 'canceled':
404
+ case 'processing':
405
+ default:
406
+ return 'default';
407
+ }
408
+ }
409
+
410
+ export function getInvoiceStatusColor(status: string) {
411
+ switch (status) {
412
+ case 'paid':
413
+ return 'success';
414
+ case 'open':
415
+ return 'secondary';
416
+ case 'uncollectible':
417
+ return 'warning';
418
+ case 'draft':
419
+ case 'void':
420
+ default:
421
+ return 'default';
422
+ }
423
+ }
424
+
425
+ export function getWebhookStatusColor(status: string) {
426
+ switch (status) {
427
+ case 'enabled':
428
+ return 'success';
429
+ case 'disabled':
430
+ default:
431
+ return 'default';
432
+ }
433
+ }
434
+
435
+ export function isPriceRecurringAligned(list: LineItem[], products: TProductExpanded[], index: number) {
436
+ const prices = list.map((x) => {
437
+ const product = getProductByPriceId(products, x.price_id);
438
+ const price = product?.prices.find((p) => p.id === x.price_id);
439
+ return price;
440
+ });
441
+
442
+ if (prices[index]?.type !== 'recurring') {
443
+ return true;
444
+ }
445
+
446
+ const recurring = prices.slice(0, index).find((x) => x?.type === 'recurring')?.recurring;
447
+ if (!recurring) {
448
+ return true;
449
+ }
450
+
451
+ return prices.slice(0, index + 1).every((x: any) => {
452
+ if (x.type !== 'recurring') {
453
+ return true;
454
+ }
455
+
456
+ // If the interval and interval_count are different, the recurring is not aligned
457
+ if (recurring?.interval !== x.recurring?.interval || recurring?.interval_count !== x.recurring?.interval_count) {
458
+ return false;
459
+ }
460
+
461
+ return true;
462
+ });
463
+ }
464
+
465
+ export function formatSubscriptionProduct(items: TSubscriptionItemExpanded[], maxLength = 2) {
466
+ const names = items.map((x) => x.price.product.name);
467
+ return (
468
+ names.slice(0, maxLength).join(', ') + (names.length > maxLength ? ` and ${names.length - maxLength} more` : '')
469
+ );
470
+ }
471
+
472
+ export function formatAmount(amount: string, decimals: number, points = 2) {
473
+ return Number(fromUnitToToken(amount, decimals)).toFixed(points);
474
+ }