payment-kit 1.13.18 → 1.13.20
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/README.md +14 -0
- package/api/src/index.ts +17 -6
- package/api/src/integrations/stripe/handlers/index.ts +53 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +252 -0
- package/api/src/integrations/stripe/handlers/payment-intent.ts +172 -0
- package/api/src/integrations/stripe/handlers/setup-intent.ts +42 -0
- package/api/src/integrations/stripe/handlers/subscription.ts +61 -0
- package/api/src/integrations/stripe/resource.ts +317 -0
- package/api/src/integrations/stripe/setup.ts +50 -0
- package/api/src/jobs/invoice.ts +11 -0
- package/api/src/jobs/payment.ts +15 -7
- package/api/src/jobs/subscription.ts +18 -2
- package/api/src/libs/session.ts +104 -8
- package/api/src/libs/util.ts +47 -1
- package/api/src/routes/checkout-sessions.ts +134 -27
- package/api/src/routes/connect/collect.ts +12 -4
- package/api/src/routes/connect/pay.ts +30 -20
- package/api/src/routes/connect/setup.ts +12 -4
- package/api/src/routes/connect/shared.ts +28 -4
- package/api/src/routes/connect/subscribe.ts +12 -5
- package/api/src/routes/customers.ts +37 -5
- package/api/src/routes/events.ts +9 -6
- package/api/src/routes/index.ts +2 -0
- package/api/src/routes/integrations/stripe.ts +64 -0
- package/api/src/routes/invoices.ts +19 -9
- package/api/src/routes/payment-intents.ts +19 -9
- package/api/src/routes/payment-links.ts +57 -15
- package/api/src/routes/payment-methods.ts +98 -1
- package/api/src/routes/prices.ts +71 -14
- package/api/src/routes/products.ts +79 -22
- package/api/src/routes/settings.ts +10 -11
- package/api/src/routes/subscription-items.ts +5 -5
- package/api/src/routes/subscriptions.ts +61 -10
- package/api/src/routes/usage-records.ts +52 -18
- package/api/src/routes/webhook-attempts.ts +5 -5
- package/api/src/routes/webhook-endpoints.ts +5 -5
- package/api/src/store/migrations/20230905-genesis.ts +2 -2
- package/api/src/store/migrations/20230911-seeding.ts +4 -3
- package/api/src/store/models/checkout-session.ts +15 -7
- package/api/src/store/models/index.ts +31 -7
- package/api/src/store/models/invoice.ts +1 -1
- package/api/src/store/models/payment-intent.ts +2 -5
- package/api/src/store/models/payment-link.ts +1 -1
- package/api/src/store/models/payment-method.ts +54 -33
- package/api/src/store/models/price.ts +52 -17
- package/api/src/store/models/product.ts +0 -3
- package/api/src/store/models/subscription.ts +3 -5
- package/api/src/store/models/types.ts +56 -2
- package/api/third.d.ts +2 -0
- package/blocklet.yml +1 -1
- package/package.json +13 -6
- package/public/currencies/dai.png +0 -0
- package/public/currencies/dollar.png +0 -0
- package/public/currencies/usdc.png +0 -0
- package/public/currencies/usdt.png +0 -0
- package/public/methods/arcblock.png +0 -0
- package/public/methods/binance.png +0 -0
- package/public/methods/coinbase.png +0 -0
- package/public/methods/ethereum.jpg +0 -0
- package/public/methods/stripe.png +0 -0
- package/src/components/checkout/form/address.tsx +84 -10
- package/src/components/checkout/form/index.tsx +169 -83
- package/src/components/checkout/form/phone.tsx +102 -0
- package/src/components/checkout/form/stripe.tsx +195 -0
- package/src/components/checkout/pay.tsx +115 -34
- package/src/components/checkout/product-item.tsx +4 -3
- package/src/components/checkout/summary.tsx +5 -4
- package/src/components/customer/edit.tsx +73 -0
- package/src/components/customer/form.tsx +104 -0
- package/src/components/drawer-form.tsx +4 -4
- package/src/components/input.tsx +22 -4
- package/src/components/invoice/table.tsx +8 -3
- package/src/components/metadata/editor.tsx +2 -3
- package/src/components/payment-link/after-pay.tsx +1 -1
- package/src/components/payment-link/before-pay.tsx +11 -6
- package/src/components/payment-link/chrome.tsx +13 -0
- package/src/components/payment-link/preview.tsx +31 -0
- package/src/components/payment-link/product-select.tsx +8 -3
- package/src/components/payment-link/rename.tsx +2 -2
- package/src/components/payment-method/arcblock.tsx +53 -0
- package/src/components/payment-method/bitcoin.tsx +53 -0
- package/src/components/payment-method/ethereum.tsx +53 -0
- package/src/components/payment-method/form.tsx +54 -0
- package/src/components/payment-method/stripe.tsx +45 -0
- package/src/components/portal/invoice/list.tsx +1 -1
- package/src/components/portal/subscription/list.tsx +1 -1
- package/src/components/price/currency-select.tsx +53 -0
- package/src/components/price/form.tsx +118 -24
- package/src/components/product/add-price.tsx +1 -1
- package/src/components/product/edit-price.tsx +6 -2
- package/src/components/subscription/items/index.tsx +7 -6
- package/src/components/subscription/items/usage-records.tsx +98 -0
- package/src/components/subscription/list.tsx +3 -2
- package/src/components/subscription/status.tsx +68 -0
- package/src/contexts/settings.tsx +2 -2
- package/src/env.d.ts +2 -0
- package/src/libs/util.ts +116 -21
- package/src/locales/en.tsx +72 -3
- package/src/pages/admin/billing/invoices/detail.tsx +5 -2
- package/src/pages/admin/billing/subscriptions/detail.tsx +6 -6
- package/src/pages/admin/customers/customers/detail.tsx +43 -9
- package/src/pages/admin/payments/intents/detail.tsx +8 -3
- package/src/pages/admin/payments/links/create.tsx +23 -3
- package/src/pages/admin/payments/links/detail.tsx +13 -26
- package/src/pages/admin/products/prices/detail.tsx +55 -11
- package/src/pages/admin/products/prices/list.tsx +7 -1
- package/src/pages/admin/products/products/create.tsx +1 -1
- package/src/pages/admin/products/products/detail.tsx +14 -7
- package/src/pages/admin/settings/index.tsx +16 -6
- package/src/pages/admin/settings/payment-methods/create.tsx +81 -0
- package/src/pages/admin/settings/{payment-methods.tsx → payment-methods/index.tsx} +9 -6
- package/src/pages/checkout/pay.tsx +3 -1
- package/src/pages/customer/index.tsx +36 -1
- package/public/.gitkeep +0 -0
package/src/libs/util.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/indent */
|
|
1
2
|
import type {
|
|
2
3
|
LineItem,
|
|
4
|
+
PriceCurrency,
|
|
3
5
|
PriceRecurring,
|
|
4
6
|
TCheckoutSessionExpanded,
|
|
5
7
|
TLineItemExpanded,
|
|
6
8
|
TPaymentCurrency,
|
|
7
9
|
TPaymentLinkExpanded,
|
|
10
|
+
TPaymentMethodExpanded,
|
|
8
11
|
TPrice,
|
|
9
12
|
TProductExpanded,
|
|
10
13
|
TSubscriptionItemExpanded,
|
|
11
14
|
} from '@did-pay/types';
|
|
12
15
|
import { BN, fromUnitToToken } from '@ocap/util';
|
|
13
16
|
import cloneDeep from 'lodash/cloneDeep';
|
|
17
|
+
import isEqual from 'lodash/isEqual';
|
|
14
18
|
|
|
15
19
|
import dayjs from './dayjs';
|
|
16
20
|
|
|
@@ -128,7 +132,10 @@ export const formatProductPrice = (
|
|
|
128
132
|
};
|
|
129
133
|
|
|
130
134
|
export const formatPrice = (price: TPrice, currency: TPaymentCurrency, unit_label?: string, quantity: number = 1) => {
|
|
131
|
-
const amount = fromUnitToToken(
|
|
135
|
+
const amount = fromUnitToToken(
|
|
136
|
+
new BN(getPriceUintAmountByCurrency(price, currency)).mul(new BN(quantity)),
|
|
137
|
+
currency.decimal
|
|
138
|
+
).toString();
|
|
132
139
|
if (price?.type === 'recurring' && price.recurring) {
|
|
133
140
|
const recurring = formatRecurring(price.recurring, false, '/');
|
|
134
141
|
|
|
@@ -192,12 +199,33 @@ export function formatRecurring(recurring: PriceRecurring, translate: boolean =
|
|
|
192
199
|
return `every ${recurring.interval_count} ${recurring.interval}s`;
|
|
193
200
|
}
|
|
194
201
|
|
|
202
|
+
export function getPriceUintAmountByCurrency(price: TPrice, currency: TPaymentCurrency) {
|
|
203
|
+
const options = getPriceCurrencyOptions(price);
|
|
204
|
+
const option = options.find((x) => x.currency_id === currency.id);
|
|
205
|
+
if (option) {
|
|
206
|
+
return option.unit_amount;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return price.unit_amount;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function getPriceCurrencyOptions(price: TPrice): PriceCurrency[] {
|
|
213
|
+
if (Array.isArray(price.currency_options)) {
|
|
214
|
+
return price.currency_options;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return [{ currency_id: price.currency_id, unit_amount: price.unit_amount, tiers: null, custom_unit_amount: null }];
|
|
218
|
+
}
|
|
219
|
+
|
|
195
220
|
export function formatLineItemPricing(
|
|
196
221
|
item: TLineItemExpanded,
|
|
197
222
|
currency: TPaymentCurrency,
|
|
198
223
|
trial: number
|
|
199
224
|
): { primary: string; secondary?: string } {
|
|
200
|
-
const amount = fromUnitToToken(
|
|
225
|
+
const amount = fromUnitToToken(
|
|
226
|
+
new BN(getPriceUintAmountByCurrency(item.price, currency)).mul(new BN(item.quantity)),
|
|
227
|
+
currency.decimal
|
|
228
|
+
).toString();
|
|
201
229
|
|
|
202
230
|
if (item.price.type === 'recurring' && item.price.recurring) {
|
|
203
231
|
if (trial > 0) {
|
|
@@ -222,7 +250,7 @@ export function formatLineItemPricing(
|
|
|
222
250
|
};
|
|
223
251
|
}
|
|
224
252
|
|
|
225
|
-
export function getCheckoutAmount(items: TLineItemExpanded[]
|
|
253
|
+
export function getCheckoutAmount(items: TLineItemExpanded[], currency: TPaymentCurrency, includeFreeTrial = false) {
|
|
226
254
|
const subtotal = items
|
|
227
255
|
.reduce((acc, x) => {
|
|
228
256
|
if (x.price.type === 'recurring') {
|
|
@@ -233,7 +261,7 @@ export function getCheckoutAmount(items: TLineItemExpanded[] = [], includeFreeTr
|
|
|
233
261
|
return acc;
|
|
234
262
|
}
|
|
235
263
|
}
|
|
236
|
-
return acc.add(new BN(x.price
|
|
264
|
+
return acc.add(new BN(getPriceUintAmountByCurrency(x.price, currency)).mul(new BN(x.quantity)));
|
|
237
265
|
}, new BN(0))
|
|
238
266
|
.toString();
|
|
239
267
|
|
|
@@ -247,7 +275,7 @@ export function getCheckoutAmount(items: TLineItemExpanded[] = [], includeFreeTr
|
|
|
247
275
|
return acc;
|
|
248
276
|
}
|
|
249
277
|
}
|
|
250
|
-
return acc.add(new BN(x.price
|
|
278
|
+
return acc.add(new BN(getPriceUintAmountByCurrency(x.price, currency)).mul(new BN(x.quantity)));
|
|
251
279
|
}, new BN(0))
|
|
252
280
|
.toString();
|
|
253
281
|
|
|
@@ -255,31 +283,36 @@ export function getCheckoutAmount(items: TLineItemExpanded[] = [], includeFreeTr
|
|
|
255
283
|
}
|
|
256
284
|
|
|
257
285
|
export function formatPaymentLinkPricing(link: TPaymentLinkExpanded, currency: TPaymentCurrency) {
|
|
258
|
-
const amount = getCheckoutAmount(link.line_items, !!link.subscription_data?.trial_period_days);
|
|
259
|
-
return formatCheckoutHeadlines(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
286
|
+
const amount = getCheckoutAmount(link.line_items, currency, !!link.subscription_data?.trial_period_days);
|
|
287
|
+
return formatCheckoutHeadlines(
|
|
288
|
+
{
|
|
289
|
+
mode: 'payment',
|
|
290
|
+
status: 'open',
|
|
291
|
+
payment_status: 'unpaid',
|
|
292
|
+
currency,
|
|
293
|
+
amount_total: amount.total,
|
|
294
|
+
amount_subtotal: amount.subtotal,
|
|
295
|
+
expires_at: dayjs().add(30, 'days').unix(),
|
|
296
|
+
...link,
|
|
297
|
+
} as any,
|
|
298
|
+
currency
|
|
299
|
+
);
|
|
269
300
|
}
|
|
270
301
|
|
|
271
|
-
export function formatCheckoutHeadlines(
|
|
302
|
+
export function formatCheckoutHeadlines(
|
|
303
|
+
session: TCheckoutSessionExpanded,
|
|
304
|
+
currency: TPaymentCurrency
|
|
305
|
+
): {
|
|
272
306
|
action: string;
|
|
273
307
|
amount: string;
|
|
274
308
|
then?: string;
|
|
275
309
|
secondary?: string;
|
|
276
310
|
} {
|
|
277
311
|
const items = session.line_items as TLineItemExpanded[];
|
|
278
|
-
const total = session.amount_total;
|
|
279
312
|
const trial = session.subscription_data?.trial_period_days || 0;
|
|
280
|
-
const currency = session.currency as TPaymentCurrency;
|
|
281
313
|
|
|
282
314
|
const brand = getStatementDescriptor(items);
|
|
315
|
+
const { total } = getCheckoutAmount(items, currency, !!trial);
|
|
283
316
|
const amount = `${fromUnitToToken(total, currency.decimal)} ${currency.symbol}`;
|
|
284
317
|
|
|
285
318
|
// empty
|
|
@@ -313,7 +346,7 @@ export function formatCheckoutHeadlines(session: TCheckoutSessionExpanded): {
|
|
|
313
346
|
if (x.price.recurring?.usage_type === 'metered') {
|
|
314
347
|
return acc;
|
|
315
348
|
}
|
|
316
|
-
return acc.add(new BN(x.price
|
|
349
|
+
return acc.add(new BN(getPriceUintAmountByCurrency(x.price, currency)).mul(new BN(x.quantity)));
|
|
317
350
|
}, new BN(0)),
|
|
318
351
|
currency.decimal
|
|
319
352
|
),
|
|
@@ -360,7 +393,7 @@ export function formatCheckoutHeadlines(session: TCheckoutSessionExpanded): {
|
|
|
360
393
|
if (x.price.recurring?.usage_type === 'metered') {
|
|
361
394
|
return acc;
|
|
362
395
|
}
|
|
363
|
-
return acc.add(new BN(x.price
|
|
396
|
+
return acc.add(new BN(getPriceUintAmountByCurrency(x.price, currency)).mul(new BN(x.quantity)));
|
|
364
397
|
}, new BN(0)),
|
|
365
398
|
currency.decimal
|
|
366
399
|
);
|
|
@@ -432,6 +465,29 @@ export function getWebhookStatusColor(status: string) {
|
|
|
432
465
|
}
|
|
433
466
|
}
|
|
434
467
|
|
|
468
|
+
export function isPriceCurrencyAligned(list: LineItem[], products: TProductExpanded[], index: number) {
|
|
469
|
+
const prices = list.map((x) => {
|
|
470
|
+
const product = getProductByPriceId(products, x.price_id);
|
|
471
|
+
const price = product?.prices.find((p) => p.id === x.price_id);
|
|
472
|
+
return price;
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
const current = getPriceCurrencyOptions(prices[index] as TPrice)
|
|
476
|
+
.map((x) => x.currency_id)
|
|
477
|
+
.sort();
|
|
478
|
+
|
|
479
|
+
for (let i = 0; i < index; i++) {
|
|
480
|
+
const previous = getPriceCurrencyOptions(prices[i] as TPrice)
|
|
481
|
+
.map((x) => x.currency_id)
|
|
482
|
+
.sort();
|
|
483
|
+
if (isEqual(current, previous) === false) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
|
|
435
491
|
export function isPriceRecurringAligned(list: LineItem[], products: TProductExpanded[], index: number) {
|
|
436
492
|
const prices = list.map((x) => {
|
|
437
493
|
const product = getProductByPriceId(products, x.price_id);
|
|
@@ -462,6 +518,16 @@ export function isPriceRecurringAligned(list: LineItem[], products: TProductExpa
|
|
|
462
518
|
});
|
|
463
519
|
}
|
|
464
520
|
|
|
521
|
+
export function isPriceAligned(list: LineItem[], products: TProductExpanded[], index: number) {
|
|
522
|
+
const currency = isPriceCurrencyAligned(list, products, index);
|
|
523
|
+
const recurring = isPriceRecurringAligned(list, products, index);
|
|
524
|
+
return {
|
|
525
|
+
currency,
|
|
526
|
+
recurring,
|
|
527
|
+
aligned: currency && recurring,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
465
531
|
export function formatSubscriptionProduct(items: TSubscriptionItemExpanded[], maxLength = 2) {
|
|
466
532
|
const names = items.map((x) => x.price.product.name);
|
|
467
533
|
return (
|
|
@@ -472,3 +538,32 @@ export function formatSubscriptionProduct(items: TSubscriptionItemExpanded[], ma
|
|
|
472
538
|
export function formatAmount(amount: string, decimals: number, points = 2) {
|
|
473
539
|
return Number(fromUnitToToken(amount, decimals)).toFixed(points);
|
|
474
540
|
}
|
|
541
|
+
|
|
542
|
+
export function findCurrency(methods: TPaymentMethodExpanded[], currencyId: string) {
|
|
543
|
+
for (const method of methods) {
|
|
544
|
+
for (const currency of method.payment_currencies) {
|
|
545
|
+
if (currency.id === currencyId) {
|
|
546
|
+
return currency;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export function filterCurrencies(method: TPaymentMethodExpanded, hasSelected: (currency: any) => boolean) {
|
|
555
|
+
method.payment_currencies = method.payment_currencies.filter((x) => !hasSelected(x));
|
|
556
|
+
return method;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export function getSupportedPaymentMethods(methods: TPaymentMethodExpanded[], hasSelected: (currency: any) => boolean) {
|
|
560
|
+
const filtered = cloneDeep(methods).map((x) => filterCurrencies(x, hasSelected));
|
|
561
|
+
return filtered.filter((x) => x.payment_currencies.length);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export function getSupportedPaymentCurrencies(items: TLineItemExpanded[]) {
|
|
565
|
+
const currencies = items.reduce((acc, x: any) => {
|
|
566
|
+
return acc.concat(x.price.currency_options.map((c: any) => c.currency_id));
|
|
567
|
+
}, []);
|
|
568
|
+
return Array.from(new Set(currencies));
|
|
569
|
+
}
|
package/src/locales/en.tsx
CHANGED
|
@@ -92,6 +92,7 @@ export default flat({
|
|
|
92
92
|
archived: 'This product has been archived',
|
|
93
93
|
archivedTip:
|
|
94
94
|
'This product can’t be added to new invoices, subscriptions, payment links, or pricing tables. Any existing subscriptions with this product remain active until canceled and any existing payment links or pricing tables are deactivated.',
|
|
95
|
+
locked: 'This product is locked because at least one of its prices is used by a subscription or a payment.',
|
|
95
96
|
image: {
|
|
96
97
|
label: 'Image',
|
|
97
98
|
add: 'Add image',
|
|
@@ -131,6 +132,8 @@ export default flat({
|
|
|
131
132
|
additional: 'Additional options',
|
|
132
133
|
model: 'Pricing model',
|
|
133
134
|
amount: 'Price',
|
|
135
|
+
locked: 'This price is locked because it is used by a subscription or a payment.',
|
|
136
|
+
amountTip: 'Choose recurring for subscriptions and one-time for everything else.',
|
|
134
137
|
duplicate: 'Duplicate price',
|
|
135
138
|
edit: 'Edit price',
|
|
136
139
|
archive: 'Archive price',
|
|
@@ -152,8 +155,14 @@ export default flat({
|
|
|
152
155
|
recurring: {
|
|
153
156
|
interval: 'Billing period',
|
|
154
157
|
metered: 'Usage is metered?',
|
|
158
|
+
meteredTip:
|
|
159
|
+
'Metered billing lets you charge customers based on reported usage at the end of each billing period.',
|
|
155
160
|
aggregate: 'Charge for metered usage by',
|
|
156
161
|
},
|
|
162
|
+
currency: {
|
|
163
|
+
add: 'Add more currencies',
|
|
164
|
+
list: 'Currencies',
|
|
165
|
+
},
|
|
157
166
|
},
|
|
158
167
|
coupon: {
|
|
159
168
|
create: 'Create Coupon',
|
|
@@ -186,7 +195,8 @@ export default flat({
|
|
|
186
195
|
noProducts: 'Payment link must have at least one product',
|
|
187
196
|
noRedirectUrl: 'Payment link must have a redirect url',
|
|
188
197
|
noSubscriptionTrialDays: 'You must specify a trial period for subscription',
|
|
189
|
-
|
|
198
|
+
recurringNotAligned: 'The prices on all line items must have the same recurring interval',
|
|
199
|
+
currencyNotAligned: 'The prices on all line items must have the same currency settings',
|
|
190
200
|
edit: 'Edit payment link',
|
|
191
201
|
rename: 'Change name',
|
|
192
202
|
archive: 'Archive payment link',
|
|
@@ -207,8 +217,48 @@ export default flat({
|
|
|
207
217
|
refund: 'Refund payment',
|
|
208
218
|
},
|
|
209
219
|
paymentMethod: {
|
|
210
|
-
|
|
220
|
+
_name: 'Payment Method',
|
|
211
221
|
type: 'Type',
|
|
222
|
+
add: 'Add payment method',
|
|
223
|
+
save: 'Save payment method',
|
|
224
|
+
saved: 'Payment method successfully saved',
|
|
225
|
+
settings: 'Settings',
|
|
226
|
+
name: {
|
|
227
|
+
label: 'Name',
|
|
228
|
+
tip: 'Consumer facing',
|
|
229
|
+
},
|
|
230
|
+
description: {
|
|
231
|
+
label: 'Description',
|
|
232
|
+
tip: 'Not consumer facing',
|
|
233
|
+
},
|
|
234
|
+
stripe: {
|
|
235
|
+
publishable_key: {
|
|
236
|
+
label: 'Publishable Key',
|
|
237
|
+
tip: 'Publishable Key, See Dashboard > Developers > API Keys',
|
|
238
|
+
},
|
|
239
|
+
secret_key: {
|
|
240
|
+
label: 'Secret Key',
|
|
241
|
+
tip: 'Secret Key, See Dashboard > Developers > API Keys',
|
|
242
|
+
},
|
|
243
|
+
webhook_signing_secret: {
|
|
244
|
+
label: 'Webhook Signing Secret',
|
|
245
|
+
tip: 'Webhook Signing Secret, See Dashboard > Developers > Webhooks > Signing Secret',
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
arcblock: {
|
|
249
|
+
chain_id: {
|
|
250
|
+
label: 'Chain ID',
|
|
251
|
+
tip: 'Just a name',
|
|
252
|
+
},
|
|
253
|
+
api_host: {
|
|
254
|
+
label: 'API Host',
|
|
255
|
+
tip: 'The graphql endpoint to send transaction to',
|
|
256
|
+
},
|
|
257
|
+
explorer_host: {
|
|
258
|
+
label: 'Explorer Host',
|
|
259
|
+
tip: 'The webapp endpoint to view transaction details',
|
|
260
|
+
},
|
|
261
|
+
},
|
|
212
262
|
},
|
|
213
263
|
paymentCurrency: {
|
|
214
264
|
name: 'Payment Currency',
|
|
@@ -245,6 +295,7 @@ export default flat({
|
|
|
245
295
|
collectionMethod: 'Billing',
|
|
246
296
|
currentPeriod: 'Current Period',
|
|
247
297
|
trialingPeriod: 'Trial Period',
|
|
298
|
+
trailEnd: 'Trial ends {date}',
|
|
248
299
|
discount: 'Discount',
|
|
249
300
|
startedAt: 'Started',
|
|
250
301
|
nextInvoice: 'Next Invoice',
|
|
@@ -257,6 +308,7 @@ export default flat({
|
|
|
257
308
|
schedule: 'Scheduled to cancel',
|
|
258
309
|
title: 'Cancel subscription',
|
|
259
310
|
required: 'Custom cancel time is required',
|
|
311
|
+
will: 'Cancels on {date}',
|
|
260
312
|
at: {
|
|
261
313
|
title: 'Cancel',
|
|
262
314
|
now: 'Immediately ({date})',
|
|
@@ -281,6 +333,17 @@ export default flat({
|
|
|
281
333
|
void: 'Void invoices',
|
|
282
334
|
voidTip: 'For businesses not currently offering services.',
|
|
283
335
|
},
|
|
336
|
+
until: {
|
|
337
|
+
never: 'Collection paused',
|
|
338
|
+
custom: 'Collection paused until {date}',
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
usage: {
|
|
342
|
+
title: 'Usage records',
|
|
343
|
+
current: 'Usage records for current period',
|
|
344
|
+
view: 'View usage',
|
|
345
|
+
vary: 'Varies with usage',
|
|
346
|
+
used: 'Unit used',
|
|
284
347
|
},
|
|
285
348
|
},
|
|
286
349
|
customer: {
|
|
@@ -331,8 +394,11 @@ export default flat({
|
|
|
331
394
|
payment: 'Pay',
|
|
332
395
|
subscription: 'Subscribe',
|
|
333
396
|
setup: 'Subscribe',
|
|
397
|
+
continue: 'Confirm {action}',
|
|
334
398
|
connect: 'Connect and {action}',
|
|
399
|
+
login: 'Login to load and save contact information',
|
|
335
400
|
portal: 'Manage subscriptions',
|
|
401
|
+
cardPay: '{action} with card',
|
|
336
402
|
completed: {
|
|
337
403
|
payment: 'Thanks for your purchase',
|
|
338
404
|
subscription: 'Thanks for your subscribing',
|
|
@@ -341,6 +407,8 @@ export default flat({
|
|
|
341
407
|
},
|
|
342
408
|
confirm:
|
|
343
409
|
'By confirming your subscription, you allow {payee} to charge your account for this and future payments in accordance with their terms. You can always cancel your subscription.',
|
|
410
|
+
required: 'Required',
|
|
411
|
+
invalid: 'Invalid',
|
|
344
412
|
billing: {
|
|
345
413
|
auto: 'Country',
|
|
346
414
|
required: 'Billing address',
|
|
@@ -349,12 +417,13 @@ export default flat({
|
|
|
349
417
|
city: 'City or town',
|
|
350
418
|
line1: 'Address',
|
|
351
419
|
line2: 'Line2',
|
|
352
|
-
postal_code: 'Postal
|
|
420
|
+
postal_code: 'Postal code',
|
|
353
421
|
},
|
|
354
422
|
customer: {
|
|
355
423
|
name: 'Name',
|
|
356
424
|
email: 'Email',
|
|
357
425
|
phone: 'Phone',
|
|
426
|
+
phonePlaceholder: 'Phone number',
|
|
358
427
|
phoneTip: 'In case we need to contact you about your order',
|
|
359
428
|
},
|
|
360
429
|
},
|
|
@@ -128,7 +128,7 @@ export default function InvoiceDetail(props: { id: string }) {
|
|
|
128
128
|
)}
|
|
129
129
|
<InfoRow label={t('admin.invoice.billing')} value={data.collection_method} />
|
|
130
130
|
<InfoRow
|
|
131
|
-
label={t('admin.paymentMethod.
|
|
131
|
+
label={t('admin.paymentMethod._name')}
|
|
132
132
|
value={<Currency logo={data.paymentMethod.logo} name={data.paymentMethod.name} />}
|
|
133
133
|
/>
|
|
134
134
|
<InfoRow
|
|
@@ -205,7 +205,10 @@ export default function InvoiceDetail(props: { id: string }) {
|
|
|
205
205
|
<Box className="section">
|
|
206
206
|
<SectionHeader title={t('admin.events')} />
|
|
207
207
|
<Box className="section-body">
|
|
208
|
-
<EventList
|
|
208
|
+
<EventList
|
|
209
|
+
features={{ toolbar: false }}
|
|
210
|
+
object_id={[data.id, data.payment_intent_id].filter(Boolean).join(',')}
|
|
211
|
+
/>
|
|
209
212
|
</Box>
|
|
210
213
|
</Box>
|
|
211
214
|
</Root>
|
|
@@ -18,11 +18,11 @@ import InfoRow from '../../../../components/info-row';
|
|
|
18
18
|
import InvoiceList from '../../../../components/invoice/list';
|
|
19
19
|
import MetadataEditor from '../../../../components/metadata/editor';
|
|
20
20
|
import SectionHeader from '../../../../components/section/header';
|
|
21
|
-
import Status from '../../../../components/status';
|
|
22
21
|
import SubscriptionActions from '../../../../components/subscription/actions';
|
|
23
22
|
import SubscriptionItemList from '../../../../components/subscription/items';
|
|
23
|
+
import SubscriptionStatus from '../../../../components/subscription/status';
|
|
24
24
|
import api from '../../../../libs/api';
|
|
25
|
-
import { formatError, formatSubscriptionProduct, formatTime
|
|
25
|
+
import { formatError, formatSubscriptionProduct, formatTime } from '../../../../libs/util';
|
|
26
26
|
|
|
27
27
|
const fetchData = (id: string): Promise<TSubscriptionExpanded> => {
|
|
28
28
|
return api.get(`/api/subscriptions/${id}`).then((res) => res.data);
|
|
@@ -95,7 +95,7 @@ export default function SubscriptionDetail(props: { id: string }) {
|
|
|
95
95
|
<Typography variant="h5" sx={{ fontWeight: 600 }}>
|
|
96
96
|
{formatSubscriptionProduct(data.items)}
|
|
97
97
|
</Typography>
|
|
98
|
-
<
|
|
98
|
+
<SubscriptionStatus subscription={data} sx={{ ml: 1 }} />
|
|
99
99
|
</Stack>
|
|
100
100
|
<SubscriptionActions data={data} onChange={runAsync} variant="normal" />
|
|
101
101
|
</Stack>
|
|
@@ -166,17 +166,17 @@ export default function SubscriptionDetail(props: { id: string }) {
|
|
|
166
166
|
<InfoRow label={t('admin.subscription.discount')} value={data.discount_id ? data.discount_id : ''} />
|
|
167
167
|
<InfoRow label={t('admin.subscription.collectionMethod')} value={data.collection_method} />
|
|
168
168
|
<InfoRow
|
|
169
|
-
label={t('admin.paymentMethod.
|
|
169
|
+
label={t('admin.paymentMethod._name')}
|
|
170
170
|
value={<Currency logo={data.paymentMethod.logo} name={data.paymentMethod.name} />}
|
|
171
171
|
/>
|
|
172
172
|
<InfoRow
|
|
173
173
|
label={t('admin.paymentCurrency.name')}
|
|
174
174
|
value={<Currency logo={data.paymentCurrency.logo} name={data.paymentCurrency.symbol} />}
|
|
175
175
|
/>
|
|
176
|
-
{data.payment_details?.tx_hash && (
|
|
176
|
+
{data.payment_details?.arcblock?.tx_hash && (
|
|
177
177
|
<InfoRow
|
|
178
178
|
label={t('common.txHash')}
|
|
179
|
-
value={<TxLink hash={data.payment_details?.tx_hash} method={data.paymentMethod} />}
|
|
179
|
+
value={<TxLink hash={data.payment_details.arcblock?.tx_hash} method={data.paymentMethod} />}
|
|
180
180
|
/>
|
|
181
181
|
)}
|
|
182
182
|
</Stack>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/* eslint-disable react/no-unstable-nested-components */
|
|
2
|
+
import DidAddress from '@arcblock/ux/lib/DID';
|
|
2
3
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
4
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
4
5
|
import type { TCustomerExpanded } from '@did-pay/types';
|
|
@@ -7,10 +8,12 @@ import { Alert, Box, Button, CircularProgress, Stack, Typography } from '@mui/ma
|
|
|
7
8
|
import { styled } from '@mui/system';
|
|
8
9
|
import { useRequest, useSetState } from 'ahooks';
|
|
9
10
|
import { isEmpty } from 'lodash';
|
|
11
|
+
import { FlagEmoji } from 'react-international-phone';
|
|
10
12
|
import { Link, useNavigate } from 'react-router-dom';
|
|
11
13
|
|
|
12
14
|
import Copyable from '../../../../components/copyable';
|
|
13
15
|
import CustomerActions from '../../../../components/customer/actions';
|
|
16
|
+
import EditCustomer from '../../../../components/customer/edit';
|
|
14
17
|
import EventList from '../../../../components/event/list';
|
|
15
18
|
import InfoMetric from '../../../../components/info-metric';
|
|
16
19
|
import InfoRow from '../../../../components/info-row';
|
|
@@ -35,12 +38,11 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
35
38
|
},
|
|
36
39
|
editing: {
|
|
37
40
|
metadata: false,
|
|
38
|
-
|
|
41
|
+
customer: false,
|
|
39
42
|
},
|
|
40
43
|
loading: {
|
|
41
44
|
metadata: false,
|
|
42
|
-
|
|
43
|
-
product: false,
|
|
45
|
+
customer: false,
|
|
44
46
|
},
|
|
45
47
|
});
|
|
46
48
|
|
|
@@ -54,9 +56,9 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
54
56
|
return <CircularProgress />;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
const
|
|
59
|
+
const onUpdateMetadata = async (updates: TCustomerExpanded) => {
|
|
58
60
|
try {
|
|
59
|
-
setState((prev) => ({ loading: { ...prev.loading,
|
|
61
|
+
setState((prev) => ({ loading: { ...prev.loading, metadata: true } }));
|
|
60
62
|
await api.put(`/api/customers/${props.id}`, updates).then((res) => res.data);
|
|
61
63
|
Toast.success(t('common.saved'));
|
|
62
64
|
runAsync();
|
|
@@ -64,11 +66,24 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
64
66
|
console.error(err);
|
|
65
67
|
Toast.error(formatError(err));
|
|
66
68
|
} finally {
|
|
67
|
-
setState((prev) => ({ loading: { ...prev.loading,
|
|
69
|
+
setState((prev) => ({ loading: { ...prev.loading, metadata: false } }));
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const onUpdateInfo = async (updates: TCustomerExpanded) => {
|
|
74
|
+
try {
|
|
75
|
+
setState((prev) => ({ loading: { ...prev.loading, customer: true } }));
|
|
76
|
+
await api.put(`/api/customers/${props.id}`, updates).then((res) => res.data);
|
|
77
|
+
Toast.success(t('common.saved'));
|
|
78
|
+
runAsync();
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.error(err);
|
|
81
|
+
Toast.error(formatError(err));
|
|
82
|
+
} finally {
|
|
83
|
+
setState((prev) => ({ loading: { ...prev.loading, customer: false } }));
|
|
68
84
|
}
|
|
69
85
|
};
|
|
70
86
|
|
|
71
|
-
const onUpdateMetadata = createUpdater('metadata');
|
|
72
87
|
const onChange = (action: string) => {
|
|
73
88
|
if (action === 'remove') {
|
|
74
89
|
navigate('/admin/customers');
|
|
@@ -120,12 +135,14 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
120
135
|
variant="outlined"
|
|
121
136
|
color="inherit"
|
|
122
137
|
size="small"
|
|
123
|
-
|
|
138
|
+
disabled={state.editing.customer}
|
|
139
|
+
onClick={() => setState((prev) => ({ editing: { ...prev.editing, customer: true } }))}>
|
|
124
140
|
<Edit fontSize="small" sx={{ mr: 0.5 }} />
|
|
125
141
|
{t('common.edit')}
|
|
126
142
|
</Button>
|
|
127
143
|
</SectionHeader>
|
|
128
144
|
<Stack>
|
|
145
|
+
<InfoRow label={t('common.did')} value={<DidAddress did={data.did} />} />
|
|
129
146
|
<InfoRow label={t('admin.customer.name')} value={data.name} />
|
|
130
147
|
<InfoRow label={t('admin.customer.phone')} value={data.phone} />
|
|
131
148
|
<InfoRow label={t('admin.customer.email')} value={data.email} />
|
|
@@ -136,7 +153,16 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
136
153
|
label={t('admin.customer.address.label')}
|
|
137
154
|
value={
|
|
138
155
|
<Stack direction="column">
|
|
139
|
-
<InfoRow
|
|
156
|
+
<InfoRow
|
|
157
|
+
label={t('admin.customer.address.country')}
|
|
158
|
+
value={
|
|
159
|
+
data.address?.country ? (
|
|
160
|
+
<FlagEmoji iso2={data.address?.country} style={{ display: 'flex', width: 24 }} />
|
|
161
|
+
) : (
|
|
162
|
+
''
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
/>
|
|
140
166
|
<InfoRow label={t('admin.customer.address.state')} value={data.address?.state} />
|
|
141
167
|
<InfoRow label={t('admin.customer.address.city')} value={data.address?.city} />
|
|
142
168
|
<InfoRow label={t('admin.customer.address.line1')} value={data.address?.line1} />
|
|
@@ -145,6 +171,14 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
145
171
|
</Stack>
|
|
146
172
|
}
|
|
147
173
|
/>
|
|
174
|
+
{state.editing.customer && (
|
|
175
|
+
<EditCustomer
|
|
176
|
+
data={data}
|
|
177
|
+
loading={state.loading.customer}
|
|
178
|
+
onSave={onUpdateInfo}
|
|
179
|
+
onCancel={() => setState((prev) => ({ editing: { ...prev.editing, customer: false } }))}
|
|
180
|
+
/>
|
|
181
|
+
)}
|
|
148
182
|
</Stack>
|
|
149
183
|
</Box>
|
|
150
184
|
<Box className="section">
|
|
@@ -127,12 +127,12 @@ export default function PaymentIntentDetail(props: { id: string }) {
|
|
|
127
127
|
</Stack>
|
|
128
128
|
</Box>
|
|
129
129
|
<Box className="section">
|
|
130
|
-
<SectionHeader title={t('admin.paymentMethod.
|
|
130
|
+
<SectionHeader title={t('admin.paymentMethod._name')} />
|
|
131
131
|
<Stack>
|
|
132
132
|
<InfoRow label={t('common.id')} value={data.paymentMethod.id} />
|
|
133
133
|
<InfoRow label={t('admin.paymentMethod.type')} value={data.paymentMethod.type} />
|
|
134
134
|
<InfoRow
|
|
135
|
-
label={t('admin.paymentMethod.
|
|
135
|
+
label={t('admin.paymentMethod._name')}
|
|
136
136
|
value={<Currency logo={data.paymentMethod.logo} name={data.paymentMethod.name} />}
|
|
137
137
|
/>
|
|
138
138
|
<InfoRow
|
|
@@ -141,7 +141,12 @@ export default function PaymentIntentDetail(props: { id: string }) {
|
|
|
141
141
|
/>
|
|
142
142
|
<InfoRow
|
|
143
143
|
label={t('common.txHash')}
|
|
144
|
-
value={
|
|
144
|
+
value={
|
|
145
|
+
<TxLink
|
|
146
|
+
hash={data.payment_details?.arcblock?.tx_hash || data.metadata?.txHash}
|
|
147
|
+
method={data.paymentMethod}
|
|
148
|
+
/>
|
|
149
|
+
}
|
|
145
150
|
/>
|
|
146
151
|
</Stack>
|
|
147
152
|
</Box>
|