payment-kit 1.19.0 → 1.19.2

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 (139) hide show
  1. package/api/src/crons/index.ts +8 -0
  2. package/api/src/index.ts +4 -0
  3. package/api/src/libs/credit-grant.ts +146 -0
  4. package/api/src/libs/env.ts +1 -0
  5. package/api/src/libs/invoice.ts +4 -3
  6. package/api/src/libs/notification/template/base.ts +388 -2
  7. package/api/src/libs/notification/template/customer-credit-grant-granted.ts +149 -0
  8. package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +151 -0
  9. package/api/src/libs/notification/template/customer-credit-insufficient.ts +254 -0
  10. package/api/src/libs/notification/template/subscription-canceled.ts +193 -202
  11. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +215 -237
  12. package/api/src/libs/notification/template/subscription-renewed.ts +130 -200
  13. package/api/src/libs/notification/template/subscription-succeeded.ts +100 -202
  14. package/api/src/libs/notification/template/subscription-trial-start.ts +142 -188
  15. package/api/src/libs/notification/template/subscription-trial-will-end.ts +146 -174
  16. package/api/src/libs/notification/template/subscription-upgraded.ts +96 -192
  17. package/api/src/libs/notification/template/subscription-will-canceled.ts +94 -135
  18. package/api/src/libs/notification/template/subscription-will-renew.ts +220 -245
  19. package/api/src/libs/payment.ts +69 -0
  20. package/api/src/libs/queue/index.ts +3 -2
  21. package/api/src/libs/session.ts +8 -0
  22. package/api/src/libs/subscription.ts +74 -3
  23. package/api/src/libs/util.ts +3 -1
  24. package/api/src/libs/ws.ts +23 -1
  25. package/api/src/locales/en.ts +33 -0
  26. package/api/src/locales/zh.ts +31 -0
  27. package/api/src/queues/credit-consume.ts +728 -0
  28. package/api/src/queues/credit-grant.ts +572 -0
  29. package/api/src/queues/notification.ts +173 -128
  30. package/api/src/queues/payment.ts +210 -122
  31. package/api/src/queues/subscription.ts +179 -0
  32. package/api/src/routes/checkout-sessions.ts +157 -9
  33. package/api/src/routes/connect/shared.ts +3 -2
  34. package/api/src/routes/credit-grants.ts +241 -0
  35. package/api/src/routes/credit-transactions.ts +208 -0
  36. package/api/src/routes/customers.ts +34 -5
  37. package/api/src/routes/index.ts +8 -0
  38. package/api/src/routes/meter-events.ts +347 -0
  39. package/api/src/routes/meters.ts +219 -0
  40. package/api/src/routes/payment-currencies.ts +20 -2
  41. package/api/src/routes/payment-links.ts +1 -1
  42. package/api/src/routes/payment-methods.ts +14 -2
  43. package/api/src/routes/prices.ts +43 -0
  44. package/api/src/routes/pricing-table.ts +13 -7
  45. package/api/src/routes/products.ts +63 -4
  46. package/api/src/routes/settings.ts +1 -1
  47. package/api/src/routes/subscriptions.ts +4 -0
  48. package/api/src/routes/webhook-endpoints.ts +0 -3
  49. package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
  50. package/api/src/store/models/credit-grant.ts +486 -0
  51. package/api/src/store/models/credit-transaction.ts +268 -0
  52. package/api/src/store/models/customer.ts +8 -0
  53. package/api/src/store/models/index.ts +52 -1
  54. package/api/src/store/models/meter-event.ts +423 -0
  55. package/api/src/store/models/meter.ts +176 -0
  56. package/api/src/store/models/payment-currency.ts +66 -14
  57. package/api/src/store/models/price.ts +6 -0
  58. package/api/src/store/models/product.ts +2 -2
  59. package/api/src/store/models/subscription.ts +24 -0
  60. package/api/src/store/models/types.ts +28 -2
  61. package/api/tests/libs/subscription.spec.ts +53 -0
  62. package/blocklet.yml +9 -1
  63. package/package.json +4 -4
  64. package/scripts/sdk.js +233 -1
  65. package/src/app.tsx +10 -0
  66. package/src/components/collapse.tsx +11 -1
  67. package/src/components/conditional-section.tsx +87 -0
  68. package/src/components/customer/credit-grant-item-list.tsx +99 -0
  69. package/src/components/customer/credit-overview.tsx +246 -0
  70. package/src/components/customer/form.tsx +7 -3
  71. package/src/components/invoice/list.tsx +19 -1
  72. package/src/components/metadata/form.tsx +287 -91
  73. package/src/components/meter/actions.tsx +101 -0
  74. package/src/components/meter/add-usage-dialog.tsx +239 -0
  75. package/src/components/meter/events-list.tsx +657 -0
  76. package/src/components/meter/form.tsx +245 -0
  77. package/src/components/meter/products.tsx +264 -0
  78. package/src/components/meter/usage-guide.tsx +174 -0
  79. package/src/components/payment-currency/form.tsx +2 -0
  80. package/src/components/payment-intent/list.tsx +19 -1
  81. package/src/components/payment-link/item.tsx +2 -2
  82. package/src/components/payment-link/preview.tsx +1 -1
  83. package/src/components/payment-link/product-select.tsx +52 -12
  84. package/src/components/payment-method/arcblock.tsx +2 -0
  85. package/src/components/payment-method/base.tsx +2 -0
  86. package/src/components/payment-method/bitcoin.tsx +2 -0
  87. package/src/components/payment-method/ethereum.tsx +2 -0
  88. package/src/components/payment-method/stripe.tsx +2 -0
  89. package/src/components/payouts/list.tsx +19 -1
  90. package/src/components/payouts/portal/list.tsx +6 -11
  91. package/src/components/price/currency-select.tsx +56 -32
  92. package/src/components/price/form.tsx +912 -407
  93. package/src/components/pricing-table/preview.tsx +1 -1
  94. package/src/components/product/add-price.tsx +9 -7
  95. package/src/components/product/create.tsx +7 -4
  96. package/src/components/product/edit-price.tsx +21 -12
  97. package/src/components/product/features.tsx +17 -7
  98. package/src/components/product/form.tsx +100 -90
  99. package/src/components/refund/list.tsx +19 -1
  100. package/src/components/section/header.tsx +5 -18
  101. package/src/components/subscription/items/index.tsx +1 -1
  102. package/src/components/subscription/metrics.tsx +37 -5
  103. package/src/components/subscription/portal/actions.tsx +2 -1
  104. package/src/contexts/products.tsx +26 -9
  105. package/src/hooks/subscription.ts +34 -0
  106. package/src/libs/meter-utils.ts +196 -0
  107. package/src/libs/util.ts +4 -0
  108. package/src/locales/en.tsx +389 -5
  109. package/src/locales/zh.tsx +368 -1
  110. package/src/pages/admin/billing/index.tsx +61 -33
  111. package/src/pages/admin/billing/invoices/detail.tsx +1 -1
  112. package/src/pages/admin/billing/meters/create.tsx +60 -0
  113. package/src/pages/admin/billing/meters/detail.tsx +435 -0
  114. package/src/pages/admin/billing/meters/index.tsx +210 -0
  115. package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
  116. package/src/pages/admin/billing/subscriptions/detail.tsx +47 -14
  117. package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
  118. package/src/pages/admin/customers/customers/detail.tsx +14 -10
  119. package/src/pages/admin/customers/index.tsx +5 -0
  120. package/src/pages/admin/developers/events/detail.tsx +1 -1
  121. package/src/pages/admin/developers/index.tsx +1 -1
  122. package/src/pages/admin/payments/intents/detail.tsx +1 -1
  123. package/src/pages/admin/payments/payouts/detail.tsx +1 -1
  124. package/src/pages/admin/payments/refunds/detail.tsx +1 -1
  125. package/src/pages/admin/products/index.tsx +3 -2
  126. package/src/pages/admin/products/links/detail.tsx +1 -1
  127. package/src/pages/admin/products/prices/actions.tsx +16 -4
  128. package/src/pages/admin/products/prices/detail.tsx +30 -3
  129. package/src/pages/admin/products/prices/list.tsx +8 -1
  130. package/src/pages/admin/products/pricing-tables/detail.tsx +1 -1
  131. package/src/pages/admin/products/products/create.tsx +233 -57
  132. package/src/pages/admin/products/products/detail.tsx +2 -1
  133. package/src/pages/admin/settings/payment-methods/index.tsx +3 -0
  134. package/src/pages/customer/credit-grant/detail.tsx +308 -0
  135. package/src/pages/customer/index.tsx +44 -9
  136. package/src/pages/customer/recharge/account.tsx +5 -5
  137. package/src/pages/customer/subscription/change-payment.tsx +4 -2
  138. package/src/pages/customer/subscription/detail.tsx +48 -14
  139. package/src/pages/customer/subscription/embed.tsx +1 -1
