payment-kit 1.15.33 → 1.15.35

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 (63) hide show
  1. package/api/src/integrations/stripe/handlers/setup-intent.ts +3 -1
  2. package/api/src/integrations/stripe/handlers/subscription.ts +2 -8
  3. package/api/src/integrations/stripe/resource.ts +0 -11
  4. package/api/src/libs/invoice.ts +202 -1
  5. package/api/src/libs/notification/template/subscription-canceled.ts +15 -2
  6. package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -1
  7. package/api/src/libs/notification/template/subscription-renewed.ts +1 -1
  8. package/api/src/libs/notification/template/subscription-trial-will-end.ts +9 -5
  9. package/api/src/libs/notification/template/subscription-will-canceled.ts +9 -5
  10. package/api/src/libs/notification/template/subscription-will-renew.ts +10 -12
  11. package/api/src/libs/payment.ts +3 -2
  12. package/api/src/libs/refund.ts +4 -0
  13. package/api/src/libs/subscription.ts +58 -14
  14. package/api/src/queues/invoice.ts +1 -0
  15. package/api/src/queues/payment.ts +3 -1
  16. package/api/src/queues/refund.ts +9 -8
  17. package/api/src/queues/subscription.ts +111 -40
  18. package/api/src/routes/checkout-sessions.ts +22 -6
  19. package/api/src/routes/connect/change-payment.ts +51 -34
  20. package/api/src/routes/connect/change-plan.ts +25 -3
  21. package/api/src/routes/connect/recharge.ts +28 -3
  22. package/api/src/routes/connect/setup.ts +27 -6
  23. package/api/src/routes/connect/shared.ts +223 -1
  24. package/api/src/routes/connect/subscribe.ts +25 -3
  25. package/api/src/routes/customers.ts +2 -2
  26. package/api/src/routes/invoices.ts +27 -105
  27. package/api/src/routes/payment-links.ts +3 -0
  28. package/api/src/routes/refunds.ts +22 -1
  29. package/api/src/routes/subscriptions.ts +112 -21
  30. package/api/src/routes/webhook-attempts.ts +14 -1
  31. package/api/src/store/models/invoice.ts +3 -1
  32. package/blocklet.yml +1 -1
  33. package/package.json +4 -4
  34. package/src/app.tsx +3 -1
  35. package/src/components/invoice/list.tsx +83 -31
  36. package/src/components/invoice/recharge.tsx +244 -0
  37. package/src/components/payment-intent/actions.tsx +2 -1
  38. package/src/components/payment-link/actions.tsx +6 -6
  39. package/src/components/payment-link/item.tsx +53 -18
  40. package/src/components/pricing-table/actions.tsx +14 -3
  41. package/src/components/pricing-table/payment-settings.tsx +1 -1
  42. package/src/components/refund/actions.tsx +43 -1
  43. package/src/components/refund/list.tsx +1 -1
  44. package/src/components/subscription/actions/cancel.tsx +10 -7
  45. package/src/components/subscription/metrics.tsx +1 -1
  46. package/src/components/subscription/portal/actions.tsx +22 -1
  47. package/src/components/subscription/portal/list.tsx +1 -0
  48. package/src/components/webhook/attempts.tsx +19 -121
  49. package/src/components/webhook/request-info.tsx +139 -0
  50. package/src/locales/en.tsx +4 -0
  51. package/src/locales/zh.tsx +8 -0
  52. package/src/pages/admin/billing/invoices/detail.tsx +15 -0
  53. package/src/pages/admin/billing/invoices/index.tsx +1 -1
  54. package/src/pages/admin/billing/subscriptions/detail.tsx +12 -4
  55. package/src/pages/admin/customers/customers/detail.tsx +1 -0
  56. package/src/pages/admin/payments/refunds/detail.tsx +2 -2
  57. package/src/pages/admin/products/links/create.tsx +4 -1
  58. package/src/pages/customer/index.tsx +1 -1
  59. package/src/pages/customer/invoice/detail.tsx +34 -14
  60. package/src/pages/customer/recharge.tsx +45 -35
  61. package/src/pages/customer/subscription/change-plan.tsx +8 -1
  62. package/src/pages/customer/subscription/detail.tsx +12 -22
  63. package/src/pages/customer/subscription/embed.tsx +3 -1
@@ -30,12 +30,14 @@ import { joinURL } from 'ufo';
30
30
  import type { TSubscriptionExpanded } from '@blocklet/payment-types';
31
31
  import { ArrowBackOutlined } from '@mui/icons-material';
