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
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
+
import { Collapse, CountrySelect, FormInput, FormLabel } from '@blocklet/payment-react';
|
|
3
|
+
import { InputAdornment, Stack, TextField, Typography } from '@mui/material';
|
|
4
|
+
import { Controller, useFormContext } from 'react-hook-form';
|
|
5
|
+
import { defaultCountries, parseCountry } from 'react-international-phone';
|
|
6
|
+
|
|
7
|
+
import MetadataForm from '../metadata/form';
|
|
8
|
+
import TaxCodeSelect from './tax-code-select';
|
|
9
|
+
import taxCodesRaw from './taxCodes.json';
|
|
10
|
+
|
|
11
|
+
export type TaxRateFormValues = {
|
|
12
|
+
display_name: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
country: string;
|
|
15
|
+
state?: string;
|
|
16
|
+
postal_code?: string;
|
|
17
|
+
tax_code?: string;
|
|
18
|
+
percentage: number;
|
|
19
|
+
metadata?: Record<string, any>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const generateTaxRateDisplayName = (
|
|
23
|
+
country: string,
|
|
24
|
+
state: string | undefined,
|
|
25
|
+
postalCode: string | undefined,
|
|
26
|
+
percentage: number,
|
|
27
|
+
taxCode: string | undefined,
|
|
28
|
+
locale: string
|
|
29
|
+
): string => {
|
|
30
|
+
const taxCodesById: Record<string, any> = taxCodesRaw as unknown as Record<string, any>;
|
|
31
|
+
const parts: string[] = [];
|
|
32
|
+
|
|
33
|
+
if (country) {
|
|
34
|
+
const match = defaultCountries.find((item) => item[1] === country.toLowerCase());
|
|
35
|
+
if (match) {
|
|
36
|
+
const countryData = parseCountry(match);
|
|
37
|
+
parts.push(countryData.name);
|
|
38
|
+
} else {
|
|
39
|
+
parts.push(country.toUpperCase());
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (state) {
|
|
44
|
+
parts.push(state);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (postalCode) {
|
|
48
|
+
parts.push(postalCode);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (taxCode && taxCodesById[taxCode]) {
|
|
52
|
+
const taxCodeData = taxCodesById[taxCode];
|
|
53
|
+
const language = locale.startsWith('zh') ? 'zh' : 'en';
|
|
54
|
+
const taxCodeName = taxCodeData.name?.[language] || taxCodeData.name?.en || '';
|
|
55
|
+
if (taxCodeName) {
|
|
56
|
+
parts.push(`(${taxCodeName})`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const locationPart = parts.join(' · ');
|
|
61
|
+
return `${locationPart} - ${percentage}%`;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default function TaxRateForm({ isEdit = false }: { isEdit?: boolean }) {
|
|
65
|
+
const { t } = useLocaleContext();
|
|
66
|
+
const {
|
|
67
|
+
control,
|
|
68
|
+
register,
|
|
69
|
+
formState: { errors },
|
|
70
|
+
} = useFormContext<TaxRateFormValues & { metadata: any }>();
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Stack spacing={3} sx={{ py: 1 }}>
|
|
74
|
+
<Stack spacing={2}>
|
|
75
|
+
{/* 必填字段区域 */}
|
|
76
|
+
<Stack spacing={1}>
|
|
77
|
+
<FormLabel sx={{ color: 'text.primary', my: 0 }} required>
|
|
78
|
+
{t('admin.taxRate.country')}
|
|
79
|
+
</FormLabel>
|
|
80
|
+
|
|
81
|
+
<Controller
|
|
82
|
+
name="country"
|
|
83
|
+
control={control}
|
|
84
|
+
rules={{ required: t('admin.taxRate.countryRequired') }}
|
|
85
|
+
render={({ field: { ref, ...fieldProps } }) => (
|
|
86
|
+
<>
|
|
87
|
+
<CountrySelect
|
|
88
|
+
{...fieldProps}
|
|
89
|
+
name={fieldProps.name as any}
|
|
90
|
+
onChange={(value) => fieldProps.onChange(value || '')}
|
|
91
|
+
disabled={isEdit}
|
|
92
|
+
/>
|
|
93
|
+
{errors.country && (
|
|
94
|
+
<Typography variant="caption" color="error">
|
|
95
|
+
{errors.country.message as string}
|
|
96
|
+
</Typography>
|
|
97
|
+
)}
|
|
98
|
+
</>
|
|
99
|
+
)}
|
|
100
|
+
/>
|
|
101
|
+
</Stack>
|
|
102
|
+
|
|
103
|
+
<FormInput
|
|
104
|
+
name="percentage"
|
|
105
|
+
type="number"
|
|
106
|
+
label={t('admin.taxRate.percentage')}
|
|
107
|
+
placeholder="0"
|
|
108
|
+
required
|
|
109
|
+
InputProps={{
|
|
110
|
+
endAdornment: <InputAdornment position="end">%</InputAdornment>,
|
|
111
|
+
}}
|
|
112
|
+
rules={{
|
|
113
|
+
required: t('admin.taxRate.percentageRequired'),
|
|
114
|
+
min: { value: 0, message: t('admin.taxRate.percentageMin') },
|
|
115
|
+
max: { value: 99.9999, message: t('admin.taxRate.percentageMax') },
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
|
|
119
|
+
{/* 可选字段区域 */}
|
|
120
|
+
<Stack spacing={1}>
|
|
121
|
+
<FormLabel sx={{ color: 'text.primary', my: 0 }}>{t('admin.taxRate.taxCode')}</FormLabel>
|
|
122
|
+
<TaxCodeSelect label="" placeholder={t('admin.taxRate.selectTaxCode')} disabled={isEdit} />
|
|
123
|
+
</Stack>
|
|
124
|
+
|
|
125
|
+
<Stack direction={{ xs: 'column', md: 'row' }} spacing={2}>
|
|
126
|
+
<Stack spacing={1} sx={{ flex: 1 }}>
|
|
127
|
+
<FormLabel sx={{ color: 'text.primary', my: 0 }}>{t('admin.taxRate.state')}</FormLabel>
|
|
128
|
+
<TextField size="small" {...register('state')} placeholder={t('admin.taxRate.state')} disabled={isEdit} />
|
|
129
|
+
</Stack>
|
|
130
|
+
<Stack spacing={1} sx={{ flex: 1 }}>
|
|
131
|
+
<FormLabel sx={{ color: 'text.primary', my: 0 }}>{t('admin.taxRate.postalCode')}</FormLabel>
|
|
132
|
+
|
|
133
|
+
<TextField
|
|
134
|
+
size="small"
|
|
135
|
+
{...register('postal_code')}
|
|
136
|
+
placeholder={t('admin.taxRate.postalCode')}
|
|
137
|
+
disabled={isEdit}
|
|
138
|
+
/>
|
|
139
|
+
</Stack>
|
|
140
|
+
</Stack>
|
|
141
|
+
|
|
142
|
+
<FormInput
|
|
143
|
+
name="display_name"
|
|
144
|
+
label={t('admin.taxRate.displayName')}
|
|
145
|
+
placeholder={t('admin.taxRate.displayNameAuto')}
|
|
146
|
+
rules={{
|
|
147
|
+
maxLength: {
|
|
148
|
+
value: 100,
|
|
149
|
+
message: t('common.maxLength', { len: 100 }),
|
|
150
|
+
},
|
|
151
|
+
}}
|
|
152
|
+
/>
|
|
153
|
+
|
|
154
|
+
<FormInput
|
|
155
|
+
name="description"
|
|
156
|
+
label={t('admin.taxRate.description')}
|
|
157
|
+
placeholder={t('admin.taxRate.descriptionPlaceholder')}
|
|
158
|
+
multiline
|
|
159
|
+
minRows={2}
|
|
160
|
+
maxRows={4}
|
|
161
|
+
rules={{
|
|
162
|
+
maxLength: {
|
|
163
|
+
value: 500,
|
|
164
|
+
message: t('common.maxLength', { len: 500 }),
|
|
165
|
+
},
|
|
166
|
+
}}
|
|
167
|
+
/>
|
|
168
|
+
</Stack>
|
|
169
|
+
|
|
170
|
+
<Collapse trigger={t('admin.taxRate.advanced')}>
|
|
171
|
+
<Stack spacing={1}>
|
|
172
|
+
<MetadataForm title={t('common.metadata.label')} name="metadata" color="inherit" />
|
|
173
|
+
</Stack>
|
|
174
|
+
</Collapse>
|
|
175
|
+
</Stack>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defaultCountries, parseCountry } from 'react-international-phone';
|
|
2
|
+
|
|
3
|
+
import taxCodesRaw from './taxCodes.json';
|
|
4
|
+
|
|
5
|
+
const taxCodesById: Record<string, any> = taxCodesRaw as unknown as Record<string, any>;
|
|
6
|
+
|
|
7
|
+
export const getTaxCodeInfo = (taxCode: string, locale: string = 'en') => {
|
|
8
|
+
if (!taxCode || !taxCodesById[taxCode]) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const taxCodeData = taxCodesById[taxCode];
|
|
13
|
+
const language = locale.startsWith('zh') ? 'zh' : 'en';
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
id: taxCode,
|
|
17
|
+
name: taxCodeData.name?.[language] || taxCodeData.name?.en || taxCode,
|
|
18
|
+
description: taxCodeData.description?.[language] || taxCodeData.description?.en || '',
|
|
19
|
+
type: taxCodeData.type,
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const getCountryInfo = (countryCode: string) => {
|
|
24
|
+
if (!countryCode) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const match = defaultCountries.find((item) => item[1] === countryCode.toLowerCase());
|
|
29
|
+
if (!match) {
|
|
30
|
+
return {
|
|
31
|
+
iso2: countryCode.toLowerCase(),
|
|
32
|
+
name: countryCode.toUpperCase(),
|
|
33
|
+
dialCode: '',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return parseCountry(match);
|
|
38
|
+
};
|