payment-kit 1.21.12 → 1.21.14
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.
- package/api/src/crons/payment-stat.ts +31 -23
- package/api/src/libs/invoice.ts +29 -4
- package/api/src/libs/product.ts +28 -4
- package/api/src/routes/checkout-sessions.ts +46 -1
- package/api/src/routes/connect/re-stake.ts +2 -0
- package/api/src/routes/index.ts +2 -0
- package/api/src/routes/invoices.ts +63 -2
- package/api/src/routes/payment-stats.ts +244 -22
- package/api/src/routes/products.ts +3 -0
- package/api/src/routes/subscriptions.ts +2 -1
- package/api/src/routes/tax-rates.ts +220 -0
- package/api/src/store/migrations/20251001-add-tax-code-to-products.ts +20 -0
- package/api/src/store/migrations/20251001-create-tax-rates.ts +17 -0
- package/api/src/store/migrations/20251007-relate-tax-rate-to-invoice.ts +24 -0
- package/api/src/store/migrations/20251009-add-tax-behavior.ts +21 -0
- package/api/src/store/models/index.ts +3 -0
- package/api/src/store/models/invoice-item.ts +10 -0
- package/api/src/store/models/price.ts +7 -0
- package/api/src/store/models/product.ts +7 -0
- package/api/src/store/models/tax-rate.ts +352 -0
- package/api/tests/models/tax-rate.spec.ts +777 -0
- package/blocklet.yml +2 -2
- package/package.json +6 -6
- package/public/currencies/dollar.png +0 -0
- package/src/components/collapse.tsx +3 -2
- package/src/components/drawer-form.tsx +2 -1
- package/src/components/invoice/list.tsx +38 -1
- package/src/components/invoice/table.tsx +48 -2
- package/src/components/metadata/form.tsx +2 -2
- package/src/components/payment-intent/list.tsx +19 -1
- package/src/components/payouts/list.tsx +19 -1
- package/src/components/price/currency-select.tsx +105 -48
- package/src/components/price/form.tsx +3 -1
- package/src/components/product/form.tsx +79 -5
- package/src/components/refund/list.tsx +20 -1
- package/src/components/subscription/items/actions.tsx +25 -15
- package/src/components/subscription/list.tsx +16 -1
- package/src/components/tax/actions.tsx +140 -0
- package/src/components/tax/filter-toolbar.tsx +230 -0
- package/src/components/tax/tax-code-select.tsx +633 -0
- package/src/components/tax/tax-rate-form.tsx +177 -0
- package/src/components/tax/tax-utils.ts +38 -0
- package/src/components/tax/taxCodes.json +10882 -0
- package/src/components/uploader.tsx +3 -0
- package/src/locales/en.tsx +152 -0
- package/src/locales/zh.tsx +149 -0
- package/src/pages/admin/billing/invoices/detail.tsx +1 -1
- package/src/pages/admin/index.tsx +2 -0
- package/src/pages/admin/overview.tsx +1114 -322
- package/src/pages/admin/products/vendors/index.tsx +4 -2
- package/src/pages/admin/tax/create.tsx +104 -0
- package/src/pages/admin/tax/detail.tsx +476 -0
- package/src/pages/admin/tax/edit.tsx +126 -0
- package/src/pages/admin/tax/index.tsx +86 -0
- package/src/pages/admin/tax/list.tsx +334 -0
- package/src/pages/customer/subscription/change-payment.tsx +1 -1
- package/src/pages/home.tsx +6 -3
package/blocklet.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
name: z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk
|
|
2
2
|
title: Payment Kit
|
|
3
|
-
description:
|
|
3
|
+
description: A decentralized Stripe-like payment solution for Blocklets.
|
|
4
4
|
keywords:
|
|
5
5
|
- payment
|
|
6
6
|
- stripe
|
|
@@ -14,7 +14,7 @@ repository:
|
|
|
14
14
|
type: git
|
|
15
15
|
url: git+https://github.com/blocklet/payment-kit.git
|
|
16
16
|
specVersion: 1.2.8
|
|
17
|
-
version: 1.21.
|
|
17
|
+
version: 1.21.14
|
|
18
18
|
logo: logo.png
|
|
19
19
|
files:
|
|
20
20
|
- dist
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.14",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"@blocklet/error": "^0.2.5",
|
|
57
57
|
"@blocklet/js-sdk": "^1.16.53-beta-20251011-054719-4ed2f6b7",
|
|
58
58
|
"@blocklet/logger": "^1.16.53-beta-20251011-054719-4ed2f6b7",
|
|
59
|
-
"@blocklet/payment-broker-client": "1.21.
|
|
60
|
-
"@blocklet/payment-react": "1.21.
|
|
61
|
-
"@blocklet/payment-vendor": "1.21.
|
|
59
|
+
"@blocklet/payment-broker-client": "1.21.14",
|
|
60
|
+
"@blocklet/payment-react": "1.21.14",
|
|
61
|
+
"@blocklet/payment-vendor": "1.21.14",
|
|
62
62
|
"@blocklet/sdk": "^1.16.53-beta-20251011-054719-4ed2f6b7",
|
|
63
63
|
"@blocklet/ui-react": "^3.1.46",
|
|
64
64
|
"@blocklet/uploader": "^0.2.15",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"devDependencies": {
|
|
129
129
|
"@abtnode/types": "^1.16.53-beta-20251011-054719-4ed2f6b7",
|
|
130
130
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
131
|
-
"@blocklet/payment-types": "1.21.
|
|
131
|
+
"@blocklet/payment-types": "1.21.14",
|
|
132
132
|
"@types/cookie-parser": "^1.4.9",
|
|
133
133
|
"@types/cors": "^2.8.19",
|
|
134
134
|
"@types/debug": "^4.1.12",
|
|
@@ -175,5 +175,5 @@
|
|
|
175
175
|
"parser": "typescript"
|
|
176
176
|
}
|
|
177
177
|
},
|
|
178
|
-
"gitHead": "
|
|
178
|
+
"gitHead": "31f93a8310fe5184be8dd4ff23b362906c8a66cf"
|
|
179
179
|
}
|
|
Binary file
|
|
@@ -13,6 +13,7 @@ type Props = {
|
|
|
13
13
|
onChange?: (value: string, expanded: boolean) => void;
|
|
14
14
|
lazy?: boolean;
|
|
15
15
|
card?: boolean;
|
|
16
|
+
timeout?: number;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
export default function IconCollapse(rawProps: Props) {
|
|
@@ -75,8 +76,8 @@ export default function IconCollapse(rawProps: Props) {
|
|
|
75
76
|
</Stack>
|
|
76
77
|
</Stack>
|
|
77
78
|
|
|
78
|
-
<Collapse in={expanded} sx={{ width: '100%' }}>
|
|
79
|
-
{expanded || props.lazy ? props.children : null}
|
|
79
|
+
<Collapse in={expanded} timeout={props.timeout || 'auto'} sx={{ width: '100%' }}>
|
|
80
|
+
{expanded || !props.lazy ? props.children : null}
|
|
80
81
|
</Collapse>
|
|
81
82
|
</>
|
|
82
83
|
);
|
|
@@ -58,7 +58,8 @@ export default function DrawerForm(rawProps: Props) {
|
|
|
58
58
|
onClose={handleClose}
|
|
59
59
|
sx={props.style || {}}
|
|
60
60
|
width={props.width}
|
|
61
|
-
disableEscapeKeyDown
|
|
61
|
+
disableEscapeKeyDown
|
|
62
|
+
disableScrollLock>
|
|
62
63
|
<Stack
|
|
63
64
|
direction="row"
|
|
64
65
|
className="drawer-form-header-wrapper"
|
|
@@ -15,7 +15,7 @@ import type { TInvoiceExpanded } from '@blocklet/payment-types';
|
|
|
15
15
|
import { Avatar, CircularProgress, Typography } from '@mui/material';
|
|
16
16
|
import { useLocalStorageState } from 'ahooks';
|
|
17
17
|
import { useEffect, useState } from 'react';
|
|
18
|
-
import { Link } from 'react-router-dom';
|
|
18
|
+
import { Link, useSearchParams } from 'react-router-dom';
|
|
19
19
|
import CustomerLink from '../customer/link';
|
|
20
20
|
import FilterToolbar from '../filter-toolbar';
|
|
21
21
|
import InvoiceActions from './action';
|
|
@@ -24,6 +24,9 @@ const fetchData = (params: Record<string, any> = {}): Promise<{ list: TInvoiceEx
|
|
|
24
24
|
const search = new URLSearchParams();
|
|
25
25
|
Object.keys(params).forEach((key) => {
|
|
26
26
|
let v = params[key];
|
|
27
|
+
if (v === undefined || v === null || v === '') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
27
30
|
if (key === 'q') {
|
|
28
31
|
v = Object.entries(v)
|
|
29
32
|
.map((x) => x.join(':'))
|
|
@@ -40,9 +43,11 @@ type SearchProps = {
|
|
|
40
43
|
pageSize: number;
|
|
41
44
|
page: number;
|
|
42
45
|
customer_id?: string;
|
|
46
|
+
currency_id?: string;
|
|
43
47
|
subscription_id?: string;
|
|
44
48
|
q?: any;
|
|
45
49
|
o?: string;
|
|
50
|
+
tax_rate_id?: string;
|
|
46
51
|
};
|
|
47
52
|
|
|
48
53
|
type ListProps = {
|
|
@@ -58,6 +63,7 @@ type ListProps = {
|
|
|
58
63
|
ignore_zero?: boolean;
|
|
59
64
|
include_staking?: boolean;
|
|
60
65
|
include_return_staking?: boolean;
|
|
66
|
+
tax_rate_id?: string;
|
|
61
67
|
|
|
62
68
|
mode?: 'admin' | 'customer';
|
|
63
69
|
};
|
|
@@ -69,6 +75,9 @@ const getListKey = (props: ListProps) => {
|
|
|
69
75
|
if (props.subscription_id) {
|
|
70
76
|
return `subscription-invoices-${props.subscription_id}`;
|
|
71
77
|
}
|
|
78
|
+
if (props.tax_rate_id) {
|
|
79
|
+
return `tax-rate-invoices-${props.tax_rate_id}`;
|
|
80
|
+
}
|
|
72
81
|
|
|
73
82
|
return 'invoices';
|
|
74
83
|
};
|
|
@@ -103,6 +112,7 @@ function InvoiceLink({ invoice, children }: { invoice: TInvoiceExpanded; childre
|
|
|
103
112
|
export default function InvoiceList({
|
|
104
113
|
customer_id = '',
|
|
105
114
|
subscription_id = '',
|
|
115
|
+
tax_rate_id = '',
|
|
106
116
|
features = {
|
|
107
117
|
customer: true,
|
|
108
118
|
filter: true,
|
|
@@ -113,10 +123,16 @@ export default function InvoiceList({
|
|
|
113
123
|
ignore_zero = false,
|
|
114
124
|
mode = 'admin',
|
|
115
125
|
}: ListProps) {
|
|
126
|
+
const [searchParams] = useSearchParams();
|
|
116
127
|
const listKey = getListKey({ customer_id, subscription_id });
|
|
117
128
|
|
|
118
129
|
const { t, locale } = useLocaleContext();
|
|
119
130
|
const defaultPageSize = useDefaultPageSize(20);
|
|
131
|
+
|
|
132
|
+
const urlStatus = searchParams.get('status');
|
|
133
|
+
const urlCurrencyId = searchParams.get('currency_id');
|
|
134
|
+
const urlCustomerId = searchParams.get('customer_id');
|
|
135
|
+
|
|
120
136
|
const [search, setSearch] = useLocalStorageState<
|
|
121
137
|
SearchProps & { ignore_zero?: boolean; include_staking?: boolean; include_return_staking?: boolean }
|
|
122
138
|
>(listKey, {
|
|
@@ -124,6 +140,7 @@ export default function InvoiceList({
|
|
|
124
140
|
status: status as string,
|
|
125
141
|
customer_id,
|
|
126
142
|
subscription_id,
|
|
143
|
+
tax_rate_id,
|
|
127
144
|
pageSize: defaultPageSize,
|
|
128
145
|
page: 1,
|
|
129
146
|
ignore_zero: !!ignore_zero,
|
|
@@ -132,6 +149,17 @@ export default function InvoiceList({
|
|
|
132
149
|
},
|
|
133
150
|
});
|
|
134
151
|
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (urlStatus || urlCurrencyId || urlCustomerId) {
|
|
154
|
+
setSearch((prev) => ({
|
|
155
|
+
...prev!,
|
|
156
|
+
...(urlStatus && { status: urlStatus }),
|
|
157
|
+
...(urlCurrencyId && { currency_id: urlCurrencyId }),
|
|
158
|
+
...(urlCustomerId && { customer_id: urlCustomerId }),
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
}, [urlStatus, urlCurrencyId, urlCustomerId, setSearch]);
|
|
162
|
+
|
|
135
163
|
const [data, setData] = useState({}) as any;
|
|
136
164
|
|
|
137
165
|
const refresh = () =>
|
|
@@ -140,6 +168,7 @@ export default function InvoiceList({
|
|
|
140
168
|
include_staking: !!include_staking,
|
|
141
169
|
ignore_zero: !!ignore_zero,
|
|
142
170
|
include_return_staking: !!include_return_staking,
|
|
171
|
+
tax_rate_id,
|
|
143
172
|
}).then((res: any) => {
|
|
144
173
|
setData(res);
|
|
145
174
|
});
|
|
@@ -148,6 +177,14 @@ export default function InvoiceList({
|
|
|
148
177
|
refresh();
|
|
149
178
|
}, [search]);
|
|
150
179
|
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (tax_rate_id && search?.tax_rate_id !== tax_rate_id) {
|
|
182
|
+
// @ts-ignore
|
|
183
|
+
setSearch((prev) => ({ ...(prev || {}), tax_rate_id, page: 1 }));
|
|
184
|
+
}
|
|
185
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
186
|
+
}, [tax_rate_id]);
|
|
187
|
+
|
|
151
188
|
if (!data.list) {
|
|
152
189
|
return <CircularProgress />;
|
|
153
190
|
}
|
|
@@ -9,6 +9,7 @@ import { useSetState } from 'ahooks';
|
|
|
9
9
|
|
|
10
10
|
import { styled } from '@mui/system';
|
|
11
11
|
import { isEmpty } from 'lodash';
|
|
12
|
+
import { useNavigate } from 'react-router-dom';
|
|
12
13
|
import LineItemActions from '../subscription/items/actions';
|
|
13
14
|
import { UsageRecordDialog } from '../subscription/items/usage-records';
|
|
14
15
|
import { getInvoiceUsageReportStartEnd } from '../../libs/util';
|
|
@@ -17,6 +18,7 @@ type Props = {
|
|
|
17
18
|
invoice: TInvoiceExpanded;
|
|
18
19
|
simple?: boolean;
|
|
19
20
|
emptyNodeText?: string;
|
|
21
|
+
mode?: 'admin' | 'portal';
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
type InvoiceDetailItem = {
|
|
@@ -182,8 +184,10 @@ export function getInvoiceRows(invoice: TInvoiceExpanded, t: (key: string) => st
|
|
|
182
184
|
};
|
|
183
185
|
}
|
|
184
186
|
|
|
185
|
-
export default function InvoiceTable({ invoice, simple = false, emptyNodeText = '' }: Props) {
|
|
187
|
+
export default function InvoiceTable({ invoice, simple = false, emptyNodeText = '', mode = 'portal' }: Props) {
|
|
186
188
|
const { t, locale } = useLocaleContext();
|
|
189
|
+
const isAdmin = mode === 'admin';
|
|
190
|
+
const navigate = useNavigate();
|
|
187
191
|
const { detail, summary } = getInvoiceRows(invoice, t);
|
|
188
192
|
const [state, setState] = useSetState({
|
|
189
193
|
subscriptionId: '',
|
|
@@ -293,6 +297,7 @@ export default function InvoiceTable({ invoice, simple = false, emptyNodeText =
|
|
|
293
297
|
width: 200,
|
|
294
298
|
align: 'right',
|
|
295
299
|
},
|
|
300
|
+
|
|
296
301
|
{
|
|
297
302
|
label: t('common.discount'),
|
|
298
303
|
name: 'discount',
|
|
@@ -321,6 +326,7 @@ export default function InvoiceTable({ invoice, simple = false, emptyNodeText =
|
|
|
321
326
|
},
|
|
322
327
|
},
|
|
323
328
|
},
|
|
329
|
+
|
|
324
330
|
{
|
|
325
331
|
label: t('common.amount'),
|
|
326
332
|
name: 'amount',
|
|
@@ -333,6 +339,46 @@ export default function InvoiceTable({ invoice, simple = false, emptyNodeText =
|
|
|
333
339
|
},
|
|
334
340
|
},
|
|
335
341
|
},
|
|
342
|
+
...(isAdmin
|
|
343
|
+
? [
|
|
344
|
+
{
|
|
345
|
+
label: t('admin.taxRate.label'),
|
|
346
|
+
name: 'tax_rate',
|
|
347
|
+
width: 100,
|
|
348
|
+
align: 'right',
|
|
349
|
+
options: {
|
|
350
|
+
customBodyRenderLite: (_: string, index: number) => {
|
|
351
|
+
const item = detail[index] as InvoiceDetailItem;
|
|
352
|
+
const taxRate = (item.raw as any).tax_rate;
|
|
353
|
+
if (!taxRate) {
|
|
354
|
+
return (
|
|
355
|
+
<Typography component="span" sx={{ color: 'text.secondary' }}>
|
|
356
|
+
—
|
|
357
|
+
</Typography>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
return (
|
|
361
|
+
<Tooltip
|
|
362
|
+
title={`${taxRate.display_name || taxRate.id} (${taxRate.percentage}%)`}
|
|
363
|
+
arrow
|
|
364
|
+
placement="top">
|
|
365
|
+
<Typography
|
|
366
|
+
component="span"
|
|
367
|
+
sx={{
|
|
368
|
+
color: 'text.link',
|
|
369
|
+
fontWeight: 500,
|
|
370
|
+
cursor: 'default',
|
|
371
|
+
}}
|
|
372
|
+
onClick={() => navigate(`/admin/tax/${taxRate.id}`)}>
|
|
373
|
+
{taxRate.percentage}%
|
|
374
|
+
</Typography>
|
|
375
|
+
</Tooltip>
|
|
376
|
+
);
|
|
377
|
+
},
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
]
|
|
381
|
+
: []),
|
|
336
382
|
...(simple
|
|
337
383
|
? []
|
|
338
384
|
: [
|
|
@@ -343,7 +389,7 @@ export default function InvoiceTable({ invoice, simple = false, emptyNodeText =
|
|
|
343
389
|
options: {
|
|
344
390
|
customBodyRenderLite: (_: string, index: number) => {
|
|
345
391
|
const item = detail[index] as InvoiceDetailItem;
|
|
346
|
-
return <LineItemActions data={item as any} />;
|
|
392
|
+
return <LineItemActions data={item.raw as any} mode={mode} />;
|
|
347
393
|
},
|
|
348
394
|
},
|
|
349
395
|
},
|
|
@@ -219,7 +219,7 @@ export default function MetadataForm({
|
|
|
219
219
|
message: t('common.maxLength', { len: 40 }),
|
|
220
220
|
},
|
|
221
221
|
}}
|
|
222
|
-
label="Key
|
|
222
|
+
label="Key"
|
|
223
223
|
placeholder="Key"
|
|
224
224
|
// @ts-ignore
|
|
225
225
|
ref={errors?.metadata?.[index]?.key ? errorRef : null}
|
|
@@ -229,7 +229,7 @@ export default function MetadataForm({
|
|
|
229
229
|
size="small"
|
|
230
230
|
errorPosition="right"
|
|
231
231
|
name={`metadata.${index}.value`}
|
|
232
|
-
label="Value
|
|
232
|
+
label="Value"
|
|
233
233
|
placeholder="Value"
|
|
234
234
|
rules={{
|
|
235
235
|
validate: (value: any) => {
|
|
@@ -13,7 +13,7 @@ import type { TPaymentIntentExpanded } from '@blocklet/payment-types';
|
|
|
13
13
|
import { Avatar, CircularProgress, Typography } from '@mui/material';
|
|
14
14
|
import { useLocalStorageState } from 'ahooks';
|
|
15
15
|
import { useEffect, useState } from 'react';
|
|
16
|
-
import { Link } from 'react-router-dom';
|
|
16
|
+
import { Link, useSearchParams } from 'react-router-dom';
|
|
17
17
|
|
|
18
18
|
import { debounce } from '../../libs/util';
|
|
19
19
|
import CustomerLink from '../customer/link';
|
|
@@ -39,6 +39,7 @@ type SearchProps = {
|
|
|
39
39
|
pageSize: number;
|
|
40
40
|
page: number;
|
|
41
41
|
customer_id?: string;
|
|
42
|
+
currency_id?: string;
|
|
42
43
|
invoice_id?: string;
|
|
43
44
|
q?: any;
|
|
44
45
|
o?: any;
|
|
@@ -75,9 +76,15 @@ export default function PaymentList({
|
|
|
75
76
|
},
|
|
76
77
|
}: ListProps) {
|
|
77
78
|
const { t } = useLocaleContext();
|
|
79
|
+
const [searchParams] = useSearchParams();
|
|
78
80
|
|
|
79
81
|
const listKey = getListKey({ customer_id, invoice_id });
|
|
80
82
|
const defaultPageSize = useDefaultPageSize(20);
|
|
83
|
+
|
|
84
|
+
const urlStatus = searchParams.get('status');
|
|
85
|
+
const urlCurrencyId = searchParams.get('currency_id');
|
|
86
|
+
const urlCustomerId = searchParams.get('customer_id');
|
|
87
|
+
|
|
81
88
|
const [search, setSearch] = useLocalStorageState<SearchProps>(listKey, {
|
|
82
89
|
defaultValue: {
|
|
83
90
|
status: '',
|
|
@@ -88,6 +95,17 @@ export default function PaymentList({
|
|
|
88
95
|
},
|
|
89
96
|
});
|
|
90
97
|
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (urlStatus || urlCurrencyId || urlCustomerId) {
|
|
100
|
+
setSearch((prev) => ({
|
|
101
|
+
...prev!,
|
|
102
|
+
...(urlStatus && { status: urlStatus }),
|
|
103
|
+
...(urlCurrencyId && { currency_id: urlCurrencyId }),
|
|
104
|
+
...(urlCustomerId && { customer_id: urlCustomerId }),
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
}, [urlStatus, urlCurrencyId, urlCustomerId, setSearch]);
|
|
108
|
+
|
|
91
109
|
const [data, setData] = useState({}) as any;
|
|
92
110
|
|
|
93
111
|
const fetchListData = () => {
|
|
@@ -13,7 +13,7 @@ import type { TPayoutExpanded } from '@blocklet/payment-types';
|
|
|
13
13
|
import { Avatar, CircularProgress, Typography } from '@mui/material';
|
|
14
14
|
import { useLocalStorageState } from 'ahooks';
|
|
15
15
|
import { useEffect, useState } from 'react';
|
|
16
|
-
import { Link } from 'react-router-dom';
|
|
16
|
+
import { Link, useSearchParams } from 'react-router-dom';
|
|
17
17
|
|
|
18
18
|
import DID from '@arcblock/ux/lib/DID';
|
|
19
19
|
import ShortenLabel from '@arcblock/ux/lib/UserCard/Content/shorten-label';
|
|
@@ -42,6 +42,7 @@ type SearchProps = {
|
|
|
42
42
|
pageSize: number;
|
|
43
43
|
page: number;
|
|
44
44
|
customer_id?: string;
|
|
45
|
+
currency_id?: string;
|
|
45
46
|
payment_intent_id?: string;
|
|
46
47
|
q?: any;
|
|
47
48
|
o?: any;
|
|
@@ -80,9 +81,15 @@ export default function PayoutList({
|
|
|
80
81
|
},
|
|
81
82
|
}: ListProps) {
|
|
82
83
|
const { t } = useLocaleContext();
|
|
84
|
+
const [searchParams] = useSearchParams();
|
|
83
85
|
|
|
84
86
|
const listKey = getListKey({ customer_id, payment_intent_id });
|
|
85
87
|
const defaultPageSize = useDefaultPageSize(20);
|
|
88
|
+
|
|
89
|
+
const urlStatus = searchParams.get('status');
|
|
90
|
+
const urlCurrencyId = searchParams.get('currency_id');
|
|
91
|
+
const urlCustomerId = searchParams.get('customer_id');
|
|
92
|
+
|
|
86
93
|
const [search, setSearch] = useLocalStorageState<SearchProps>(listKey, {
|
|
87
94
|
defaultValue: {
|
|
88
95
|
status: status as string,
|
|
@@ -93,6 +100,17 @@ export default function PayoutList({
|
|
|
93
100
|
},
|
|
94
101
|
});
|
|
95
102
|
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (urlStatus || urlCurrencyId || urlCustomerId) {
|
|
105
|
+
setSearch((prev) => ({
|
|
106
|
+
...prev!,
|
|
107
|
+
...(urlStatus && { status: urlStatus }),
|
|
108
|
+
...(urlCurrencyId && { currency_id: urlCurrencyId }),
|
|
109
|
+
...(urlCustomerId && { customer_id: urlCustomerId }),
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
}, [urlStatus, urlCurrencyId, urlCustomerId, setSearch]);
|
|
113
|
+
|
|
96
114
|
const [data, setData] = useState({}) as any;
|
|
97
115
|
|
|
98
116
|
useEffect(() => {
|
|
@@ -2,11 +2,9 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
|
2
2
|
import { usePaymentContext } from '@blocklet/payment-react';
|
|
3
3
|
import { AddOutlined, ArrowDropDown } from '@mui/icons-material';
|
|
4
4
|
import { ListSubheader, MenuItem, Select, Stack, SxProps, Typography } from '@mui/material';
|
|
5
|
-
import { useState, useEffect } from 'react';
|
|
5
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
6
6
|
import type { LiteralUnion } from 'type-fest';
|
|
7
7
|
|
|
8
|
-
import { flatten } from 'lodash';
|
|
9
|
-
import { getSupportedPaymentMethods } from '../../libs/util';
|
|
10
8
|
import Currency from '../currency';
|
|
11
9
|
|
|
12
10
|
type Props = {
|
|
@@ -19,6 +17,12 @@ type Props = {
|
|
|
19
17
|
selectSX?: SxProps;
|
|
20
18
|
currencyFilter?: (currency: any) => boolean;
|
|
21
19
|
hideMethod?: boolean;
|
|
20
|
+
includeAllOption?: boolean;
|
|
21
|
+
allOption?: {
|
|
22
|
+
value: string;
|
|
23
|
+
label: string;
|
|
24
|
+
};
|
|
25
|
+
selectedSx?: SxProps;
|
|
22
26
|
};
|
|
23
27
|
|
|
24
28
|
export default function CurrencySelect({
|
|
@@ -31,16 +35,29 @@ export default function CurrencySelect({
|
|
|
31
35
|
selectSX = {},
|
|
32
36
|
currencyFilter = () => true,
|
|
33
37
|
hideMethod = false,
|
|
38
|
+
includeAllOption = false,
|
|
39
|
+
allOption = { value: 'all', label: 'All' },
|
|
40
|
+
selectedSx = {},
|
|
34
41
|
}: Props) {
|
|
35
42
|
const { t } = useLocaleContext();
|
|
36
43
|
const { settings } = usePaymentContext();
|
|
37
44
|
const [mode, setMode] = useState(initialMode);
|
|
38
45
|
|
|
39
|
-
const
|
|
46
|
+
const currencyGroups = useMemo(() => {
|
|
47
|
+
return (settings.paymentMethods || []).map((method) => ({
|
|
48
|
+
method,
|
|
49
|
+
currencies: (method.payment_currencies || []).map((currency: any) => ({
|
|
50
|
+
...currency,
|
|
51
|
+
method,
|
|
52
|
+
})),
|
|
53
|
+
}));
|
|
54
|
+
}, [settings.paymentMethods]);
|
|
55
|
+
|
|
56
|
+
const allCurrencies = useMemo(() => currencyGroups.flatMap((group) => group.currencies), [currencyGroups]);
|
|
40
57
|
|
|
41
58
|
useEffect(() => {
|
|
42
|
-
if (value && initialMode === 'selected' &&
|
|
43
|
-
const currency =
|
|
59
|
+
if (value && initialMode === 'selected' && allCurrencies.length > 0) {
|
|
60
|
+
const currency = allCurrencies.find((c) => c.id === value);
|
|
44
61
|
if (currency && !hasSelected(currency)) {
|
|
45
62
|
const timer = setTimeout(() => {
|
|
46
63
|
onSelect(value);
|
|
@@ -49,7 +66,7 @@ export default function CurrencySelect({
|
|
|
49
66
|
}
|
|
50
67
|
}
|
|
51
68
|
return undefined;
|
|
52
|
-
}, [value, initialMode]);
|
|
69
|
+
}, [value, initialMode, allCurrencies, hasSelected]);
|
|
53
70
|
|
|
54
71
|
const handleSelect = (e: any) => {
|
|
55
72
|
if (disabled) {
|
|
@@ -59,15 +76,36 @@ export default function CurrencySelect({
|
|
|
59
76
|
onSelect(e.target.value);
|
|
60
77
|
};
|
|
61
78
|
|
|
62
|
-
const selectedCurrency =
|
|
79
|
+
const selectedCurrency = allCurrencies.find((x) => x.id === value);
|
|
63
80
|
|
|
64
|
-
const selectedPaymentMethod =
|
|
81
|
+
const selectedPaymentMethod = currencyGroups.find((group) =>
|
|
82
|
+
group.currencies.some((currency) => currency.id === value)
|
|
83
|
+
)?.method;
|
|
65
84
|
|
|
66
|
-
const
|
|
85
|
+
const availableGroups = useMemo(() => {
|
|
86
|
+
return currencyGroups
|
|
87
|
+
.map((group) => ({
|
|
88
|
+
method: group.method,
|
|
89
|
+
currencies: group.currencies.filter((currency) => currencyFilter(currency) && !hasSelected(currency)),
|
|
90
|
+
}))
|
|
91
|
+
.filter((group) => group.currencies.length > 0);
|
|
92
|
+
}, [currencyGroups, hasSelected]);
|
|
67
93
|
|
|
68
|
-
const canSelect =
|
|
94
|
+
const canSelect = availableGroups.length > 0 && !disabled;
|
|
69
95
|
|
|
70
96
|
if (mode === 'selected') {
|
|
97
|
+
const isAllSelected = includeAllOption && value === allOption.value;
|
|
98
|
+
let displayLabel = '';
|
|
99
|
+
if (isAllSelected) {
|
|
100
|
+
displayLabel = allOption.label;
|
|
101
|
+
} else if (selectedCurrency?.symbol) {
|
|
102
|
+
displayLabel = hideMethod
|
|
103
|
+
? selectedCurrency.symbol
|
|
104
|
+
: `${selectedCurrency.symbol} (${selectedPaymentMethod?.name || ''})`;
|
|
105
|
+
} else if (!hideMethod && selectedPaymentMethod?.name) {
|
|
106
|
+
displayLabel = `(${selectedPaymentMethod.name})`;
|
|
107
|
+
}
|
|
108
|
+
|
|
71
109
|
return (
|
|
72
110
|
<Typography
|
|
73
111
|
onClick={() => {
|
|
@@ -81,8 +119,10 @@ export default function CurrencySelect({
|
|
|
81
119
|
minWidth: '120px',
|
|
82
120
|
justifyContent: 'flex-end',
|
|
83
121
|
textAlign: 'right',
|
|
122
|
+
width: '100%',
|
|
123
|
+
...selectedSx,
|
|
84
124
|
}}>
|
|
85
|
-
{
|
|
125
|
+
{displayLabel}
|
|
86
126
|
{canSelect && <ArrowDropDown sx={{ color: 'text.secondary', fontSize: 21 }} />}
|
|
87
127
|
</Typography>
|
|
88
128
|
);
|
|
@@ -97,47 +137,64 @@ export default function CurrencySelect({
|
|
|
97
137
|
<Select
|
|
98
138
|
size="small"
|
|
99
139
|
value={value}
|
|
100
|
-
renderValue={() =>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
140
|
+
renderValue={() => {
|
|
141
|
+
if (includeAllOption && value === allOption.value) {
|
|
142
|
+
return (
|
|
143
|
+
<Typography variant="body1" sx={{ display: 'inline-flex', fontSize: '12px', color: 'text.secondary' }}>
|
|
144
|
+
{allOption.label}
|
|
145
|
+
</Typography>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const symbolText = selectedCurrency?.symbol || '';
|
|
150
|
+
const methodText = !hideMethod && selectedPaymentMethod?.name ? ` (${selectedPaymentMethod.name})` : '';
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<Typography variant="body1" sx={{ display: 'inline-flex', fontSize: '12px', color: 'text.secondary' }}>
|
|
154
|
+
{`${symbolText}${methodText}`.trim()}
|
|
155
|
+
</Typography>
|
|
156
|
+
);
|
|
157
|
+
}}
|
|
105
158
|
onChange={handleSelect}
|
|
106
159
|
open
|
|
107
160
|
sx={{ width, ...selectSX }}
|
|
108
161
|
disabled={disabled}
|
|
109
162
|
onClose={() => setMode(initialMode)}>
|
|
110
|
-
{
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
163
|
+
{includeAllOption ? (
|
|
164
|
+
<MenuItem value={allOption.value}>
|
|
165
|
+
<Typography
|
|
166
|
+
sx={{
|
|
167
|
+
fontWeight: 'bold',
|
|
168
|
+
}}>
|
|
169
|
+
{allOption.label}
|
|
170
|
+
</Typography>
|
|
171
|
+
</MenuItem>
|
|
172
|
+
) : null}
|
|
173
|
+
{availableGroups.flatMap((group) => {
|
|
174
|
+
const header = hideMethod ? null : (
|
|
175
|
+
<ListSubheader
|
|
176
|
+
key={group.method.id}
|
|
177
|
+
sx={{ fontSize: '0.875rem', color: 'text.secondary', lineHeight: '2.1875rem' }}>
|
|
178
|
+
{group.method.name}
|
|
179
|
+
</ListSubheader>
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const items = group.currencies.map((currency) => (
|
|
183
|
+
<MenuItem key={currency.id} sx={{ pl: 3 }} value={currency.id}>
|
|
184
|
+
<Stack direction="row" sx={{ width: '100%', justifyContent: 'space-between', gap: 2 }}>
|
|
185
|
+
{hideMethod ? null : <Currency logo={currency.logo} name={currency.name} />}
|
|
186
|
+
<Typography
|
|
187
|
+
sx={{
|
|
188
|
+
fontWeight: hideMethod ? 'normal' : 'bold',
|
|
189
|
+
}}>
|
|
190
|
+
{currency.symbol}
|
|
191
|
+
</Typography>
|
|
192
|
+
</Stack>
|
|
193
|
+
</MenuItem>
|
|
194
|
+
));
|
|
195
|
+
|
|
196
|
+
return header ? [header, ...items] : items;
|
|
197
|
+
})}
|
|
141
198
|
</Select>
|
|
142
199
|
);
|
|
143
200
|
}
|
|
@@ -380,7 +380,9 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
380
380
|
}}>
|
|
381
381
|
<Stack spacing={2} sx={{ flex: 1, minWidth: { xs: 'auto', sm: '300px' }, '>div': { width: '100%' } }}>
|
|
382
382
|
<Box sx={{ width: '100%' }}>
|
|
383
|
-
<FormLabel tooltip={t('admin.price.amountTip')}
|
|
383
|
+
<FormLabel tooltip={t('admin.price.amountTip')} description={t('admin.price.amountDescription')}>
|
|
384
|
+
{t('admin.price.amount')}
|
|
385
|
+
</FormLabel>
|
|
384
386
|
<Controller
|
|
385
387
|
name={getFieldName('unit_amount')}
|
|
386
388
|
control={control}
|