32
32
  import { BN, fromUnitToToken } from '@ocap/util';
33
+ import RechargeList from '../../components/invoice/recharge';
33
34
  import SubscriptionDescription from '../../components/subscription/description';
34
35
  import InfoRow from '../../components/info-row';
35
36
  import Currency from '../../components/currency';
36
37
  import SubscriptionMetrics from '../../components/subscription/metrics';
37
38
  import { goBackOrFallback } from '../../libs/util';
38
39
  import CustomerLink from '../../components/customer/link';
40
+ import { useSessionContext } from '../../contexts/session';
39
41
 
40
42
  const Root = styled(Stack)(({ theme }) => ({
41
43
  marginBottom: theme.spacing(3),
@@ -70,6 +72,7 @@ export default function RechargePage() {
70
72
  } | null>(null);
71
73
  const [customAmount, setCustomAmount] = useState(false);
72
74
  const [presetAmounts, setPresetAmounts] = useState<Array<{ amount: string; cycles: number }>>([]);
75
+ const { session } = useSessionContext();
73
76
 
74
77
  const {
75
78
  paymentCurrency,
@@ -84,42 +87,41 @@ export default function RechargePage() {
84
87
 
85
88
  // @ts-ignore
86
89
  const receiveAddress = paymentDetails?.[paymentMethod?.type]?.payer;
90
+ const fetchData = async () => {
91
+ try {
92
+ setLoading(true);
93
+ const [subscriptionRes, payerTokenRes, upcomingRes] = await Promise.all([
94
+ api.get(`/api/subscriptions/${subscriptionId}`),
95
+ api.get(`/api/subscriptions/${subscriptionId}/payer-token`),
96
+ api.get(`/api/subscriptions/${subscriptionId}/upcoming`),
97
+ ]);
98
+ setSubscription(subscriptionRes.data);
99
+ setPayerValue(payerTokenRes.data);
100
+
101
+ // Calculate preset amounts
102
+ const getCycleAmount = (cycles: number) =>
103
+ fromUnitToToken(
104
+ new BN(upcomingRes.data.amount === '0' ? upcomingRes.data.minExpectedAmount : upcomingRes.data.amount)
105
+ .mul(new BN(cycles))
106
+ .toString(),
107
+ upcomingRes.data?.currency?.decimal
108
+ );
109
+ setPresetAmounts([
110
+ { amount: getCycleAmount(1), cycles: 1 },
111
+ { amount: getCycleAmount(2), cycles: 2 },
112
+ { amount: getCycleAmount(3), cycles: 3 },
113
+ { amount: getCycleAmount(5), cycles: 5 },
114
+ { amount: getCycleAmount(10), cycles: 10 },
115
+ ]);
116
+ } catch (err) {
117
+ setError(err instanceof Error ? err.message : t('common.fetchError'));
118
+ console.error(err);
119
+ } finally {
120
+ setLoading(false);
121
+ }
122
+ };
87
123
 
88
124
  useEffect(() => {
89
- const fetchData = async () => {
90
- try {
91
- setLoading(true);
92
- const [subscriptionRes, payerTokenRes, upcomingRes] = await Promise.all([
93
- api.get(`/api/subscriptions/${subscriptionId}`),
94
- api.get(`/api/subscriptions/${subscriptionId}/payer-token`),
95
- api.get(`/api/subscriptions/${subscriptionId}/upcoming`),
96
- ]);
97
- setSubscription(subscriptionRes.data);
98
- setPayerValue(payerTokenRes.data);
99
-
100
- // Calculate preset amounts
101
- const getCycleAmount = (cycles: number) =>
102
- fromUnitToToken(
103
- new BN(upcomingRes.data.amount === '0' ? upcomingRes.data.minExpectedAmount : upcomingRes.data.amount)
104
- .mul(new BN(cycles))
105
- .toString(),
106
- upcomingRes.data?.currency?.decimal
107
- );
108
- setPresetAmounts([
109
- { amount: getCycleAmount(1), cycles: 1 },
110
- { amount: getCycleAmount(2), cycles: 2 },
111
- { amount: getCycleAmount(3), cycles: 3 },
112
- { amount: getCycleAmount(5), cycles: 5 },
113
- { amount: getCycleAmount(10), cycles: 10 },
114
- ]);
115
- } catch (err) {
116
- setError(err instanceof Error ? err.message : t('common.fetchError'));
117
- console.error(err);
118
- } finally {
119
- setLoading(false);
120
- }
121
- };
122
-
123
125
  fetchData();
124
126
  }, [subscriptionId]);
125
127
 
@@ -135,7 +137,7 @@ export default function RechargePage() {
135
137
  onSuccess: () => {
136
138
  connect.close();
137
139
  Toast.success(t('customer.recharge.success'));
138
- window.location.reload();
140
+ fetchData();
139
141
  },
140
142
  onClose: () => {
141
143
  connect.close();
@@ -181,6 +183,9 @@ export default function RechargePage() {
181
183
  return <Alert severity="info">{t('common.dataNotFound')}</Alert>;
182
184
  }
183
185
 
186
+ if (subscription?.customer?.did && session?.user?.did && subscription.customer.did !== session.user.did) {
187
+ return <Alert severity="error">You do not have permission to access other customer data</Alert>;
188
+ }
184
189
  const currentBalance = formatBNStr(payerValue?.token || '0', paymentCurrency?.decimal, 6, false);
185
190
 
186
191
  const supportRecharge = ['arcblock', 'ethereum'].includes(paymentMethod?.type || '');
@@ -414,6 +419,11 @@ export default function RechargePage() {
414
419
  <Alert severity="info">{t('customer.recharge.unsupported')}</Alert>
415
420
  )}
416
421
  </Box>
422
+ <Divider />
423
+ <Typography variant="h2" gutterBottom>
424
+ {t('customer.recharge.history')}
425
+ </Typography>
426
+ <RechargeList subscription_id={subscriptionId} currency_id={paymentCurrency?.id} />
417
427
  </Root>
418
428
  );
419
429
  }
@@ -243,7 +243,14 @@ export default function CustomerSubscriptionChangePlan() {
243
243
  </Stack>
244
244
  <Stack direction="column" sx={{ marginTop: 32 }}>
245
245
  <SectionHeader title={t('payment.customer.changePlan.config')} />
246
- <PricingTable mode="select" alignItems="left" interval={interval} table={table} onSelect={handleSelect} />
246
+ <PricingTable
247
+ mode="select"
248
+ alignItems="left"
249
+ interval={interval}
250
+ table={table}
251
+ onSelect={handleSelect}
252
+ hideCurrency
253
+ />
247
254
  </Stack>
248
255
  {!data.table && <Alert severity="error">{t('payment.customer.changePlan.tableNotFound')}</Alert>}
249
256
  {state.priceId && state.total && state.setup && (
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
- import { CustomerInvoiceList, TxLink, api, formatTime } from '@blocklet/payment-react';
3
+ import { CustomerInvoiceList, TxLink, api, formatTime, hasDelegateTxHash } from '@blocklet/payment-react';
4
4
  import type { TSubscriptionExpanded } from '@blocklet/payment-types';
5
5
  import { ArrowBackOutlined } from '@mui/icons-material';
6
6
  import { Alert, Box, Button, CircularProgress, Divider, Stack, Typography } from '@mui/material';
@@ -15,7 +15,8 @@ import SubscriptionDescription from '../../../components/subscription/descriptio
15
15
  import SubscriptionItemList from '../../../components/subscription/items';
16
16
  import SubscriptionMetrics from '../../../components/subscription/metrics';
17
17
  import SubscriptionActions from '../../../components/subscription/portal/actions';
18
- import { canChangePaymentMethod, goBackOrFallback } from '../../../libs/util';
18
+ import { canChangePaymentMethod } from '../../../libs/util';
19
+ import { useSessionContext } from '../../../contexts/session';
19
20
 
20
21
  const fetchData = (id: string | undefined): Promise<TSubscriptionExpanded> => {
21
22
  return api.get(`/api/subscriptions/${id}`).then((res) => res.data);
@@ -24,19 +25,15 @@ const fetchData = (id: string | undefined): Promise<TSubscriptionExpanded> => {
24
25
  const InfoDirection = 'column';
25
26
  const InfoAlignItems = 'flex-start';
26
27
 
27
- const supportRecharge = (subscription: TSubscriptionExpanded) => {
28
- return (
29
- ['active', 'trialing', 'past_due'].includes(subscription?.status) &&
30
- ['arcblock', 'ethereum'].includes(subscription?.paymentMethod?.type)
31
- );
32
- };
33
-
34
28
  export default function CustomerSubscriptionDetail() {
35
29
  const { id } = useParams() as { id: string };
36
30
  const navigate = useNavigate();
37
31
  const { t } = useLocaleContext();
32
+ const { session } = useSessionContext();
38
33
  const { loading, error, data, refresh } = useRequest(() => fetchData(id));
39
-
34
+ if (data?.customer?.did && session?.user?.did && data.customer.did !== session.user.did) {
35
+ return <Alert severity="error">You do not have permission to access other customer data</Alert>;
36
+ }
40
37
  if (error) {
41
38
  return <Alert severity="error">{error.message}</Alert>;
42
39
  }
@@ -56,7 +53,7 @@ export default function CustomerSubscriptionDetail() {
56
53
  sx={{ position: 'relative', mt: '16px' }}>
57
54
  <Stack
58
55
  direction="row"
59
- onClick={() => goBackOrFallback('/customer')}
56
+ onClick={() => navigate('/customer', { replace: true })}
60
57
  alignItems="center"
61
58
  sx={{ fontWeight: 'normal', cursor: 'pointer' }}>
62
59
  <ArrowBackOutlined fontSize="small" sx={{ mr: 0.5, color: 'text.secondary' }} />
@@ -69,6 +66,7 @@ export default function CustomerSubscriptionDetail() {
69
66
  subscription={data}
70
67
  onChange={() => refresh()}
71
68
  showExtra
69
+ showRecharge
72
70
  actionProps={{
73
71
  cancel: {
74
72
  variant: 'outlined',
@@ -84,14 +82,6 @@ export default function CustomerSubscriptionDetail() {
84
82
  },
85
83
  }}
86
84
  />
87
- {supportRecharge(data) && (
88
- <Button
89
- variant="outlined"
90
- color="primary"
91
- onClick={() => navigate(`/customer/subscription/${data.id}/recharge`)}>
92
- {t('customer.recharge.title')}
93
- </Button>
94
- )}
95
85
  </Stack>
96
86
  </Stack>
97
87
  <Box
@@ -243,7 +233,7 @@ export default function CustomerSubscriptionDetail() {
243
233
  direction={InfoDirection}
244
234
  alignItems={InfoAlignItems}
245
235
  />
246
- {(data.payment_details?.arcblock?.tx_hash || data.payment_details?.ethereum?.tx_hash) && (
236
+ {data.payment_details && hasDelegateTxHash(data.payment_details, data.paymentMethod) && (
247
237
  <InfoRow
248
238
  label={t('common.delegateTxHash')}
249
239
  value={<TxLink details={data.payment_details} method={data.paymentMethod} />}
@@ -251,7 +241,7 @@ export default function CustomerSubscriptionDetail() {
251
241
  alignItems={InfoAlignItems}
252
242
  />
253
243
  )}
254
- {data.payment_details?.arcblock?.staking?.tx_hash && (
244
+ {data.paymentMethod?.type === 'arcblock' && data.payment_details?.arcblock?.staking?.tx_hash && (
255
245
  <InfoRow
256
246
  label={t('common.stakeTxHash')}
257
247
  value={
@@ -300,7 +290,7 @@ export default function CustomerSubscriptionDetail() {
300
290
  {t('customer.invoiceHistory')}
301
291
  </Typography>
302
292
  <Box className="section-body">
303
- <CustomerInvoiceList subscription_id={data.id} include_staking type="table" />
293
+ <CustomerInvoiceList subscription_id={data.id} type="table" include_staking />
304
294
  </Box>
305
295
  </Box>
306
296
  </Root>
@@ -11,6 +11,7 @@ import {
11
11
  formatToDate,
12
12
  formatToDatetime,
13
13
  getDidConnectQueryParams,
14
+ getInvoiceDescriptionAndReason,
14
15
  getInvoiceStatusColor,
15
16
  getPrefix,
16
17
  getSubscriptionStatusColor,
@@ -210,8 +211,9 @@ export default function SubscriptionEmbed() {
210
211
  {(invoices as any).map((item: any) => {
211
212
  return (
212
213
  <ListItem key={item.id} disableGutters sx={{ display: 'flex', justifyContent: 'space-between' }}>
213
- <Typography component="span" sx={{ flex: 3 }}>
214
+ <Typography component="div" sx={{ flex: 3, gap: 1, display: 'flex', alignItems: 'center' }}>
214
215
  {formatToDate(item.created_at, locale, 'YYYY-MM-DD')}
216
+ <Status label={getInvoiceDescriptionAndReason(item, locale)?.type} />
215
217
  </Typography>
216
218
  <Typography component="span" sx={{ flex: 1, textAlign: 'right' }}>
217
219
  {formatBNStr(item.total, item.paymentCurrency.decimal)}&nbsp;