@@ -7,6 +7,7 @@ import {
7
7
  usePaymentContext,
8
8
  OverdueInvoicePayment,
9
9
  } from '@blocklet/payment-react';
10
+
10
11
  import type { GroupedBN, TCustomerExpanded } from '@blocklet/payment-types';
11
12
  import {
12
13
  ExpandMore,
@@ -38,6 +39,8 @@ import { flatten, isEmpty } from 'lodash';
38
39
  import { memo, useEffect, useState } from 'react';
39
40
  import { useNavigate, useSearchParams } from 'react-router-dom';
40
41
  import { joinURL } from 'ufo';
42
+ import CreditOverview from '../../components/customer/credit-overview';
43
+ import ConditionalSection from '../../components/conditional-section';
41
44
 
42
45
  import { useTransitionContext } from '../../components/progress-bar';
43
46
  import CurrentSubscriptions from '../../components/subscription/portal/list';
@@ -46,7 +49,15 @@ import { useSessionContext } from '../../contexts/session';
46
49
  import api from '../../libs/api';
47
50
  import CustomerRevenueList from '../../components/payouts/portal/list';
48
51
 
49
- type Result = TCustomerExpanded & { summary: { [key: string]: GroupedBN }; error?: string };
52
+ type Result = TCustomerExpanded & {
53
+ summary: { [key: string]: GroupedBN };
54
+ creditSummary?: {
55
+ grants?: { [key: string]: { totalAmount: string; remainingAmount: string; grantCount: number } };
56
+ transactions?: any;
57
+ pendingAmount?: { [key: string]: string };
58
+ };
59
+ error?: string;
60
+ };
50
61
 
51
62
  const fetchData = (): Promise<Result> => {
52
63
  return api.get('/api/customers/me?skipError=true&create=true').then((res) => res.data);
@@ -204,7 +215,6 @@ export default function CustomerHome() {
204
215
  const navigate = useNavigate();
205
216
  const [subscriptionStatus, setSubscriptionStatus] = useState(false);
206
217
  const [hasSubscriptions, setHasSubscriptions] = useState(false);
207
- const [hasRevenues, setHasRevenues] = useState(false);
208
218
  const { startTransition } = useTransitionContext();
209
219
  const {
210
220
  data,
@@ -273,6 +283,12 @@ export default function CustomerHome() {
273
283
  }
274
284
  };
275
285
 
286
+ // const handleAddCredit = (currency: any) => {
287
+ // if (currency?.id) {
288
+ // navigate(`/customer/credit-packages?currency=${currency.id}`);
289
+ // }
290
+ // };
291
+
276
292
  const onToggleActive = (e: SelectChangeEvent) => {
277
293
  setSubscriptionLoading(true);
278
294
  setState({ onlyActive: e.target.value === 'active' });
@@ -281,6 +297,10 @@ export default function CustomerHome() {
281
297
  }, 300);
282
298
  };
283
299
 
300
+ // const handleViewCreditDetails = (currencyId: string) => {
301
+ // navigate(`/customer/credit-grants?currency=${currencyId}`);
302
+ // };
303
+
284
304
  const SubscriptionCard =
285
305
  loadingCard || !hasSubscriptions ? null : (
286
306
  <Box className="base-card section section-subscription">
@@ -428,7 +448,6 @@ export default function CustomerHome() {
428
448
  gap: 1,
429
449
  mt: 1.5,
430
450
  }}>
431
- {/* 使用配置渲染卡片 */}
432
451
  {Object.entries(CARD_CONFIG).map(([type, config]) => {
433
452
  if (!isCardVisible(type, config, data, c, method)) {
434
453
  return null;
@@ -460,6 +479,18 @@ export default function CustomerHome() {
460
479
  </Box>
461
480
  );
462
481
 
482
+ // 独立的Credit Card组件
483
+ const CreditCard = (
484
+ <ConditionalSection skeleton={loadingCard}>
485
+ <Box className="base-card section section-credit">
486
+ <Box className="section-header" sx={{ mb: 2 }}>
487
+ <Typography variant="h3">{t('admin.creditGrants.title')}</Typography>
488
+ </Box>
489
+ <CreditOverview customerId={data?.id || ''} settings={settings} />
490
+ </Box>
491
+ </ConditionalSection>
492
+ );
493
+
463
494
  const InvoiceCard = loadingCard ? (
464
495
  <CardSkeleton height={200} />
465
496
  ) : (
@@ -488,13 +519,15 @@ export default function CustomerHome() {
488
519
  </Box>
489
520
  );
490
521
 
491
- const RevenueCard = loadingCard ? null : (
492
- <Box className="base-card section section-revenue" sx={{ visibility: hasRevenues ? 'visible' : 'hidden' }}>
493
- <Box className="section-header">
494
- <Typography variant="h3">{t('customer.payout.title')}</Typography>
522
+ const RevenueCard = (
523
+ <ConditionalSection skeleton={loadingCard}>
524
+ <Box className="base-card section section-revenue">
525
+ <Box className="section-header">
526
+ <Typography variant="h3">{t('customer.payout.title')}</Typography>
527
+ </Box>
528
+ <CustomerRevenueList />
495
529
  </Box>
496
- <CustomerRevenueList setHasRevenues={setHasRevenues} />
497
- </Box>
530
+ </ConditionalSection>
498
531
  );
499
532
 
500
533
  return (
@@ -551,6 +584,8 @@ export default function CustomerHome() {
551
584
  <Root>
552
585
  {SummaryCard}
553
586
  {SummaryCard && <Divider />}
587
+ {CreditCard}
588
+ {CreditCard && <Divider />}
554
589
  {SubscriptionCard}
555
590
  {SubscriptionCard && <Divider />}
556
591
  {InvoiceCard}
@@ -553,14 +553,14 @@ export default function BalanceRechargePage() {
553
553
  helperText={amountError}
554
554
  onChange={handleAmountChange}
555
555
  inputRef={customInputRef}
556
+ autoComplete="off"
556
557
  slotProps={{
557
558
  input: {
558
559
  endAdornment: <Typography>{currency.symbol}</Typography>,
559
- autoComplete: 'off',
560
- inputProps: {
561
- min: 0,
562
- max: MAX_SAFE_AMOUNT,
563
- },
560
+ },
561
+ htmlInput: {
562
+ min: 0,
563
+ max: MAX_SAFE_AMOUNT,
564
564
  },
565
565
  }}
566
566
  />
@@ -17,6 +17,7 @@ import {
17
17
  getPriceCurrencyOptions,
18
18
  getStatementDescriptor,
19
19
  isValidCountry,
20
+ showStaking,
20
21
  usePaymentContext,
21
22
  } from '@blocklet/payment-react';
22
23
  import type { TCustomer, TPaymentCurrency, TPaymentMethod, TSubscriptionExpanded } from '@blocklet/payment-types';
@@ -222,6 +223,7 @@ function CustomerSubscriptionChangePayment({ subscription, customer, onComplete
222
223
  setState({ stripePaying: false });
223
224
  };
224
225
 
226
+ const currency = findCurrency(settings.paymentMethods, selectedCurrencyId) as TPaymentCurrency;
225
227
  return (
226
228
  <Stack direction="column" spacing={4} sx={{ maxWidth: '540px' }}>
227
229
  <Stack
@@ -248,10 +250,10 @@ function CustomerSubscriptionChangePayment({ subscription, customer, onComplete
248
250
  <SectionHeader title={t('payment.customer.changePayment.review')} />
249
251
  <PaymentSummary
250
252
  items={subscription.items as any[]}
251
- currency={findCurrency(settings.paymentMethods, selectedCurrencyId) as TPaymentCurrency}
253
+ currency={currency}
252
254
  trialInDays={0}
253
255
  billingThreshold={0}
254
- showStaking={method.type === 'arcblock' && !subscription.billing_thresholds?.no_stake}
256
+ showStaking={showStaking(method, currency, !!subscription.billing_thresholds?.no_stake)}
255
257
  />
256
258
  </Stack>
257
259
  <Stack direction="column" spacing={2}>
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
3
  import {
4
+ CreditTransactionsList,
4
5
  CustomerInvoiceList,
5
6
  TxLink,
6
7
  api,
@@ -45,7 +46,7 @@ import SubscriptionActions, { ActionMethods } from '../../../components/subscrip
45
46
  import { canChangePaymentMethod } from '../../../libs/util';
46
47
  import { useSessionContext } from '../../../contexts/session';
47
48
  import InfoMetric from '../../../components/info-metric';
48
- import { useUnpaidInvoicesCheckForSubscription } from '../../../hooks/subscription';
49
+ import { useUnpaidInvoicesCheckForSubscription, usePendingAmountForSubscription } from '../../../hooks/subscription';
49
50
  import { formatSmartDuration, TimeUnit } from '../../../libs/dayjs';
50
51
  import InfoRowGroup from '../../../components/info-row-group';
51
52
 
@@ -74,6 +75,7 @@ export default function CustomerSubscriptionDetail() {
74
75
  const { session } = useSessionContext();
75
76
  const { loading, error, data, refresh } = useRequest(() => fetchData(id));
76
77
  const { hasUnpaid, checkUnpaidInvoices } = useUnpaidInvoicesCheckForSubscription(id);
78
+ const { hasPendingAmount, pendingAmount } = usePendingAmountForSubscription(id, data?.paymentCurrency);
77
79
  const overdraftProtectionReady =
78
80
  ['active', 'trialing', 'past_due'].includes(data?.status || '') && data?.paymentMethod?.type === 'arcblock';
79
81
  const {
@@ -119,11 +121,13 @@ export default function CustomerSubscriptionDetail() {
119
121
  return <CircularProgress />;
120
122
  }
121
123
 
124
+ const isCredit = data?.paymentCurrency?.type === 'credit';
122
125
  const showOverdraftProtection =
123
126
  data?.paymentMethod?.type === 'arcblock' &&
124
127
  !overdraftProtectionLoading &&
125
128
  !!overdraftProtection &&
126
- ['active', 'trialing', 'past_due'].includes(data.status);
129
+ ['active', 'trialing', 'past_due'].includes(data.status) &&
130
+ !isCredit;
127
131
 
128
132
  const renderOverdraftProtectionLabel = () => {
129
133
  const enabled = data?.overdraft_protection?.enabled;
@@ -303,6 +307,14 @@ export default function CustomerSubscriptionDetail() {
303
307
  {t('customer.unpaidInvoicesWarningTip')}
304
308
  </Alert>
305
309
  )}
310
+ {hasPendingAmount && pendingAmount && (
311
+ <Alert severity="error" sx={{ mb: 2 }}>
312
+ {t('customer.pendingAmountWarningTip', {
313
+ amount: formatBNStr(pendingAmount, data?.paymentCurrency?.decimal),
314
+ symbol: data?.paymentCurrency?.symbol,
315
+ })}
316
+ </Alert>
317
+ )}
306
318
  <Stack
307
319
  className="page-header"
308
320
  direction="row"
@@ -491,6 +503,17 @@ export default function CustomerSubscriptionDetail() {
491
503
  value={renderOverdraftProtectionLabel()}
492
504
  />
493
505
  )}
506
+ {isCredit && hasPendingAmount && (
507
+ <InfoMetric
508
+ label={t('admin.customer.creditGrants.pendingAmount')}
509
+ value={
510
+ <Typography
511
+ sx={{
512
+ color: 'error.main',
513
+ }}>{`${formatBNStr(pendingAmount, data?.paymentCurrency?.decimal)} ${data?.paymentCurrency?.symbol}`}</Typography>
514
+ }
515
+ />
516
+ )}
494
517
  </Stack>
495
518
  </Box>
496
519
  </Box>
@@ -642,19 +665,30 @@ export default function CustomerSubscriptionDetail() {
642
665
  </Box>
643
666
  </Box>
644
667
  <Divider />
645
- <Box className="section">
646
- <Typography variant="h3" className="section-header">
647
- {t('customer.invoiceHistory')}
648
- </Typography>
649
- <Box className="section-body">
650
- <CustomerInvoiceList
651
- subscription_id={data.id}
652
- type="table"
653
- include_staking
654
- status="open,paid,uncollectible,void"
655
- />
668
+ {isCredit ? (
669
+ <Box className="section">
670
+ <Typography variant="h3" className="section-header">
671
+ {t('admin.creditTransactions.title')}
672
+ </Typography>
673
+ <Box className="section-body">
674
+ <CreditTransactionsList customer_id={data.customer_id} subscription_id={data.id} showAdminColumns={false} />
675
+ </Box>
656
676
  </Box>
657
- </Box>
677
+ ) : (
678
+ <Box className="section">
679
+ <Typography variant="h3" className="section-header">
680
+ {t('customer.invoiceHistory')}
681
+ </Typography>
682
+ <Box className="section-body">
683
+ <CustomerInvoiceList
684
+ subscription_id={data.id}
685
+ type="table"
686
+ include_staking
687
+ status="open,paid,uncollectible,void"
688
+ />
689
+ </Box>
690
+ </Box>
691
+ )}
658
692
  </Root>
659
693
  );
660
694
  }
@@ -60,7 +60,7 @@ const fetchSubscriptionData = (id: string, authToken: string): Promise<TSubscrip
60
60
  throw new Error('Subscription ID is missing');
61
61
  }
62
62
 
63
- return api.get(`/api/subscriptions/${id}?authToken=${authToken}`).then((res) => res.data);
63
+ return api.get(`/api/subscriptions/${id}?authToken=${authToken}`).then((res: any) => res.data);
64
64
  };
65
65
 
66
66
  const checkHasPastDue = async (subscriptionId: string, authToken: string): Promise<boolean> => {