payment-kit 1.19.0 → 1.19.1

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 (133) 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/ws.ts +23 -1
  24. package/api/src/locales/en.ts +33 -0
  25. package/api/src/locales/zh.ts +31 -0
  26. package/api/src/queues/credit-consume.ts +715 -0
  27. package/api/src/queues/credit-grant.ts +572 -0
  28. package/api/src/queues/notification.ts +173 -128
  29. package/api/src/queues/payment.ts +210 -122
  30. package/api/src/queues/subscription.ts +179 -0
  31. package/api/src/routes/checkout-sessions.ts +157 -9
  32. package/api/src/routes/connect/shared.ts +3 -2
  33. package/api/src/routes/credit-grants.ts +241 -0
  34. package/api/src/routes/credit-transactions.ts +208 -0
  35. package/api/src/routes/index.ts +8 -0
  36. package/api/src/routes/meter-events.ts +347 -0
  37. package/api/src/routes/meters.ts +219 -0
  38. package/api/src/routes/payment-currencies.ts +14 -2
  39. package/api/src/routes/payment-links.ts +1 -1
  40. package/api/src/routes/payment-methods.ts +14 -2
  41. package/api/src/routes/prices.ts +43 -0
  42. package/api/src/routes/pricing-table.ts +13 -7
  43. package/api/src/routes/products.ts +63 -4
  44. package/api/src/routes/settings.ts +1 -1
  45. package/api/src/routes/subscriptions.ts +4 -0
  46. package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
  47. package/api/src/store/models/credit-grant.ts +486 -0
  48. package/api/src/store/models/credit-transaction.ts +268 -0
  49. package/api/src/store/models/customer.ts +8 -0
  50. package/api/src/store/models/index.ts +52 -1
  51. package/api/src/store/models/meter-event.ts +423 -0
  52. package/api/src/store/models/meter.ts +176 -0
  53. package/api/src/store/models/payment-currency.ts +66 -14
  54. package/api/src/store/models/price.ts +6 -0
  55. package/api/src/store/models/product.ts +2 -2
  56. package/api/src/store/models/subscription.ts +24 -0
  57. package/api/src/store/models/types.ts +28 -2
  58. package/api/tests/libs/subscription.spec.ts +53 -0
  59. package/blocklet.yml +9 -1
  60. package/package.json +4 -4
  61. package/scripts/sdk.js +233 -1
  62. package/src/app.tsx +10 -0
  63. package/src/components/collapse.tsx +11 -1
  64. package/src/components/customer/credit-grant-item-list.tsx +99 -0
  65. package/src/components/customer/credit-overview.tsx +233 -0
  66. package/src/components/customer/form.tsx +5 -2
  67. package/src/components/invoice/list.tsx +19 -1
  68. package/src/components/metadata/form.tsx +286 -90
  69. package/src/components/meter/actions.tsx +101 -0
  70. package/src/components/meter/add-usage-dialog.tsx +239 -0
  71. package/src/components/meter/events-list.tsx +657 -0
  72. package/src/components/meter/form.tsx +245 -0
  73. package/src/components/meter/products.tsx +264 -0
  74. package/src/components/meter/usage-guide.tsx +174 -0
  75. package/src/components/payment-currency/form.tsx +2 -0
  76. package/src/components/payment-intent/list.tsx +19 -1
  77. package/src/components/payment-link/preview.tsx +1 -1
  78. package/src/components/payment-link/product-select.tsx +52 -12
  79. package/src/components/payment-method/arcblock.tsx +2 -0
  80. package/src/components/payment-method/base.tsx +2 -0
  81. package/src/components/payment-method/bitcoin.tsx +2 -0
  82. package/src/components/payment-method/ethereum.tsx +2 -0
  83. package/src/components/payment-method/stripe.tsx +2 -0
  84. package/src/components/payouts/list.tsx +19 -1
  85. package/src/components/price/currency-select.tsx +51 -31
  86. package/src/components/price/form.tsx +881 -407
  87. package/src/components/pricing-table/preview.tsx +1 -1
  88. package/src/components/product/add-price.tsx +9 -7
  89. package/src/components/product/create.tsx +7 -4
  90. package/src/components/product/edit-price.tsx +21 -12
  91. package/src/components/product/features.tsx +17 -7
  92. package/src/components/product/form.tsx +104 -89
  93. package/src/components/refund/list.tsx +19 -1
  94. package/src/components/section/header.tsx +5 -18
  95. package/src/components/subscription/items/index.tsx +1 -1
  96. package/src/components/subscription/metrics.tsx +37 -5
  97. package/src/components/subscription/portal/actions.tsx +2 -1
  98. package/src/contexts/products.tsx +26 -9
  99. package/src/hooks/subscription.ts +34 -0
  100. package/src/libs/meter-utils.ts +196 -0
  101. package/src/libs/util.ts +4 -0
  102. package/src/locales/en.tsx +385 -4
  103. package/src/locales/zh.tsx +364 -0
  104. package/src/pages/admin/billing/index.tsx +61 -33
  105. package/src/pages/admin/billing/invoices/detail.tsx +1 -1
  106. package/src/pages/admin/billing/meters/create.tsx +60 -0
  107. package/src/pages/admin/billing/meters/detail.tsx +435 -0
  108. package/src/pages/admin/billing/meters/index.tsx +210 -0
  109. package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
  110. package/src/pages/admin/billing/subscriptions/detail.tsx +47 -14
  111. package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
  112. package/src/pages/admin/customers/customers/detail.tsx +22 -10
  113. package/src/pages/admin/customers/index.tsx +5 -0
  114. package/src/pages/admin/developers/events/detail.tsx +1 -1
  115. package/src/pages/admin/developers/index.tsx +1 -1
  116. package/src/pages/admin/payments/intents/detail.tsx +1 -1
  117. package/src/pages/admin/payments/payouts/detail.tsx +1 -1
  118. package/src/pages/admin/payments/refunds/detail.tsx +1 -1
  119. package/src/pages/admin/products/index.tsx +3 -2
  120. package/src/pages/admin/products/links/detail.tsx +1 -1
  121. package/src/pages/admin/products/prices/actions.tsx +16 -4
  122. package/src/pages/admin/products/prices/detail.tsx +30 -3
  123. package/src/pages/admin/products/prices/list.tsx +8 -1
  124. package/src/pages/admin/products/pricing-tables/detail.tsx +1 -1
  125. package/src/pages/admin/products/products/create.tsx +233 -57
  126. package/src/pages/admin/products/products/detail.tsx +2 -1
  127. package/src/pages/admin/settings/payment-methods/index.tsx +3 -0
  128. package/src/pages/customer/credit-grant/detail.tsx +308 -0
  129. package/src/pages/customer/index.tsx +35 -2
  130. package/src/pages/customer/recharge/account.tsx +5 -5
  131. package/src/pages/customer/subscription/change-payment.tsx +4 -2
  132. package/src/pages/customer/subscription/detail.tsx +48 -14
  133. package/src/pages/customer/subscription/embed.tsx +1 -1
