payment-kit 1.18.56 → 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 (214) hide show
  1. package/.eslintrc.js +6 -0
  2. package/api/src/crons/index.ts +8 -0
  3. package/api/src/index.ts +4 -0
  4. package/api/src/libs/credit-grant.ts +146 -0
  5. package/api/src/libs/env.ts +1 -0
  6. package/api/src/libs/invoice.ts +4 -3
  7. package/api/src/libs/notification/template/base.ts +388 -2
  8. package/api/src/libs/notification/template/customer-credit-grant-granted.ts +149 -0
  9. package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +151 -0
  10. package/api/src/libs/notification/template/customer-credit-insufficient.ts +254 -0
  11. package/api/src/libs/notification/template/subscription-canceled.ts +193 -202
  12. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +215 -237
  13. package/api/src/libs/notification/template/subscription-renewed.ts +130 -200
  14. package/api/src/libs/notification/template/subscription-succeeded.ts +100 -202
  15. package/api/src/libs/notification/template/subscription-trial-start.ts +142 -188
  16. package/api/src/libs/notification/template/subscription-trial-will-end.ts +146 -174
  17. package/api/src/libs/notification/template/subscription-upgraded.ts +96 -192
  18. package/api/src/libs/notification/template/subscription-will-canceled.ts +94 -135
  19. package/api/src/libs/notification/template/subscription-will-renew.ts +220 -245
  20. package/api/src/libs/payment.ts +69 -0
  21. package/api/src/libs/queue/index.ts +3 -2
  22. package/api/src/libs/session.ts +8 -0
  23. package/api/src/libs/subscription.ts +74 -3
  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 +715 -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/index.ts +8 -0
  37. package/api/src/routes/meter-events.ts +347 -0
  38. package/api/src/routes/meters.ts +219 -0
  39. package/api/src/routes/payment-currencies.ts +14 -2
  40. package/api/src/routes/payment-links.ts +1 -1
  41. package/api/src/routes/payment-methods.ts +14 -2
  42. package/api/src/routes/prices.ts +43 -0
  43. package/api/src/routes/pricing-table.ts +13 -7
  44. package/api/src/routes/products.ts +63 -4
  45. package/api/src/routes/settings.ts +1 -1
  46. package/api/src/routes/subscriptions.ts +4 -0
  47. package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
  48. package/api/src/store/models/credit-grant.ts +486 -0
  49. package/api/src/store/models/credit-transaction.ts +268 -0
  50. package/api/src/store/models/customer.ts +8 -0
  51. package/api/src/store/models/index.ts +52 -1
  52. package/api/src/store/models/meter-event.ts +423 -0
  53. package/api/src/store/models/meter.ts +176 -0
  54. package/api/src/store/models/payment-currency.ts +66 -14
  55. package/api/src/store/models/price.ts +6 -0
  56. package/api/src/store/models/product.ts +2 -2
  57. package/api/src/store/models/subscription.ts +24 -0
  58. package/api/src/store/models/types.ts +28 -2
  59. package/api/tests/libs/subscription.spec.ts +53 -0
  60. package/blocklet.yml +9 -1
  61. package/package.json +57 -58
  62. package/scripts/sdk.js +233 -1
  63. package/src/app.tsx +10 -0
  64. package/src/components/actions.tsx +22 -9
  65. package/src/components/balance-list.tsx +40 -12
  66. package/src/components/collapse.tsx +33 -15
  67. package/src/components/copyable.tsx +8 -7
  68. package/src/components/currency.tsx +15 -7
  69. package/src/components/customer/actions.tsx +1 -5
  70. package/src/components/customer/credit-grant-item-list.tsx +99 -0
  71. package/src/components/customer/credit-overview.tsx +233 -0
  72. package/src/components/customer/form.tsx +7 -2
  73. package/src/components/customer/link.tsx +4 -12
  74. package/src/components/customer/notification-preference.tsx +18 -9
  75. package/src/components/customer/overdraft-protection.tsx +112 -41
  76. package/src/components/drawer-form.tsx +42 -18
  77. package/src/components/error.tsx +1 -5
  78. package/src/components/event/list.tsx +9 -10
  79. package/src/components/filter-toolbar.tsx +20 -19
  80. package/src/components/info-card.tsx +32 -18
  81. package/src/components/info-metric.tsx +16 -6
  82. package/src/components/info-row-group.tsx +1 -7
  83. package/src/components/info-row.tsx +30 -24
  84. package/src/components/invoice/action.tsx +1 -7
  85. package/src/components/invoice/list.tsx +34 -26
  86. package/src/components/invoice/recharge.tsx +5 -7
  87. package/src/components/invoice/table.tsx +17 -12
  88. package/src/components/layout/user.tsx +1 -1
  89. package/src/components/metadata/form.tsx +290 -94
  90. package/src/components/metadata/list.tsx +11 -3
  91. package/src/components/meter/actions.tsx +101 -0
  92. package/src/components/meter/add-usage-dialog.tsx +239 -0
  93. package/src/components/meter/events-list.tsx +657 -0
  94. package/src/components/meter/form.tsx +245 -0
  95. package/src/components/meter/products.tsx +264 -0
  96. package/src/components/meter/usage-guide.tsx +174 -0
  97. package/src/components/passport/actions.tsx +9 -4
  98. package/src/components/payment-currency/add.tsx +16 -3
  99. package/src/components/payment-currency/form.tsx +14 -6
  100. package/src/components/payment-intent/actions.tsx +24 -16
  101. package/src/components/payment-intent/list.tsx +30 -9
  102. package/src/components/payment-link/actions.tsx +1 -5
  103. package/src/components/payment-link/after-pay.tsx +4 -2
  104. package/src/components/payment-link/before-pay.tsx +14 -4
  105. package/src/components/payment-link/item.tsx +27 -6
  106. package/src/components/payment-link/preview.tsx +9 -9
  107. package/src/components/payment-link/product-select.tsx +69 -15
  108. package/src/components/payment-method/arcblock.tsx +8 -1
  109. package/src/components/payment-method/base.tsx +8 -1
  110. package/src/components/payment-method/bitcoin.tsx +8 -1
  111. package/src/components/payment-method/ethereum.tsx +8 -1
  112. package/src/components/payment-method/evm-rpc-input.tsx +11 -7
  113. package/src/components/payment-method/form.tsx +2 -7
  114. package/src/components/payment-method/stripe.tsx +2 -0
  115. package/src/components/payouts/actions.tsx +1 -5
  116. package/src/components/payouts/list.tsx +30 -10
  117. package/src/components/payouts/portal/list.tsx +11 -9
  118. package/src/components/price/currency-select.tsx +63 -32
  119. package/src/components/price/form.tsx +895 -370
  120. package/src/components/price/upsell-select.tsx +10 -2
  121. package/src/components/price/upsell.tsx +7 -2
  122. package/src/components/pricing-table/actions.tsx +1 -5
  123. package/src/components/pricing-table/customer-settings.tsx +5 -1
  124. package/src/components/pricing-table/payment-settings.tsx +14 -4
  125. package/src/components/pricing-table/preview.tsx +9 -9
  126. package/src/components/pricing-table/price-item.tsx +6 -1
  127. package/src/components/pricing-table/product-item.tsx +6 -1
  128. package/src/components/pricing-table/product-settings.tsx +17 -4
  129. package/src/components/product/actions.tsx +1 -5
  130. package/src/components/product/add-price.tsx +9 -7
  131. package/src/components/product/create.tsx +8 -9
  132. package/src/components/product/cross-sell-select.tsx +5 -1
  133. package/src/components/product/cross-sell.tsx +7 -2
  134. package/src/components/product/edit-price.tsx +21 -12
  135. package/src/components/product/features.tsx +26 -6
  136. package/src/components/product/form.tsx +115 -72
  137. package/src/components/progress-bar.tsx +1 -1
  138. package/src/components/refund/actions.tsx +1 -7
  139. package/src/components/refund/list.tsx +31 -18
  140. package/src/components/section/header.tsx +12 -14
  141. package/src/components/subscription/actions/cancel.tsx +22 -5
  142. package/src/components/subscription/actions/index.tsx +9 -10
  143. package/src/components/subscription/actions/pause.tsx +32 -6
  144. package/src/components/subscription/actions/slash-stake.tsx +5 -3
  145. package/src/components/subscription/description.tsx +12 -8
  146. package/src/components/subscription/items/index.tsx +31 -16
  147. package/src/components/subscription/items/usage-records.tsx +19 -5
  148. package/src/components/subscription/list.tsx +5 -7
  149. package/src/components/subscription/metrics.tsx +62 -15
  150. package/src/components/subscription/portal/actions.tsx +78 -71
  151. package/src/components/subscription/portal/cancel.tsx +10 -3
  152. package/src/components/subscription/portal/list.tsx +48 -26
  153. package/src/components/uploader.tsx +5 -13
  154. package/src/components/webhook/attempts.tsx +51 -16
  155. package/src/components/webhook/request-info.tsx +8 -6
  156. package/src/contexts/products.tsx +27 -10
  157. package/src/hooks/subscription.ts +34 -0
  158. package/src/libs/meter-utils.ts +196 -0
  159. package/src/libs/util.ts +4 -0
  160. package/src/locales/en.tsx +385 -4
  161. package/src/locales/zh.tsx +364 -0
  162. package/src/pages/admin/billing/index.tsx +61 -33
  163. package/src/pages/admin/billing/invoices/detail.tsx +49 -13
  164. package/src/pages/admin/billing/meters/create.tsx +60 -0
  165. package/src/pages/admin/billing/meters/detail.tsx +435 -0
  166. package/src/pages/admin/billing/meters/index.tsx +210 -0
  167. package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
  168. package/src/pages/admin/billing/subscriptions/detail.tsx +90 -25
  169. package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
  170. package/src/pages/admin/customers/customers/detail.tsx +67 -14
  171. package/src/pages/admin/customers/customers/index.tsx +6 -1
  172. package/src/pages/admin/customers/index.tsx +5 -0
  173. package/src/pages/admin/developers/events/detail.tsx +37 -11
  174. package/src/pages/admin/developers/index.tsx +1 -1
  175. package/src/pages/admin/developers/webhooks/detail.tsx +41 -11
  176. package/src/pages/admin/index.tsx +15 -2
  177. package/src/pages/admin/overview.tsx +107 -19
  178. package/src/pages/admin/payments/intents/detail.tsx +58 -14
  179. package/src/pages/admin/payments/payouts/detail.tsx +63 -15
  180. package/src/pages/admin/payments/refunds/detail.tsx +58 -14
  181. package/src/pages/admin/products/index.tsx +11 -4
  182. package/src/pages/admin/products/links/create.tsx +22 -4
  183. package/src/pages/admin/products/links/detail.tsx +43 -14
  184. package/src/pages/admin/products/passports/index.tsx +23 -4
  185. package/src/pages/admin/products/prices/actions.tsx +16 -9
  186. package/src/pages/admin/products/prices/detail.tsx +73 -14
  187. package/src/pages/admin/products/prices/list.tsx +15 -3
  188. package/src/pages/admin/products/pricing-tables/create.tsx +45 -12
  189. package/src/pages/admin/products/pricing-tables/detail.tsx +45 -14
  190. package/src/pages/admin/products/products/create.tsx +233 -54
  191. package/src/pages/admin/products/products/detail.tsx +74 -18
  192. package/src/pages/admin/settings/index.tsx +8 -1
  193. package/src/pages/admin/settings/payment-methods/index.tsx +87 -19
  194. package/src/pages/admin/settings/vault-config/edit-form.tsx +42 -28
  195. package/src/pages/admin/settings/vault-config/index.tsx +57 -10
  196. package/src/pages/customer/credit-grant/detail.tsx +308 -0
  197. package/src/pages/customer/index.tsx +76 -17
  198. package/src/pages/customer/invoice/detail.tsx +63 -14
  199. package/src/pages/customer/invoice/past-due.tsx +11 -3
  200. package/src/pages/customer/payout/detail.tsx +56 -13
  201. package/src/pages/customer/recharge/account.tsx +78 -18
  202. package/src/pages/customer/recharge/subscription.tsx +86 -25
  203. package/src/pages/customer/refund/list.tsx +60 -24
  204. package/src/pages/customer/subscription/change-payment.tsx +17 -6
  205. package/src/pages/customer/subscription/change-plan.tsx +34 -7
  206. package/src/pages/customer/subscription/detail.tsx +134 -34
  207. package/src/pages/customer/subscription/embed.tsx +25 -5
  208. package/src/pages/home.tsx +26 -4
  209. package/src/pages/integrations/donations/edit-form.tsx +25 -9
  210. package/src/pages/integrations/donations/index.tsx +26 -9
  211. package/src/pages/integrations/donations/preview.tsx +59 -15
  212. package/src/pages/integrations/index.tsx +10 -1
  213. package/src/pages/integrations/overview.tsx +78 -17
  214. package/vite.config.ts +60 -30
