payment-kit 1.21.13 → 1.21.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.
- 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/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/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 -3
- package/src/components/invoice/table.tsx +48 -2
- package/src/components/metadata/form.tsx +2 -2
- package/src/components/payment-intent/list.tsx +19 -3
- package/src/components/payouts/list.tsx +19 -3
- 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 -3
- package/src/components/subscription/items/actions.tsx +25 -15
- package/src/components/subscription/list.tsx +15 -3
- 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/hooks/cache-state.ts +84 -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.15
|
|
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.15",
|
|
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.15",
|
|
60
|
+
"@blocklet/payment-react": "1.21.15",
|
|
61
|
+
"@blocklet/payment-vendor": "1.21.15",
|
|
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.15",
|
|
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": "fd48f9233f19514b537e30b013e09a7d9f7a9f48"
|
|
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"
|
|
@@ -13,9 +13,9 @@ import {
|
|
|
13
13
|
} from '@blocklet/payment-react';
|
|
14
14
|
import type { TInvoiceExpanded } from '@blocklet/payment-types';
|
|
15
15
|
import { Avatar, CircularProgress, Typography } from '@mui/material';
|
|
16
|
-
import { useLocalStorageState } from 'ahooks';
|
|
17
16
|
import { useEffect, useState } from 'react';
|
|
18
|
-
import { Link } from 'react-router-dom';
|
|
17
|
+
import { Link, useSearchParams } from 'react-router-dom';
|
|
18
|
+
import { useCacheState } from '../../hooks/cache-state';
|
|
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,23 +123,39 @@ 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);
|
|
120
|
-
|
|
131
|
+
|
|
132
|
+
const [search, setSearch] = useCacheState<
|
|
121
133
|
SearchProps & { ignore_zero?: boolean; include_staking?: boolean; include_return_staking?: boolean }
|
|
122
134
|
>(listKey, {
|
|
123
135
|
defaultValue: {
|
|
124
136
|
status: status as string,
|
|
125
137
|
customer_id,
|
|
126
138
|
subscription_id,
|
|
139
|
+
tax_rate_id,
|
|
127
140
|
pageSize: defaultPageSize,
|
|
128
141
|
page: 1,
|
|
129
142
|
ignore_zero: !!ignore_zero,
|
|
130
143
|
include_staking: !!include_staking,
|
|
131
144
|
include_return_staking: !!include_return_staking,
|
|
132
145
|
},
|
|
146
|
+
getUrlParams: () => {
|
|
147
|
+
const params: Record<string, any> = {};
|
|
148
|
+
if (searchParams.has('status')) {
|
|
149
|
+
params.status = searchParams.get('status');
|
|
150
|
+
}
|
|
151
|
+
if (searchParams.has('currency_id')) {
|
|
152
|
+
params.currency_id = searchParams.get('currency_id');
|
|
153
|
+
}
|
|
154
|
+
if (searchParams.has('customer_id')) {
|
|
155
|
+
params.customer_id = searchParams.get('customer_id');
|
|
156
|
+
}
|
|
157
|
+
return params;
|
|
158
|
+
},
|
|
133
159
|
});
|
|
134
160
|
|
|
135
161
|
const [data, setData] = useState({}) as any;
|
|
@@ -140,6 +166,7 @@ export default function InvoiceList({
|
|
|
140
166
|
include_staking: !!include_staking,
|
|
141
167
|
ignore_zero: !!ignore_zero,
|
|
142
168
|
include_return_staking: !!include_return_staking,
|
|
169
|
+
tax_rate_id,
|
|
143
170
|
}).then((res: any) => {
|
|
144
171
|
setData(res);
|
|
145
172
|
});
|
|
@@ -148,6 +175,14 @@ export default function InvoiceList({
|
|
|
148
175
|
refresh();
|
|
149
176
|
}, [search]);
|
|
150
177
|
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
if (tax_rate_id && search?.tax_rate_id !== tax_rate_id) {
|
|
180
|
+
// @ts-ignore
|
|
181
|
+
setSearch((prev) => ({ ...(prev || {}), tax_rate_id, page: 1 }));
|
|
182
|
+
}
|
|
183
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
184
|
+
}, [tax_rate_id]);
|
|
185
|
+
|
|
151
186
|
if (!data.list) {
|
|
152
187
|
return <CircularProgress />;
|
|
153
188
|
}
|
|
@@ -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) => {
|
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
} from '@blocklet/payment-react';
|
|
12
12
|
import type { TPaymentIntentExpanded } from '@blocklet/payment-types';
|
|
13
13
|
import { Avatar, CircularProgress, Typography } from '@mui/material';
|
|
14
|
-
import { useLocalStorageState } from 'ahooks';
|
|
15
14
|
import { useEffect, useState } from 'react';
|
|
16
|
-
import { Link } from 'react-router-dom';
|
|
15
|
+
import { Link, useSearchParams } from 'react-router-dom';
|
|
17
16
|
|
|
18
17
|
import { debounce } from '../../libs/util';
|
|
18
|
+
import { useCacheState } from '../../hooks/cache-state';
|
|
19
19
|
import CustomerLink from '../customer/link';
|
|
20
20
|
import FilterToolbar from '../filter-toolbar';
|
|
21
21
|
import PaymentIntentActions from './actions';
|
|
@@ -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,10 +76,12 @@ 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);
|
|
81
|
-
|
|
83
|
+
|
|
84
|
+
const [search, setSearch] = useCacheState<SearchProps>(listKey, {
|
|
82
85
|
defaultValue: {
|
|
83
86
|
status: '',
|
|
84
87
|
customer_id,
|
|
@@ -86,6 +89,19 @@ export default function PaymentList({
|
|
|
86
89
|
pageSize: defaultPageSize,
|
|
87
90
|
page: 1,
|
|
88
91
|
},
|
|
92
|
+
getUrlParams: () => {
|
|
93
|
+
const params: Record<string, any> = {};
|
|
94
|
+
if (searchParams.has('status')) {
|
|
95
|
+
params.status = searchParams.get('status');
|
|
96
|
+
}
|
|
97
|
+
if (searchParams.has('currency_id')) {
|
|
98
|
+
params.currency_id = searchParams.get('currency_id');
|
|
99
|
+
}
|
|
100
|
+
if (searchParams.has('customer_id')) {
|
|
101
|
+
params.customer_id = searchParams.get('customer_id');
|
|
102
|
+
}
|
|
103
|
+
return params;
|
|
104
|
+
},
|
|
89
105
|
});
|
|
90
106
|
|
|
91
107
|
const [data, setData] = useState({}) as any;
|
|
@@ -11,13 +11,13 @@ import {
|
|
|
11
11
|
} from '@blocklet/payment-react';
|
|
12
12
|
import type { TPayoutExpanded } from '@blocklet/payment-types';
|
|
13
13
|
import { Avatar, CircularProgress, Typography } from '@mui/material';
|
|
14
|
-
import { useLocalStorageState } from 'ahooks';
|
|
15
14
|
import { useEffect, useState } from 'react';
|
|
16
|
-
import { Link } from 'react-router-dom';
|
|
15
|
+
import { Link, useSearchParams } from 'react-router-dom';
|
|
17
16
|
|
|
18
17
|
import DID from '@arcblock/ux/lib/DID';
|
|
19
18
|
import ShortenLabel from '@arcblock/ux/lib/UserCard/Content/shorten-label';
|
|
20
19
|
import { debounce, getAppInfo } from '../../libs/util';
|
|
20
|
+
import { useCacheState } from '../../hooks/cache-state';
|
|
21
21
|
import CustomerLink from '../customer/link';
|
|
22
22
|
import FilterToolbar from '../filter-toolbar';
|
|
23
23
|
import PayoutActions from './actions';
|
|
@@ -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,10 +81,12 @@ 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);
|
|
86
|
-
|
|
88
|
+
|
|
89
|
+
const [search, setSearch] = useCacheState<SearchProps>(listKey, {
|
|
87
90
|
defaultValue: {
|
|
88
91
|
status: status as string,
|
|
89
92
|
customer_id,
|
|
@@ -91,6 +94,19 @@ export default function PayoutList({
|
|
|
91
94
|
pageSize: defaultPageSize,
|
|
92
95
|
page: 1,
|
|
93
96
|
},
|
|
97
|
+
getUrlParams: () => {
|
|
98
|
+
const params: Record<string, any> = {};
|
|
99
|
+
if (searchParams.has('status')) {
|
|
100
|
+
params.status = searchParams.get('status');
|
|
101
|
+
}
|
|
102
|
+
if (searchParams.has('currency_id')) {
|
|
103
|
+
params.currency_id = searchParams.get('currency_id');
|
|
104
|
+
}
|
|
105
|
+
if (searchParams.has('customer_id')) {
|
|
106
|
+
params.customer_id = searchParams.get('customer_id');
|
|
107
|
+
}
|
|
108
|
+
return params;
|
|
109
|
+
},
|
|
94
110
|
});
|
|
95
111
|
|
|
96
112
|
const [data, setData] = useState({}) as any;
|
|
@@ -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}
|