@@ -0,0 +1,346 @@
1
+ /* eslint-disable react/no-unstable-nested-components */
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import { api, formatTime, useMobile, CreditTransactionsList, formatBNStr } from '@blocklet/payment-react';
4
+ import type { TMeterEventExpanded } from '@blocklet/payment-types';
5
+ import { ArrowBackOutlined, InfoOutlined } from '@mui/icons-material';
6
+ import { Alert, Box, CircularProgress, Divider, Stack, Typography, Chip, Tooltip } from '@mui/material';
7
+ import { styled } from '@mui/system';
8
+ import { useRequest } from 'ahooks';
9
+
10
+ import { Link } from 'react-router-dom';
11
+ import Copyable from '../../../../components/copyable';
12
+ import CustomerLink from '../../../../components/customer/link';
13
+ import EventList from '../../../../components/event/list';
14
+ import InfoRow from '../../../../components/info-row';
15
+ import InfoMetric from '../../../../components/info-metric';
16
+ import SectionHeader from '../../../../components/section/header';
17
+ import { goBackOrFallback } from '../../../../libs/util';
18
+ import InfoRowGroup from '../../../../components/info-row-group';
19
+ import MetadataList from '../../../../components/metadata/list';
20
+
21
+ const fetchData = (id: string): Promise<TMeterEventExpanded> => {
22
+ return api.get(`/api/meter-events/${id}`).then((res: any) => res.data);
23
+ };
24
+
25
+ export default function MeterEventDetail(props: { id: string }) {
26
+ const { t } = useLocaleContext();
27
+ const { isMobile } = useMobile();
28
+
29
+ const { loading, error, data } = useRequest(() => fetchData(props.id));
30
+
31
+ if (error) {
32
+ return <Alert severity="error">{error.message}</Alert>;
33
+ }
34
+
35
+ if (loading || !data) {
36
+ return <CircularProgress />;
37
+ }
38
+
39
+ const getStatusColor = (status: string) => {
40
+ switch (status) {
41
+ case 'pending':
42
+ return 'info';
43
+ case 'requires_action':
44
+ return 'warning';
45
+ case 'requires_capture':
46
+ return 'warning';
47
+ case 'completed':
48
+ return 'success';
49
+ case 'processing':
50
+ return 'info';
51
+ default:
52
+ return 'default';
53
+ }
54
+ };
55
+
56
+ return (
57
+ <Root direction="column" spacing={2.5} sx={{ mb: 4 }}>
58
+ <Box>
59
+ <Stack
60
+ className="page-header"
61
+ direction="row"
62
+ sx={{
63
+ justifyContent: 'space-between',
64
+ alignItems: 'center',
65
+ }}>
66
+ <Stack
67
+ direction="row"
68
+ onClick={() => goBackOrFallback('/admin/billing/meters')}
69
+ sx={{
70
+ alignItems: 'center',
71
+ fontWeight: 'normal',
72
+ cursor: 'pointer',
73
+ }}>
74
+ <ArrowBackOutlined fontSize="small" sx={{ mr: 0.5, color: 'text.secondary' }} />
75
+ <Typography variant="h6" sx={{ color: 'text.secondary', fontWeight: 'normal' }}>
76
+ {t('admin.meterEvents.title')}
77
+ </Typography>
78
+ </Stack>
79
+ </Stack>
80
+ <Box
81
+ sx={{
82
+ mt: 4,
83
+ mb: 3,
84
+ display: 'flex',
85
+
86
+ gap: {
87
+ xs: 2,
88
+ sm: 2,
89
+ md: 5,
90
+ },
91
+
92
+ flexWrap: 'wrap',
93
+
94
+ flexDirection: {
95
+ xs: 'column',
96
+ sm: 'column',
97
+ md: 'row',
98
+ },
99
+
100
+ alignItems: {
101
+ xs: 'flex-start',
102
+ sm: 'flex-start',
103
+ md: 'center',
104
+ },
105
+ }}>
106
+ <Stack
107
+ direction="column"
108
+ sx={{
109
+ gap: 1,
110
+ }}>
111
+ <Copyable text={props.id} />
112
+ </Stack>
113
+
114
+ <Stack
115
+ className="section-body"
116
+ sx={{
117
+ justifyContent: 'flex-start',
118
+ flexWrap: 'wrap',
119
+
120
+ 'hr.MuiDivider-root:last-child': {
121
+ display: 'none',
122
+ },
123
+
124
+ flexDirection: {
125
+ xs: 'column',
126
+ sm: 'column',
127
+ md: 'row',
128
+ },
129
+
130
+ alignItems: 'flex-start',
131
+
132
+ gap: {
133
+ xs: 1,
134
+ sm: 1,
135
+ md: 3,
136
+ },
137
+ }}>
138
+ <InfoMetric
139
+ label={t('common.status')}
140
+ value={<Chip label={data.status} color={getStatusColor(data.status)} size="small" />}
141
+ divider
142
+ />
143
+ <InfoMetric
144
+ label={t('common.meterEvent')}
145
+ value={
146
+ <Link to={`/admin/billing/meters/${data.meter.id}`} style={{ color: 'inherit' }}>
147
+ <Typography sx={{ color: 'text.link' }}>{data.meter.event_name}</Typography>
148
+ </Link>
149
+ }
150
+ divider
151
+ />
152
+ {data.subscription && (
153
+ <InfoMetric
154
+ label={t('common.subscription')}
155
+ value={
156
+ <Link to={`/admin/billing/${data.subscription.id}`} style={{ color: 'inherit' }}>
157
+ <Typography sx={{ color: 'text.link' }}>
158
+ {data.subscription.description || data.subscription.id}
159
+ </Typography>
160
+ </Link>
161
+ }
162
+ divider
163
+ />
164
+ )}
165
+ <InfoMetric
166
+ label={t('admin.meterEvent.creditConsumed')}
167
+ value={`${formatBNStr(data.credit_consumed, data.paymentCurrency?.decimal)} ${data.paymentCurrency?.symbol}`}
168
+ divider
169
+ />
170
+ {data.credit_pending !== '0' && (
171
+ <InfoMetric
172
+ label={t('admin.customer.creditGrants.pendingAmount')}
173
+ value={
174
+ <Typography sx={{ color: 'error.main' }}>
175
+ {`${formatBNStr(data.credit_pending, data.paymentCurrency?.decimal)} ${data.paymentCurrency?.symbol}`}
176
+ </Typography>
177
+ }
178
+ divider
179
+ />
180
+ )}
181
+ </Stack>
182
+ </Box>
183
+ <Divider />
184
+ </Box>
185
+ <Stack
186
+ sx={{
187
+ flexDirection: {
188
+ xs: 'column',
189
+ lg: 'row',
190
+ },
191
+ gap: {
192
+ xs: 2.5,
193
+ md: 4,
194
+ },
195
+ '.meter-event-column-1': {
196
+ minWidth: {
197
+ xs: '100%',
198
+ lg: '600px',
199
+ },
200
+ },
201
+ '.meter-event-column-2': {
202
+ width: {
203
+ xs: '100%',
204
+ md: '100%',
205
+ lg: '320px',
206
+ },
207
+ maxWidth: {
208
+ xs: '100%',
209
+ md: '33%',
210
+ },
211
+ },
212
+ }}>
213
+ <Box
214
+ className="meter-event-column-1"
215
+ sx={{
216
+ flex: 1,
217
+ gap: 2.5,
218
+ display: 'flex',
219
+ flexDirection: 'column',
220
+ }}>
221
+ <Box className="section" sx={{ containerType: 'inline-size' }}>
222
+ <SectionHeader title={t('admin.details')} />
223
+ <InfoRowGroup
224
+ sx={{
225
+ display: 'grid',
226
+ gridTemplateColumns: {
227
+ xs: 'repeat(1, 1fr)',
228
+ xl: 'repeat(2, 1fr)',
229
+ },
230
+ '@container (min-width: 1000px)': {
231
+ gridTemplateColumns: 'repeat(2, 1fr)',
232
+ },
233
+ '.info-row-wrapper': {
234
+ gap: 1,
235
+ flexDirection: {
236
+ xs: 'column',
237
+ xl: 'row',
238
+ },
239
+ alignItems: {
240
+ xs: 'flex-start',
241
+ xl: 'center',
242
+ },
243
+ '@container (min-width: 1000px)': {
244
+ flexDirection: 'row',
245
+ alignItems: 'center',
246
+ },
247
+ },
248
+ '.currency-name': {
249
+ color: 'text.secondary',
250
+ },
251
+ }}>
252
+ <InfoRow
253
+ label={t('admin.meterEvent.usageValue')}
254
+ value={`${formatBNStr(data.payload.value, data.paymentCurrency?.decimal)} ${data.paymentCurrency?.symbol}`}
255
+ />
256
+ <InfoRow label={t('admin.meterEvent.reportedAt')} value={formatTime(data.timestamp * 1000)} />
257
+ <InfoRow
258
+ label={t('common.customer')}
259
+ value={<CustomerLink customer={data.customer} size={isMobile ? 'default' : 'small'} />}
260
+ />
261
+ <InfoRow label={t('common.createdAt')} value={formatTime(data.created_at)} />
262
+ <InfoRow
263
+ label={t('common.status')}
264
+ value={
265
+ <Stack
266
+ direction="row"
267
+ spacing={1}
268
+ sx={{
269
+ alignItems: 'center',
270
+ }}>
271
+ <Chip
272
+ label={data.status}
273
+ color={getStatusColor(data.status)}
274
+ size="small"
275
+ sx={{ textTransform: 'capitalize' }}
276
+ />
277
+ {data.metadata?.last_error && (
278
+ <Tooltip
279
+ title={
280
+ <pre style={{ whiteSpace: 'break-spaces' }}>
281
+ {JSON.stringify(
282
+ {
283
+ error: data.metadata.last_error,
284
+ failed_at: data.metadata.failed_at
285
+ ? formatTime(data.metadata.failed_at * 1000)
286
+ : undefined,
287
+ attempt_count: data.attempt_count,
288
+ next_attempt: data.next_attempt ? formatTime(data.next_attempt * 1000) : undefined,
289
+ },
290
+ null,
291
+ 2
292
+ )}
293
+ </pre>
294
+ }>
295
+ <InfoOutlined fontSize="small" color="error" />
296
+ </Tooltip>
297
+ )}
298
+ </Stack>
299
+ }
300
+ />
301
+ </InfoRowGroup>
302
+ </Box>
303
+
304
+ <Divider />
305
+
306
+ <Box className="section">
307
+ <Typography variant="h3" className="section-header">
308
+ {t('admin.creditTransactions.title')}
309
+ </Typography>
310
+ <Box className="section-body">
311
+ <CreditTransactionsList
312
+ customer_id={data.payload.customer_id}
313
+ source={data.id}
314
+ showAdminColumns
315
+ mode="dashboard"
316
+ />
317
+ </Box>
318
+ </Box>
319
+ <Divider />
320
+
321
+ <Box className="section">
322
+ <SectionHeader title={t('admin.events.title')} />
323
+ <Box className="section-body">
324
+ <EventList features={{ toolbar: false }} object_id={data.id} />
325
+ </Box>
326
+ </Box>
327
+ </Box>
328
+
329
+ {isMobile && <Divider />}
330
+
331
+ <Box className="meter-event-column-2" sx={{ gap: 2.5, display: 'flex', flexDirection: 'column' }}>
332
+ {data.metadata && Object.keys(data.metadata).length > 0 && (
333
+ <Box className="section">
334
+ <SectionHeader title={t('common.metadata.label')} />
335
+ <Box className="section-body">
336
+ <MetadataList data={data.metadata} handleEditMetadata={() => {}} />
337
+ </Box>
338
+ </Box>
339
+ )}
340
+ </Box>
341
+ </Stack>
342
+ </Root>
343
+ );
344
+ }
345
+
346
+ const Root = styled(Stack)``;
@@ -1,7 +1,15 @@
1
1
  /* eslint-disable react/no-unstable-nested-components */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
