payment-kit 1.13.15

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 (222) hide show
  1. package/.eslintrc.js +15 -0
  2. package/README.md +3 -0
  3. package/api/dev.ts +6 -0
  4. package/api/hooks/pre-start.js +12 -0
  5. package/api/src/hooks/pre-start.ts +21 -0
  6. package/api/src/index.ts +92 -0
  7. package/api/src/jobs/event.ts +72 -0
  8. package/api/src/jobs/invoice.ts +148 -0
  9. package/api/src/jobs/payment.ts +208 -0
  10. package/api/src/jobs/subscription.ts +301 -0
  11. package/api/src/jobs/webhook.ts +113 -0
  12. package/api/src/libs/audit.ts +73 -0
  13. package/api/src/libs/auth.ts +40 -0
  14. package/api/src/libs/chain/arcblock.ts +13 -0
  15. package/api/src/libs/dayjs.ts +17 -0
  16. package/api/src/libs/env.ts +5 -0
  17. package/api/src/libs/hooks.ts +42 -0
  18. package/api/src/libs/logger.ts +27 -0
  19. package/api/src/libs/middleware.ts +12 -0
  20. package/api/src/libs/payment.ts +53 -0
  21. package/api/src/libs/queue/index.ts +263 -0
  22. package/api/src/libs/queue/store.ts +47 -0
  23. package/api/src/libs/security.ts +95 -0
  24. package/api/src/libs/session.ts +164 -0
  25. package/api/src/libs/util.ts +93 -0
  26. package/api/src/locales/en.ts +3 -0
  27. package/api/src/locales/index.ts +37 -0
  28. package/api/src/locales/zh.ts +3 -0
  29. package/api/src/routes/checkout-sessions.ts +536 -0
  30. package/api/src/routes/connect/collect.ts +109 -0
  31. package/api/src/routes/connect/pay.ts +116 -0
  32. package/api/src/routes/connect/setup.ts +121 -0
  33. package/api/src/routes/connect/shared.ts +410 -0
  34. package/api/src/routes/connect/subscribe.ts +128 -0
  35. package/api/src/routes/customers.ts +70 -0
  36. package/api/src/routes/events.ts +76 -0
  37. package/api/src/routes/index.ts +59 -0
  38. package/api/src/routes/invoices.ts +126 -0
  39. package/api/src/routes/payment-currencies.ts +38 -0
  40. package/api/src/routes/payment-intents.ts +122 -0
  41. package/api/src/routes/payment-links.ts +221 -0
  42. package/api/src/routes/payment-methods.ts +39 -0
  43. package/api/src/routes/prices.ts +134 -0
  44. package/api/src/routes/products.ts +191 -0
  45. package/api/src/routes/settings.ts +33 -0
  46. package/api/src/routes/subscription-items.ts +148 -0
  47. package/api/src/routes/subscriptions.ts +254 -0
  48. package/api/src/routes/usage-records.ts +120 -0
  49. package/api/src/routes/webhook-attempts.ts +57 -0
  50. package/api/src/routes/webhook-endpoints.ts +105 -0
  51. package/api/src/store/migrate.ts +16 -0
  52. package/api/src/store/migrations/20230905-genesis.ts +52 -0
  53. package/api/src/store/migrations/20230911-seeding.ts +145 -0
  54. package/api/src/store/models/checkout-session.ts +395 -0
  55. package/api/src/store/models/coupon.ts +137 -0
  56. package/api/src/store/models/customer.ts +199 -0
  57. package/api/src/store/models/discount.ts +116 -0
  58. package/api/src/store/models/event.ts +111 -0
  59. package/api/src/store/models/index.ts +165 -0
  60. package/api/src/store/models/invoice-item.ts +185 -0
  61. package/api/src/store/models/invoice.ts +492 -0
  62. package/api/src/store/models/job.ts +75 -0
  63. package/api/src/store/models/payment-currency.ts +139 -0
  64. package/api/src/store/models/payment-intent.ts +282 -0
  65. package/api/src/store/models/payment-link.ts +219 -0
  66. package/api/src/store/models/payment-method.ts +169 -0
  67. package/api/src/store/models/price.ts +266 -0
  68. package/api/src/store/models/product.ts +162 -0
  69. package/api/src/store/models/promotion-code.ts +112 -0
  70. package/api/src/store/models/setup-intent.ts +206 -0
  71. package/api/src/store/models/subscription-item.ts +103 -0
  72. package/api/src/store/models/subscription-schedule.ts +157 -0
  73. package/api/src/store/models/subscription.ts +307 -0
  74. package/api/src/store/models/types.ts +406 -0
  75. package/api/src/store/models/usage-record.ts +132 -0
  76. package/api/src/store/models/webhook-attempt.ts +96 -0
  77. package/api/src/store/models/webhook-endpoint.ts +96 -0
  78. package/api/src/store/sequelize.ts +15 -0
  79. package/api/third.d.ts +28 -0
  80. package/blocklet.md +3 -0
  81. package/blocklet.yml +89 -0
  82. package/index.html +14 -0
  83. package/logo.png +0 -0
  84. package/package.json +133 -0
  85. package/public/.gitkeep +0 -0
  86. package/screenshots/.gitkeep +0 -0
  87. package/screenshots/1-subscription.png +0 -0
  88. package/screenshots/2-customer-1.png +0 -0
  89. package/screenshots/3-customer-2.png +0 -0
  90. package/screenshots/4-admin-3.png +0 -0
  91. package/screenshots/5-admin-4.png +0 -0
  92. package/scripts/build-clean.js +6 -0
  93. package/scripts/bump-version.mjs +35 -0
  94. package/src/app.tsx +68 -0
  95. package/src/components/actions.tsx +85 -0
  96. package/src/components/blockchain/tx.tsx +29 -0
  97. package/src/components/checkout/amount.tsx +24 -0
  98. package/src/components/checkout/error.tsx +30 -0
  99. package/src/components/checkout/footer.tsx +12 -0
  100. package/src/components/checkout/form/address.tsx +38 -0
  101. package/src/components/checkout/form/index.tsx +295 -0
  102. package/src/components/checkout/header.tsx +23 -0
  103. package/src/components/checkout/pay.tsx +222 -0
  104. package/src/components/checkout/product-card.tsx +56 -0
  105. package/src/components/checkout/product-item.tsx +37 -0
  106. package/src/components/checkout/skeleton/overview.tsx +21 -0
  107. package/src/components/checkout/skeleton/payment.tsx +35 -0
  108. package/src/components/checkout/success.tsx +183 -0
  109. package/src/components/checkout/summary.tsx +34 -0
  110. package/src/components/collapse.tsx +50 -0
  111. package/src/components/confirm.tsx +55 -0
  112. package/src/components/copyable.tsx +38 -0
  113. package/src/components/currency.tsx +15 -0
  114. package/src/components/customer/actions.tsx +73 -0
  115. package/src/components/data.tsx +20 -0
  116. package/src/components/drawer-form.tsx +77 -0
  117. package/src/components/error-fallback.tsx +7 -0
  118. package/src/components/error.tsx +39 -0
  119. package/src/components/event/list.tsx +217 -0
  120. package/src/components/info-card.tsx +40 -0
  121. package/src/components/info-metric.tsx +35 -0
  122. package/src/components/info-row.tsx +28 -0
  123. package/src/components/input.tsx +40 -0
  124. package/src/components/invoice/action.tsx +94 -0
  125. package/src/components/invoice/list.tsx +225 -0
  126. package/src/components/invoice/table.tsx +110 -0
  127. package/src/components/layout.tsx +70 -0
  128. package/src/components/livemode.tsx +23 -0
  129. package/src/components/metadata/editor.tsx +57 -0
  130. package/src/components/metadata/form.tsx +45 -0
  131. package/src/components/payment-intent/actions.tsx +81 -0
  132. package/src/components/payment-intent/list.tsx +204 -0
  133. package/src/components/payment-link/actions.tsx +114 -0
  134. package/src/components/payment-link/after-pay.tsx +87 -0
  135. package/src/components/payment-link/before-pay.tsx +175 -0
  136. package/src/components/payment-link/item.tsx +135 -0
  137. package/src/components/payment-link/product-select.tsx +66 -0
  138. package/src/components/payment-link/rename.tsx +64 -0
  139. package/src/components/portal/invoice/list.tsx +110 -0
  140. package/src/components/portal/subscription/cancel.tsx +83 -0
  141. package/src/components/portal/subscription/list.tsx +232 -0
  142. package/src/components/price/actions.tsx +21 -0
  143. package/src/components/price/form.tsx +292 -0
  144. package/src/components/product/actions.tsx +125 -0
  145. package/src/components/product/add-price.tsx +59 -0
  146. package/src/components/product/create.tsx +97 -0
  147. package/src/components/product/edit-price.tsx +75 -0
  148. package/src/components/product/edit.tsx +67 -0
  149. package/src/components/product/features.tsx +32 -0
  150. package/src/components/product/form.tsx +76 -0
  151. package/src/components/relative-time.tsx +41 -0
  152. package/src/components/section/header.tsx +29 -0
  153. package/src/components/status.tsx +12 -0
  154. package/src/components/subscription/actions/cancel.tsx +66 -0
  155. package/src/components/subscription/actions/index.tsx +172 -0
  156. package/src/components/subscription/actions/pause.tsx +83 -0
  157. package/src/components/subscription/items/actions.tsx +31 -0
  158. package/src/components/subscription/items/index.tsx +107 -0
  159. package/src/components/subscription/list.tsx +200 -0
  160. package/src/components/switch.tsx +48 -0
  161. package/src/components/table.tsx +66 -0
  162. package/src/components/uploader.tsx +81 -0
  163. package/src/components/webhook/attempts.tsx +149 -0
  164. package/src/contexts/products.tsx +42 -0
  165. package/src/contexts/session.ts +10 -0
  166. package/src/contexts/settings.tsx +54 -0
  167. package/src/env.d.ts +17 -0
  168. package/src/global.css +97 -0
  169. package/src/hooks/mobile.ts +15 -0
  170. package/src/index.tsx +6 -0
  171. package/src/libs/api.ts +19 -0
  172. package/src/libs/dayjs.ts +17 -0
  173. package/src/libs/util.ts +474 -0
  174. package/src/locales/en.tsx +395 -0
  175. package/src/locales/index.tsx +8 -0
  176. package/src/locales/zh.tsx +389 -0
  177. package/src/pages/admin/billing/index.tsx +56 -0
  178. package/src/pages/admin/billing/invoices/detail.tsx +215 -0
  179. package/src/pages/admin/billing/invoices/index.tsx +5 -0
  180. package/src/pages/admin/billing/subscriptions/detail.tsx +237 -0
  181. package/src/pages/admin/billing/subscriptions/index.tsx +5 -0
  182. package/src/pages/admin/customers/customers/detail.tsx +209 -0
  183. package/src/pages/admin/customers/customers/index.tsx +109 -0
  184. package/src/pages/admin/customers/index.tsx +47 -0
  185. package/src/pages/admin/developers/events/detail.tsx +77 -0
  186. package/src/pages/admin/developers/events/index.tsx +5 -0
  187. package/src/pages/admin/developers/index.tsx +60 -0
  188. package/src/pages/admin/developers/logs.tsx +3 -0
  189. package/src/pages/admin/developers/overview.tsx +3 -0
  190. package/src/pages/admin/developers/webhooks/detail.tsx +109 -0
  191. package/src/pages/admin/developers/webhooks/index.tsx +102 -0
  192. package/src/pages/admin/index.tsx +120 -0
  193. package/src/pages/admin/overview.tsx +3 -0
  194. package/src/pages/admin/payments/index.tsx +65 -0
  195. package/src/pages/admin/payments/intents/detail.tsx +205 -0
  196. package/src/pages/admin/payments/intents/index.tsx +5 -0
  197. package/src/pages/admin/payments/links/create.tsx +141 -0
  198. package/src/pages/admin/payments/links/detail.tsx +318 -0
  199. package/src/pages/admin/payments/links/index.tsx +167 -0
  200. package/src/pages/admin/products/coupons/index.tsx +3 -0
  201. package/src/pages/admin/products/index.tsx +81 -0
  202. package/src/pages/admin/products/prices/actions.tsx +151 -0
  203. package/src/pages/admin/products/prices/detail.tsx +203 -0
  204. package/src/pages/admin/products/prices/list.tsx +95 -0
  205. package/src/pages/admin/products/pricing-tables.tsx +3 -0
  206. package/src/pages/admin/products/products/create.tsx +105 -0
  207. package/src/pages/admin/products/products/detail.tsx +246 -0
  208. package/src/pages/admin/products/products/index.tsx +154 -0
  209. package/src/pages/admin/settings/branding.tsx +3 -0
  210. package/src/pages/admin/settings/business.tsx +3 -0
  211. package/src/pages/admin/settings/index.tsx +47 -0
  212. package/src/pages/admin/settings/payment-methods.tsx +80 -0
  213. package/src/pages/checkout/index.tsx +38 -0
  214. package/src/pages/checkout/pay.tsx +89 -0
  215. package/src/pages/customer/index.tsx +93 -0
  216. package/src/pages/customer/invoice.tsx +147 -0
  217. package/src/pages/home.tsx +9 -0
  218. package/tsconfig.api.json +9 -0
  219. package/tsconfig.eslint.json +7 -0
  220. package/tsconfig.json +99 -0
  221. package/tsconfig.types.json +11 -0
  222. package/vite.config.ts +19 -0
