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
@@ -0,0 +1,239 @@
1
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
+ import { api, formatError, useMobile, getCustomerAvatar, FormLabel } from '@blocklet/payment-react';
3
+ import type { TCustomer, TPaymentCurrency } from '@blocklet/payment-types';
4
+ import { Box, Stack, Autocomplete, TextField, Button, Avatar, Typography } from '@mui/material';
5
+ import { useSetState } from 'ahooks';
6
+ import Toast from '@arcblock/ux/lib/Toast';
7
+ import Dialog from '@arcblock/ux/lib/Dialog';
8
+ import dayjs from '../../libs/dayjs';
9
+
10
+ interface AddUsageDialogProps {
11
+ open: boolean;
12
+ onClose: () => void;
13
+ meterId: string;
14
+ customers: TCustomer[];
15
+ paymentCurrency: TPaymentCurrency;
16
+ onSuccess: () => void;
17
+ }
18
+
19
+ interface AddUsageState {
20
+ loading: boolean;
21
+ form: {
22
+ customer_id: string;
23
+ value: string;
24
+ subscription_id: string;
25
+ timestamp: Date;
26
+ };
27
+ }
28
+
29
+ const addMeterEvent = (data: any): Promise<any> => {
30
+ return api
31
+ .post('/api/meter-events', {
32
+ event_name: data.event_name,
33
+ payload: {
34
+ customer_id: data.customer_id,
35
+ value: data.value,
36
+ subscription_id: data.subscription_id || undefined,
37
+ },
38
+ timestamp: data.timestamp,
39
+ identifier: `manual_${Date.now()}`,
40
+ metadata: {
41
+ source: 'manual',
42
+ created_by: 'admin',
43
+ },
44
+ })
45
+ .then((res) => res.data);
46
+ };
47
+
48
+ export default function AddUsageDialog({ open, onClose, meterId, customers, onSuccess }: AddUsageDialogProps) {
49
+ const { t } = useLocaleContext();
50
+ const { isMobile } = useMobile('md');
51
+
52
+ const [state, setState] = useSetState<AddUsageState>({
53
+ loading: false,
54
+ form: {
55
+ customer_id: '',
56
+ value: '',
57
+ subscription_id: '',
58
+ timestamp: new Date(),
59
+ },
60
+ });
61
+
62
+ const getCustomerDisplayName = (customer: TCustomer) => {
63
+ return customer.name || customer.email || customer.did || customer.id;
64
+ };
65
+
66
+ const handleClose = () => {
67
+ setState({
68
+ form: {
69
+ customer_id: '',
70
+ value: '',
71
+ subscription_id: '',
72
+ timestamp: new Date(),
73
+ },
74
+ });
75
+ onClose();
76
+ };
77
+
78
+ const handleSubmit = async () => {
79
+ if (!state.form.customer_id || !state.form.value) {
80
+ Toast.error(t('admin.meterEvent.add.validation'));
81
+ return;
82
+ }
83
+
84
+ setState({ loading: true });
85
+ try {
86
+ // 获取计量器信息以获取事件名称
87
+ const meterResponse = await api.get(`/api/meters/${meterId}`);
88
+ const meter = meterResponse.data;
89
+
90
+ await addMeterEvent({
91
+ event_name: meter.event_name,
92
+ customer_id: state.form.customer_id,
93
+ value: state.form.value,
94
+ subscription_id: state.form.subscription_id,
95
+ timestamp: dayjs(state.form.timestamp).unix(),
96
+ });
97
+
98
+ Toast.success(t('admin.meterEvent.add.success'));
99
+ handleClose();
100
+ onSuccess();
101
+ } catch (err) {
102
+ console.error('Failed to add usage:', err);
103
+ Toast.error(formatError(err));
104
+ } finally {
105
+ setState({ loading: false });
106
+ }
107
+ };
108
+
109
+ return (
110
+ <Dialog
111
+ open={open}
112
+ onClose={handleClose}
113
+ maxWidth="sm"
114
+ fullWidth
115
+ fullScreen={isMobile}
116
+ title={t('admin.meterEvent.add.title')}
117
+ actions={
118
+ <>
119
+ <Button onClick={handleClose} variant="outlined" disabled={state.loading}>
120
+ {t('common.cancel')}
121
+ </Button>
122
+ <Button onClick={handleSubmit} variant="contained" disabled={state.loading}>
123
+ {t('admin.meterEvent.add.submit')}
124
+ </Button>
125
+ </>
126
+ }>
127
+ <Box>
128
+ <Stack spacing={3}>
129
+ <Box>
130
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.meterEvent.add.customerId')}</FormLabel>
131
+ <Autocomplete
132
+ options={customers}
133
+ getOptionLabel={getCustomerDisplayName}
134
+ value={customers.find((c) => c.id === state.form.customer_id) || null}
135
+ onChange={(_, customer) =>
136
+ setState({
137
+ form: {
138
+ ...state.form,
139
+ customer_id: customer?.id || '',
140
+ },
141
+ })
142
+ }
143
+ renderOption={(props, customer) => (
144
+ <Box component="li" {...props} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
145
+ <Avatar
146
+ src={getCustomerAvatar(
147
+ customer.did,
148
+ customer?.updated_at ? new Date(customer.updated_at).toISOString() : undefined,
149
+ 24
150
+ )}
151
+ sx={{ width: 24, height: 24 }}
152
+ alt={getCustomerDisplayName(customer)}
153
+ />
154
+ {getCustomerDisplayName(customer)}
155
+ </Box>
156
+ )}
157
+ renderInput={(params) => (
158
+ <TextField {...params} placeholder={t('admin.meterEvent.add.customerIdPlaceholder')} fullWidth />
159
+ )}
160
+ />
161
+ </Box>
162
+
163
+ <Box>
164
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.meterEvent.add.value')}</FormLabel>
165
+ <TextField
166
+ type="number"
167
+ fullWidth
168
+ value={state.form.value}
169
+ onChange={(e) =>
170
+ setState({
171
+ form: {
172
+ ...state.form,
173
+ value: e.target.value,
174
+ },
175
+ })
176
+ }
177
+ placeholder={t('admin.meterEvent.add.valuePlaceholder')}
178
+ />
179
+ </Box>
180
+
181
+ <Box>
182
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.meterEvent.add.subscriptionId')}</FormLabel>
183
+ <TextField
184
+ fullWidth
185
+ value={state.form.subscription_id}
186
+ onChange={(e) =>
187
+ setState({
188
+ form: {
189
+ ...state.form,
190
+ subscription_id: e.target.value,
191
+ },
192
+ })
193
+ }
194
+ placeholder={t('admin.meterEvent.add.subscriptionIdPlaceholder')}
195
+ />
196
+ <Typography
197
+ variant="caption"
198
+ sx={{
199
+ color: 'text.secondary',
200
+ mt: 0.5,
201
+ display: 'block',
202
+ }}>
203
+ {t('admin.meterEvent.add.subscriptionIdHint')}
204
+ </Typography>
205
+ </Box>
206
+
207
+ <Box>
208
+ <FormLabel sx={{ color: 'text.primary' }}>{t('admin.meterEvent.add.timestamp')}</FormLabel>
209
+ <TextField
210
+ type="datetime-local"
211
+ fullWidth
212
+ value={dayjs(state.form.timestamp).format('YYYY-MM-DDTHH:mm')}
213
+ onChange={(e) =>
214
+ setState({
215
+ form: {
216
+ ...state.form,
217
+ timestamp: new Date(e.target.value),
218
+ },
219
+ })
220
+ }
221
+ InputLabelProps={{
222
+ shrink: true,
223
+ }}
224
+ />
225
+ <Typography
226
+ variant="caption"
227
+ sx={{
228
+ color: 'text.secondary',
229
+ mt: 0.5,
230
+ display: 'block',
231
+ }}>
232
+ {t('admin.meterEvent.add.timestampHint')}
233
+ </Typography>
234
+ </Box>
235
+ </Stack>
236
+ </Box>
237
+ </Dialog>
238
+ );
239
+ }