3
  import Toast from '@arcblock/ux/lib/Toast';
4
- import { TxLink, api, formatError, formatTime, useMobile, hasDelegateTxHash } from '@blocklet/payment-react';
4
+ import {
5
+ TxLink,
6
+ api,
7
+ formatError,
8
+ formatTime,
9
+ useMobile,
10
+ hasDelegateTxHash,
11
+ CreditTransactionsList,
12
+ } from '@blocklet/payment-react';
5
13
  import type { TProduct, TSubscriptionExpanded } from '@blocklet/payment-types';
6
14
  import { ArrowBackOutlined } from '@mui/icons-material';
7
15
  import { Alert, Box, Button, CircularProgress, Divider, Stack, Typography } from '@mui/material';
@@ -76,6 +84,8 @@ export default function SubscriptionDetail(props: { id: string }) {
76
84
  setState((prev) => ({ editing: { ...prev.editing, metadata: true } }));
77
85
  };
78
86
 
87
+ const isCredit = data.paymentCurrency.type === 'credit';
88
+
79
89
  return (
80
90
  <Root direction="column" spacing={2.5} sx={{ mb: 4 }}>
81
91
  <Box>
@@ -310,22 +320,45 @@ export default function SubscriptionDetail(props: { id: string }) {
310
320
  </Box>
311
321
  </Box>
312
322
  <Divider />
313
- <Box className="section">
314
- <SectionHeader title={t('admin.invoices')} />
315
- <Box className="section-body">
316
- <InvoiceList features={{ customer: true, toolbar: false }} subscription_id={data.id} include_staking />
317
- </Box>
318
- </Box>
319
- <Divider />
320
- <Box className="section">
321
- <SectionHeader title={t('admin.refunds')} />
322
- <Box className="section-body">
323
- <RefundList features={{ customer: true, toolbar: false }} subscription_id={data.id} />
323
+ {isCredit ? (
324
+ <Box className="section">
325
+ <Typography variant="h3" className="section-header">
326
+ {t('admin.creditTransactions.title')}
327
+ </Typography>
328
+ <Box className="section-body">
329
+ <CreditTransactionsList
330
+ customer_id={data.customer_id}
331
+ subscription_id={data.id}
332
+ showAdminColumns
333
+ showTimeFilter
334
+ mode="dashboard"
335
+ />
336
+ </Box>
324
337
  </Box>
325
- </Box>
338
+ ) : (
339
+ <>
340
+ <Box className="section">
341
+ <SectionHeader title={t('admin.invoices')} />
342
+ <Box className="section-body">
343
+ <InvoiceList
344
+ features={{ customer: true, toolbar: false }}
345
+ subscription_id={data.id}
346
+ include_staking
347
+ />
348
+ </Box>
349
+ </Box>
350
+ <Divider />
351
+ <Box className="section">
352
+ <SectionHeader title={t('admin.refunds')} />
353
+ <Box className="section-body">
354
+ <RefundList features={{ customer: true, toolbar: false }} subscription_id={data.id} />
355
+ </Box>
356
+ </Box>
357
+ </>
358
+ )}
326
359
  <Divider />
327
360
  <Box className="section">
328
- <SectionHeader title={t('admin.events')} />
361
+ <SectionHeader title={t('admin.events.title')} />
329
362
  <Box className="section-body">
330
363
  <EventList features={{ toolbar: false }} object_id={data.id} />
331
364
  </Box>