@@ -0,0 +1,107 @@
1
+ /* eslint-disable react/no-unstable-nested-components */
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import type { TPaymentCurrency, TSubscriptionItemExpanded } from '@did-pay/types';
4
+ import { Stack, Typography } from '@mui/material';
5
+
6
+ import { formatPrice } from '../../../libs/util';
7
+ import Copyable from '../../copyable';
8
+ import Table from '../../table';
9
+ import LineItemActions from './actions';
10
+
11
+ type ListProps = {
12
+ data: TSubscriptionItemExpanded[];
13
+ currency: TPaymentCurrency;
14
+ };
15
+
16
+ SubscriptionItemList.defaultProps = {};
17
+
18
+ export default function SubscriptionItemList({ data, currency }: ListProps) {
19
+ const { t } = useLocaleContext();
20
+
21
+ const columns = [
22
+ {
23
+ label: t('admin.subscription.product'),
24
+ name: 'price_id',
25
+ options: {
26
+ customBodyRenderLite: (_: string, index: number) => {
27
+ const item = data[index] as TSubscriptionItemExpanded;
28
+ return (
29
+ <Stack>
30
+ <Typography color="text.primary" fontWeight={600}>
31
+ {item?.price.product.name} - {item?.price_id}
32
+ </Typography>
33
+ <Typography color="text.secondary">
34
+ {formatPrice(item.price, currency, item?.price.product.unit_label)}
35
+ </Typography>
36
+ </Stack>
37
+ );
38
+ },
39
+ },
40
+ },
41
+ {
42
+ label: t('admin.subscription.itemId'),
43
+ name: 'id',
44
+ options: {
45
+ customBodyRenderLite: (_: string, index: number) => {
46
+ const item = data[index] as TSubscriptionItemExpanded;
47
+ return <Copyable text={item?.id} />;
48
+ },
49
+ },
50
+ },
51
+ {
52
+ label: t('common.quantity'),
53
+ name: 'quantity',
54
+ options: {
55
+ customBodyRenderLite: (_: string, index: number) => {
56
+ const item = data[index];
57
+ return item?.quantity;
58
+ },
59
+ },
60
+ },
61
+ {
62
+ label: t('common.total'),
63
+ name: 'id',
64
+ align: 'right',
65
+ options: {
66
+ sort: false,
67
+ customBodyRenderLite: (_: string, index: number) => {
68
+ const item = data[index] as TSubscriptionItemExpanded;
69
+ return (
70
+ <Typography>{formatPrice(item.price, currency, item?.price.product.unit_label, item?.quantity)}</Typography>
71
+ );
72
+ },
73
+ },
74
+ },
75
+ {
76
+ label: '',
77
+ name: '',
78
+ align: 'center',
79
+ options: {
80
+ sort: false,
81
+ customBodyRenderLite: (_: string, index: number) => {
82
+ const item = data[index] as TSubscriptionItemExpanded;
83
+ return <LineItemActions data={item} />;
84
+ },
85
+ },
86
+ },
87
+ ];
88
+
89
+ return (
90
+ <Table
91
+ data={data}
92
+ columns={columns}
93
+ loading={false}
94
+ footer={false}
95
+ toolbar={false}
96
+ components={{
97
+ TableToolbar: () => null,
98
+ TableFooter: () => null,
99
+ }}
100
+ options={{
101
+ count: data.length,
102
+ page: 0,
103
+ rowsPerPage: 100,
104
+ }}
105
+ />
106
+ );
107
+ }
@@ -0,0 +1,200 @@
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 type { TSubscriptionExpanded } from '@did-pay/types';
5
+ import { Alert, CircularProgress, ToggleButton, ToggleButtonGroup } from '@mui/material';
6
+ import { useRequest } from 'ahooks';
7
+ import { useEffect, useState } from 'react';
8
+ import { useNavigate } from 'react-router-dom';
9
+
10
+ import api from '../../libs/api';
11
+ import { formatSubscriptionProduct, formatTime, getSubscriptionStatusColor } from '../../libs/util';
12
+ import Status from '../status';
13
+ import Table from '../table';
14
+ import SubscriptionActions from './actions';
15
+
16
+ const fetchData = (params: Record<string, any> = {}): Promise<{ list: TSubscriptionExpanded[]; count: number }> => {
17
+ const search = new URLSearchParams();
18
+ Object.keys(params).forEach((key) => {
19
+ search.set(key, String(params[key]));
20
+ });
21
+ return api.get(`/api/subscriptions?${search.toString()}`).then((res) => res.data);
22
+ };
23
+
24
+ type SearchProps = {
25
+ status: string;
26
+ pageSize: number;
27
+ page: number;
28
+ customer_id?: string;
29
+ };
30
+
31
+ type ListProps = {
32
+ features?: {
33
+ customer?: boolean;
34
+ toolbar?: boolean;
35
+ footer?: boolean;
36
+ };
37
+ customer_id?: string;
38
+ status?: string;
39
+ };
40
+
41
+ const getListKey = (props: ListProps) => {
42
+ if (props.customer_id) {
43
+ return `customer-subscriptions-${props.customer_id}`;
44
+ }
45
+
46
+ return 'subscriptions';
47
+ };
48
+
49
+ SubscriptionList.defaultProps = {
50
+ features: {
51
+ customer: true,
52
+ filter: true,
53
+ },
54
+ customer_id: '',
55
+ status: 'active,paused',
56
+ };
57
+
58
+ export default function SubscriptionList({ customer_id, features, status }: ListProps) {
59
+ const listKey = getListKey({ customer_id });
60
+ const persisted = getDurableData(listKey);
61
+
62
+ const { t } = useLocaleContext();
63
+ const navigate = useNavigate();
64
+ const [search, setSearch] = useState<SearchProps>({
65
+ status: status as string,
66
+ customer_id,
67
+ pageSize: persisted.rowsPerPage || 20,
68
+ page: persisted.page ? persisted.page + 1 : 1,
69
+ });
70
+
71
+ const { loading, error, data, refresh } = useRequest(() => fetchData(search));
72
+ useEffect(() => {
73
+ refresh();
74
+ }, [search, refresh]);
75
+
76
+ if (error) {
77
+ return <Alert severity="error">{error.message}</Alert>;
78
+ }
79
+
80
+ if (loading || !data) {
81
+ return <CircularProgress />;
82
+ }
83
+
84
+ const columns = [
85
+ {
86
+ label: t('admin.subscription.product'),
87
+ name: 'id',
88
+ options: {
89
+ customBodyRenderLite: (_: string, index: number) => {
90
+ const item = data.list[index] as TSubscriptionExpanded;
91
+ return formatSubscriptionProduct(item.items);
92
+ },
93
+ },
94
+ },
95
+ {
96
+ label: t('common.status'),
97
+ name: 'status',
98
+ options: {
99
+ customBodyRenderLite: (_: string, index: number) => {
100
+ const item = data.list[index] as TSubscriptionExpanded;
101
+ return <Status label={item.status} color={getSubscriptionStatusColor(item.status)} />;
102
+ },
103
+ },
104
+ },
105
+ {
106
+ label: t('admin.subscription.collectionMethod'),
107
+ name: 'collection_method',
108
+ options: {
109
+ customBodyRenderLite: (_: string, index: number) => {
110
+ const item = data.list[index] as TSubscriptionExpanded;
111
+ return (
112
+ <Status
113
+ label={item.collection_method === 'charge_automatically' ? 'Auto' : 'Manual'}
114
+ color={item.collection_method === 'charge_automatically' ? 'default' : 'warning'}
115
+ />
116
+ );
117
+ },
118
+ },
119
+ },
120
+ {
121
+ label: t('common.createdAt'),
122
+ name: 'created_at',
123
+ options: {
124
+ customBodyRender: (e: string) => {
125
+ return formatTime(e);
126
+ },
127
+ },
128
+ },
129
+ {
130
+ label: t('common.actions'),
131
+ name: 'id',
132
+ width: 100,
133
+ align: 'center',
134
+ options: {
135
+ sort: false,
136
+ customBodyRenderLite: (_: string, index: number) => {
137
+ const item = data.list[index] as TSubscriptionExpanded;
138
+ return <SubscriptionActions data={item} onChange={refresh} />;
139
+ },
140
+ },
141
+ },
142
+ ];
143
+
144
+ if (features?.customer) {
145
+ columns.splice(3, 0, {
146
+ label: t('common.customer'),
147
+ name: 'customer_id',
148
+ options: {
149
+ customBodyRenderLite: (_: string, index: number) => {
150
+ const item = data.list[index] as TSubscriptionExpanded;
151
+ return item.customer.email;
152
+ },
153
+ },
154
+ });
155
+ }
156
+
157
+ const onTableChange = ({ page, rowsPerPage }: any) => {
158
+ if (search.pageSize !== rowsPerPage) {
159
+ setSearch((x) => ({ ...x, pageSize: rowsPerPage, page: 1 }));
160
+ } else if (search.page !== page + 1) {
161
+ setSearch((x) => ({ ...x, page: page + 1 }));
162
+ }
163
+ };
164
+
165
+ return (
166
+ <Table
167
+ durable={listKey}
168
+ durableKeys={['page', 'rowsPerPage']}
169
+ data={data.list}
170
+ columns={columns}
171
+ loading={loading}
172
+ onChange={onTableChange}
173
+ options={{
174
+ count: data.count,
175
+ page: search.page - 1,
176
+ rowsPerPage: search.pageSize,
177
+ onRowClick: (_: any, { dataIndex }: any) => {
178
+ const item = data.list[dataIndex] as TSubscriptionExpanded;
179
+ navigate(`/admin/billing/${item.id}`);
180
+ },
181
+ }}
182
+ toolbar={features?.toolbar}
183
+ footer={features?.footer}
184
+ title={
185
+ <div className="table-toolbar-left">
186
+ <ToggleButtonGroup
187
+ value={search.status}
188
+ onChange={(_, value) => setSearch((x) => ({ ...x, status: value }))}
189
+ exclusive>
190
+ <ToggleButton value="active">Active</ToggleButton>
191
+ <ToggleButton value="trialing">Scheduled</ToggleButton>
192
+ <ToggleButton value="canceled">Canceled</ToggleButton>
193
+ <ToggleButton value="incomplete,incomplete_expired">Incomplete</ToggleButton>
194
+ <ToggleButton value="past_due,unpaid">Due</ToggleButton>
195
+ </ToggleButtonGroup>
196
+ </div>
197
+ }
198
+ />
199
+ );
200
+ }
@@ -0,0 +1,48 @@
1
+ import { Switch } from '@mui/material';
2
+ import { styled } from '@mui/system';
3
+ import type { LiteralUnion } from 'type-fest';
4
+
5
+ type SwitchProps = {
6
+ variant?: LiteralUnion<'success' | 'error' | 'warning' | 'info' | 'primary' | 'secondary', string>;
7
+ };
8
+
9
+ export default styled(Switch)<SwitchProps>(({ variant = 'success', theme }) => ({
10
+ width: 28,
11
+ height: 16,
12
+ padding: 0,
13
+ display: 'inline-flex',
14
+ '&:active': {
15
+ '& .MuiSwitch-thumb': {
16
+ width: 15,
17
+ },
18
+ '& .MuiSwitch-switchBase.Mui-checked': {
19
+ transform: 'translateX(9px)',
20
+ },
21
+ },
22
+ '& .MuiSwitch-switchBase': {
23
+ padding: 2,
24
+ '&.Mui-checked': {
25
+ transform: 'translateX(12px)',
26
+ color: '#fff',
27
+ '& + .MuiSwitch-track': {
28
+ opacity: 1,
29
+ backgroundColor: theme.palette[variant].light,
30
+ },
31
+ },
32
+ },
33
+ '& .MuiSwitch-thumb': {
34
+ boxShadow: '0 2px 4px 0 rgb(0 35 11 / 20%)',
35
+ width: 12,
36
+ height: 12,
37
+ borderRadius: 6,
38
+ transition: (theme.transitions as any).create(['width'], {
39
+ duration: 200,
40
+ }),
41
+ },
42
+ '& .MuiSwitch-track': {
43
+ borderRadius: 16 / 2,
44
+ opacity: 1,
45
+ backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,.35)' : 'rgba(0,0,0,.25)',
46
+ boxSizing: 'border-box',
47
+ },
48
+ }));
@@ -0,0 +1,66 @@
1
+ import Datatable from '@arcblock/ux/lib/Datatable';
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import { styled } from '@mui/system';
4
+
5
+ import useMobile from '../hooks/mobile';
6
+
7
+ function EmptyStub() {
8
+ return null;
9
+ }
10
+
11
+ export default function Table({ options, toolbar = true, footer = true, ...rest }: any) {
12
+ const { isMobile } = useMobile();
13
+ const { locale } = useLocaleContext();
14
+ const defaultOptions = {
15
+ print: false,
16
+ download: false,
17
+ filter: false,
18
+ selectableRows: 'none',
19
+ rowsPerPage: 20,
20
+ rowsPerPageOptions: [20, 50, 100],
21
+ searchDebounceTime: 300,
22
+ tableBodyHeight: '100%',
23
+ };
24
+
25
+ const components: any = {};
26
+ if (!toolbar) {
27
+ components.TableToolbar = EmptyStub;
28
+ }
29
+ if (!footer) {
30
+ components.TableFooter = EmptyStub;
31
+ }
32
+
33
+ return (
34
+ <Wrapped
35
+ locale={locale}
36
+ options={{ ...defaultOptions, ...options }}
37
+ {...rest}
38
+ components={components}
39
+ isMobile={isMobile}
40
+ />
41
+ );
42
+ }
43
+
44
+ const Wrapped = styled(Datatable)`
45
+ tr.MuiTableRow-root:not(.MuiTableRow-footer) {
46
+ border-bottom: 1px solid rgba(224, 224, 224, 0.4);
47
+ }
48
+
49
+ th.MuiTableCell-head {
50
+ text-transform: inherit;
51
+ }
52
+
53
+ tr.MuiTableRow-root:not(.MuiTableRow-footer):hover {
54
+ background: #f5f5f5;
55
+ }
56
+
57
+ ${(props) => {
58
+ if (props.isMobile) {
59
+ return '';
60
+ }
61
+
62
+ return `.MuiTableRow-root > td:first-of-type{
63
+ padding-left: 6px;
64
+ }`;
65
+ }}
66
+ `;
@@ -0,0 +1,81 @@
1
+ import { UploadFileOutlined } from '@mui/icons-material';
2
+ import { Box, Button, Typography } from '@mui/material';
3
+ import { styled } from '@mui/system';
4
+ import { lazy, useCallback, useEffect, useRef } from 'react';
5
+
6
+ const UploaderComponent = lazy(() => import('@blocklet/uploader/react').then((res) => ({ default: res.Uploader })));
7
+
8
+ type Props = {
9
+ onUploaded: (result: any) => void;
10
+ preview?: string;
11
+ maxFileSize?: number;
12
+ allowedFileTypes?: string[];
13
+ };
14
+
15
+ export default function Uploader({ onUploaded, preview, maxFileSize, allowedFileTypes }: Props) {
16
+ const uploaderRef = useRef<any>(null);
17
+ const handleOpen = useCallback(() => {
18
+ if (!uploaderRef.current) return;
19
+ uploaderRef.current.open();
20
+ }, []);
21
+
22
+ useEffect(() => {
23
+ if (uploaderRef.current) {
24
+ const uploader = uploaderRef.current.getUploader();
25
+ uploader.onUploadSuccess((result: any) => onUploaded({ url: result.response.data.fileUrl }));
26
+ }
27
+ }, [onUploaded]);
28
+
29
+ return (
30
+ <>
31
+ <Div
32
+ display="flex"
33
+ alignItems={preview ? 'flex-end' : 'center'}
34
+ justifyContent="center"
35
+ onClick={handleOpen}
36
+ style={{
37
+ backgroundImage: preview ? `url(${preview})` : 'none',
38
+ backgroundRepeat: 'no-repeat',
39
+ backgroundSize: 'contain',
40
+ }}>
41
+ <Button fullWidth variant={preview ? 'contained' : 'text'} color="inherit" size="small">
42
+ <UploadFileOutlined sx={{ mr: 1 }} fontSize="small" />
43
+ <Typography>{preview ? 'Change' : 'Upload'}</Typography>
44
+ </Button>
45
+ </Div>
46
+ <UploaderComponent
47
+ ref={uploaderRef}
48
+ popup
49
+ onUploadFinish={(result: any) => onUploaded({ url: result.data.url })}
50
+ coreProps={{
51
+ restrictions: {
52
+ allowedFileTypes,
53
+ maxFileSize,
54
+ },
55
+ }}
56
+ apiPathProps={{
57
+ uploader: '/api/uploads',
58
+ companion: '/api/companion',
59
+ }}
60
+ />
61
+ </>
62
+ );
63
+ }
64
+
65
+ Uploader.defaultProps = {
66
+ preview: '',
67
+ maxFileSize: 1024 * 1024 * 2,
68
+ allowedFileTypes: ['image/png', 'image/jpeg', 'image/webp'],
69
+ };
70
+
71
+ const Div = styled(Box)`
72
+ border: 1px solid #ccc;
73
+ border-radius: 4px;
74
+ cursor: pointer;
75
+ width: 120px;
76
+ height: 120px;
77
+
78
+ &:hover {
79
+ background-color: #fafafa;
80
+ }
81
+ `;
@@ -0,0 +1,149 @@
1
+ /* eslint-disable react/no-unstable-nested-components */
2
+ import CodeBlock from '@arcblock/ux/lib/CodeBlock';
3
+ import type { Paginated, TEvent, TWebhookAttemptExpanded } from '@did-pay/types';
4
+ import { CheckCircleOutlined, ErrorOutlined } from '@mui/icons-material';
5
+ import {
6
+ Box,
7
+ Button,
8
+ CircularProgress,
9
+ Grid,
10
+ List,
11
+ ListItem,
12
+ ListItemIcon,
13
+ ListItemText,
14
+ ListSubheader,
15
+ Stack,
16
+ Typography,
17
+ } from '@mui/material';
18
+ import { useInfiniteScroll } from 'ahooks';
19
+ import React, { useEffect, useState } from 'react';
20
+
21
+ import api from '../../libs/api';
22
+ import { formatTime } from '../../libs/util';
23
+
24
+ const fetchData = (params: Record<string, any> = {}): Promise<Paginated<TWebhookAttemptExpanded>> => {
25
+ const search = new URLSearchParams();
26
+ Object.keys(params).forEach((key) => {
27
+ search.set(key, String(params[key]));
28
+ });
29
+ return api.get(`/api/webhook-attempts?${search.toString()}`).then((res) => res.data);
30
+ };
31
+
32
+ const groupAttemptsByDate = (attempts: TWebhookAttemptExpanded[]) => {
33
+ const groupedAttempts: { [key: string]: TWebhookAttemptExpanded[] } = {};
34
+ attempts.forEach((attempt) => {
35
+ const date = new Date(attempt.created_at).toLocaleDateString();
36
+ if (!groupedAttempts[date]) {
37
+ groupedAttempts[date] = [];
38
+ }
39
+ groupedAttempts[date]?.push(attempt);
40
+ });
41
+ return groupedAttempts;
42
+ };
43
+
44
+ type Props = {
45
+ event_id?: string;
46
+ webhook_endpoint_id?: string;
47
+ event?: TEvent;
48
+ };
49
+
50
+ WebhookAttempts.defaultProps = {
51
+ event_id: '',
52
+ webhook_endpoint_id: '',
53
+ event: null,
54
+ };
55
+
56
+ export default function WebhookAttempts({ event_id, webhook_endpoint_id, event }: Props) {
57
+ const { data, loadMore, loadingMore, loading } = useInfiniteScroll<Paginated<TWebhookAttemptExpanded>>(
58
+ (d) => {
59
+ const size = 15;
60
+ const page = d ? Math.ceil(d.list.length / size) + 1 : 1;
61
+ return fetchData({ page, size, event_id, webhook_endpoint_id });
62
+ },
63
+ {
64
+ reloadDeps: [event_id, webhook_endpoint_id],
65
+ }
66
+ );
67
+
68
+ const attempts = data?.list || [];
69
+
70
+ // @ts-ignore
71
+ const [selected, setSelected] = useState<TWebhookAttemptExpanded>(null);
72
+ const groupedAttempts = groupAttemptsByDate(attempts);
73
+
74
+ useEffect(() => {
75
+ if (!selected && data?.list.length) {
76
+ setSelected(data.list[0] as TWebhookAttemptExpanded);
77
+ }
78
+ }, [data?.list.length, selected]); // eslint-disable-line
79
+
80
+ const handleAttemptClick = (attempt: TWebhookAttemptExpanded) => {
81
+ setSelected(attempt);
82
+ };
83
+
84
+ if (loading) {
85
+ return <CircularProgress />;
86
+ }
87
+
88
+ const hasMore = data && data.list.length < data.count;
89
+
90
+ return (
91
+ <Grid container spacing={0}>
92
+ <Grid item xs={4} className="attempt-list">
93
+ <List sx={{ maxHeight: '64vh', overflowY: 'scroll' }}>
94
+ {Object.entries(groupedAttempts).map(([date, items]) => (
95
+ <React.Fragment key={date}>
96
+ <ListSubheader>{date}</ListSubheader>
97
+ {items.map((attempt) => (
98
+ <ListItem
99
+ button
100
+ key={attempt.id}
101
+ onClick={() => handleAttemptClick(attempt)}
102
+ selected={selected === attempt}
103
+ secondaryAction={
104
+ <Typography color="text.secondary">{formatTime(attempt.created_at, 'HH:mm:ss A')}</Typography>
105
+ }>
106
+ <ListItemIcon>
107
+ {attempt.response_status > 200 ? (
108
+ <ErrorOutlined color="error" />
109
+ ) : (
110
+ <CheckCircleOutlined color="success" />
111
+ )}
112
+ </ListItemIcon>
113
+ <ListItemText primary={<Typography color="text.primary">{attempt.event.type}</Typography>} />
114
+ </ListItem>
115
+ ))}
116
+ </React.Fragment>
117
+ ))}
118
+ </List>
119
+ {hasMore && (
120
+ <Button variant="text" type="button" color="inherit" onClick={loadMore} disabled={loadingMore}>
121
+ {loadingMore ? 'Loading more...' : 'Load more'}
122
+ </Button>
123
+ )}
124
+ {!hasMore && <Typography color="text.secondary">No more data</Typography>}
125
+ </Grid>
126
+ <Grid item xs={8}>
127
+ {selected && (
128
+ <Stack direction="column" spacing={2} sx={{ pt: 3, pl: 3, borderLeft: '1px solid #eee' }}>
129
+ <Typography variant="h6">{event_id ? selected.endpoint.url : selected.event.type}</Typography>
130
+ <Box>
131
+ <Typography variant="h6">Response ({selected.response_status})</Typography>
132
+ <CodeBlock language="json">{JSON.stringify(selected.response_body, null, 2)}</CodeBlock>
133
+ </Box>
134
+ <Box>
135
+ <Typography variant="h6">Request</Typography>
136
+ <CodeBlock language="json">{JSON.stringify(selected.event, null, 2)}</CodeBlock>
137
+ </Box>
138
+ </Stack>
139
+ )}
140
+ {data?.list.length === 0 && event && (
141
+ <Box>
142
+ <Typography variant="h6">Event Data</Typography>
143
+ <CodeBlock language="json">{JSON.stringify(event.data, null, 2)}</CodeBlock>
144
+ </Box>
145
+ )}
146
+ </Grid>
147
+ </Grid>
148
+ );
149
+ }
@@ -0,0 +1,42 @@
1
+ import type { TProductExpanded } from '@did-pay/types';
2
+ import { Alert, CircularProgress } from '@mui/material';
3
+ import { useRequest } from 'ahooks';
4
+ import { createContext, useContext } from 'react';
5
+
6
+ import api from '../libs/api';
7
+
8
+ type ProductsContextType = {
9
+ products: TProductExpanded[];
10
+ refresh: () => void;
11
+ };
12
+
13
+ // @ts-ignore
14
+ const ProductsContext = createContext<ProductsContextType>({ api });
15
+ const { Provider, Consumer } = ProductsContext;
16
+
17
+ const getProducts = async (): Promise<TProductExpanded[]> => {
18
+ const { data } = await api.get('/api/products?active=true');
19
+ return data.list || [];
20
+ };
21
+
22
+ // eslint-disable-next-line react/prop-types
23
+ function ProductsProvider({ children }: { children: any }): JSX.Element {
24
+ const { data, error, run, loading } = useRequest(getProducts);
25
+
26
+ if (error) {
27
+ return <Alert severity="error">{error.message}</Alert>;
28
+ }
29
+
30
+ if (loading || !data) {
31
+ return <CircularProgress />;
32
+ }
33
+
34
+ return <Provider value={{ products: data, refresh: run }}>{children}</Provider>;
35
+ }
36
+
37
+ function useProductsContext() {
38
+ const context = useContext(ProductsContext);
39
+ return context;
40
+ }
41
+
42
+ export { ProductsContext, ProductsProvider, Consumer as ProductsConsumer, useProductsContext };