@@ -0,0 +1,210 @@
1
+ /* eslint-disable react/no-unstable-nested-components */
2
+ import { getDurableData } from '@arcblock/ux/lib/Datatable';
3
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
+ import { Status, api, formatTime, Table } from '@blocklet/payment-react';
5
+ import { CircularProgress } from '@mui/material';
6
+ import { useEffect, useState } from 'react';
7
+ import { Link } from 'react-router-dom';
8
+ import useBus from 'use-bus';
9
+
10
+ import { useLocalStorageState } from 'ahooks';
11
+ import type { TMeter } from '@blocklet/payment-types';
12
+ import MeterActions from '../../../../components/meter/actions';
13
+
14
+ const fetchData = (params: Record<string, any> = {}): Promise<{ list: TMeter[]; count: number }> => {
15
+ const search = new URLSearchParams();
16
+ Object.keys(params).forEach((key) => {
17
+ let v = params[key];
18
+ if (key === 'q') {
19
+ v = Object.entries(v)
20
+ .map((x) => x.join(':'))
21
+ .join(' ');
22
+ }
23
+ search.set(key, String(v));
24
+ });
25
+ return api.get(`/api/meters?${search.toString()}`).then((res) => res.data);
26
+ };
27
+
28
+ type SearchProps = {
29
+ status: string;
30
+ pageSize: number;
31
+ page: number;
32
+ q?: any;
33
+ o?: string;
34
+ };
35
+
36
+ export default function MetersList() {
37
+ const listKey = 'meters';
38
+ const persisted = getDurableData(listKey);
39
+
40
+ const { t } = useLocaleContext();
41
+ const [search, setSearch] = useLocalStorageState<SearchProps>(listKey, {
42
+ defaultValue: {
43
+ status: '',
44
+ pageSize: persisted.rowsPerPage || 20,
45
+ page: persisted.page ? persisted.page + 1 : 1,
46
+ },
47
+ });
48
+
49
+ const [data, setData] = useState({}) as any;
50
+
51
+ const refresh = () =>
52
+ fetchData(search).then((res: any) => {
53
+ setData(res);
54
+ });
55
+
56
+ useBus('meter.created', () => refresh(), []);
57
+
58
+ useEffect(() => {
59
+ refresh();
60
+ }, [search]);
61
+
62
+ if (!data.list) {
63
+ return <CircularProgress />;
64
+ }
65
+
66
+ const columns = [
67
+ {
68
+ label: t('admin.meter.name.label'),
69
+ name: 'name',
70
+ options: {
71
+ filter: true,
72
+ customBodyRenderLite: (_: string, index: number) => {
73
+ const item = data.list[index] as TMeter;
74
+ return <Link to={`/admin/billing/${item.id}`}>{item.name}</Link>;
75
+ },
76
+ },
77
+ },
78
+ {
79
+ label: t('admin.meter.eventName.label'),
80
+ name: 'event_name',
81
+ options: {
82
+ filter: true,
83
+ customBodyRenderLite: (_: string, index: number) => {
84
+ const item = data.list[index] as TMeter;
85
+ return <Link to={`/admin/billing/${item.id}`}>{item.event_name}</Link>;
86
+ },
87
+ },
88
+ },
89
+ {
90
+ label: t('admin.meter.unit.label'),
91
+ name: 'unit',
92
+ options: {
93
+ filter: true,
94
+ customBodyRenderLite: (_: string, index: number) => {
95
+ const item = data.list[index] as TMeter;
96
+ return <Link to={`/admin/billing/${item.id}`}>{item.unit}</Link>;
97
+ },
98
+ },
99
+ },
100
+ {
101
+ label: t('admin.meter.aggregationMethod.label'),
102
+ name: 'aggregation_method',
103
+ options: {
104
+ filter: true,
105
+ customBodyRenderLite: (_: string, index: number) => {
106
+ const item = data.list[index] as TMeter;
107
+ return (
108
+ <Link to={`/admin/billing/${item.id}`}>
109
+ {t(`admin.meter.aggregationMethod.${item.aggregation_method}`)}
110
+ </Link>
111
+ );
112
+ },
113
+ },
114
+ },
115
+ {
116
+ label: t('common.status'),
117
+ name: 'status',
118
+ options: {
119
+ filter: true,
120
+ customBodyRenderLite: (_: string, index: number) => {
121
+ const item = data.list[index] as TMeter;
122
+ return (
123
+ <Link to={`/admin/billing/${item.id}`}>
124
+ <Status
125
+ label={item?.status === 'active' ? 'Active' : 'Inactive'}
126
+ color={item?.status === 'active' ? 'success' : 'default'}
127
+ />
128
+ </Link>
129
+ );
130
+ },
131
+ },
132
+ },
133
+ {
134
+ label: t('common.createdAt'),
135
+ name: 'created_at',
136
+ options: {
137
+ sort: true,
138
+ customBodyRenderLite: (_: string, index: number) => {
139
+ const item = data.list[index] as TMeter;
140
+ return <Link to={`/admin/billing/${item.id}`}>{formatTime(item.created_at)}</Link>;
141
+ },
142
+ },
143
+ },
144
+ {
145
+ label: t('common.actions'),
146
+ name: 'id',
147
+ width: 100,
148
+ align: 'center',
149
+ options: {
150
+ sort: false,
151
+ customBodyRenderLite: (_: string, index: number) => {
152
+ const meter = data.list[index] as TMeter;
153
+ return <MeterActions data={meter} onChange={refresh} />;
154
+ },
155
+ },
156
+ },
157
+ ];
158
+
159
+ const onTableChange = ({ page, rowsPerPage }: any) => {
160
+ if (search!.pageSize !== rowsPerPage) {
161
+ setSearch((x: any) => ({ ...x, pageSize: rowsPerPage, page: 1 }));
162
+ } else if (search!.page !== page + 1) {
163
+ setSearch((x: any) => ({ ...x, page: page + 1 }));
164
+ }
165
+ };
166
+
167
+ return (
168
+ <Table
169
+ hasRowLink
170
+ durable={`__${listKey}__`}
171
+ durableKeys={['page', 'rowsPerPage', 'searchText']}
172
+ data={data.list}
173
+ columns={columns}
174
+ options={{
175
+ count: data.count,
176
+ page: search!.page - 1,
177
+ rowsPerPage: search!.pageSize,
178
+ onColumnSortChange(_: any, order: any) {
179
+ setSearch({
180
+ ...search!,
181
+ q: search!.q || {},
182
+ o: order,
183
+ });
184
+ },
185
+ onSearchChange: (text: string) => {
186
+ if (text) {
187
+ setSearch({
188
+ ...search!,
189
+ q: {
190
+ 'like-name': text,
191
+ 'like-event_name': text,
192
+ },
193
+ pageSize: 100,
194
+ page: 1,
195
+ });
196
+ } else {
197
+ setSearch({
198
+ ...search!,
199
+ pageSize: 100,
200
+ page: 1,
201
+ q: {},
202
+ });
203
+ }
204
+ },
205
+ }}
206
+ loading={!data.list}
207
+ onChange={onTableChange}
208
+ />
209
+ );
210
+ }
@@ -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)``;