payment-kit 1.13.32 → 1.13.34
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/libs/session.ts +1 -1
- package/api/src/libs/util.ts +2 -2
- package/api/src/routes/prices.ts +62 -48
- package/api/src/routes/products.ts +21 -15
- package/api/src/store/models/price.ts +1 -1
- package/blocklet.yml +1 -1
- package/package.json +3 -3
- package/src/components/checkout/product-item.tsx +5 -5
- package/src/components/metadata/editor.tsx +0 -2
- package/src/components/price/upsell-select.tsx +3 -1
- package/src/components/price/upsell.tsx +1 -1
- package/src/libs/util.ts +36 -2
- package/src/locales/zh.tsx +3 -3
- package/src/pages/admin/products/prices/actions.tsx +1 -1
package/api/src/libs/session.ts
CHANGED
|
@@ -80,7 +80,7 @@ export function getCheckoutAmount(items: TLineItemExpanded[], currency: TPayment
|
|
|
80
80
|
|
|
81
81
|
export function getRecurringPeriod(recurring: PriceRecurring) {
|
|
82
82
|
const { interval } = recurring;
|
|
83
|
-
const count = recurring.interval_count || 1;
|
|
83
|
+
const count = +recurring.interval_count || 1;
|
|
84
84
|
const dayInMs = 24 * 60 * 60 * 1000;
|
|
85
85
|
|
|
86
86
|
switch (interval) {
|
package/api/src/libs/util.ts
CHANGED
|
@@ -75,7 +75,7 @@ export function formatMetadata(metadata?: Record<string, any>): Record<string, a
|
|
|
75
75
|
|
|
76
76
|
if (Array.isArray(metadata)) {
|
|
77
77
|
return metadata.reduce((acc: Record<string, any>, x: { key: string; value: any }) => {
|
|
78
|
-
if (x.value) {
|
|
78
|
+
if (x.value !== '') {
|
|
79
79
|
acc[x.key] = x.value;
|
|
80
80
|
}
|
|
81
81
|
return acc;
|
|
@@ -84,7 +84,7 @@ export function formatMetadata(metadata?: Record<string, any>): Record<string, a
|
|
|
84
84
|
|
|
85
85
|
// remove empty values
|
|
86
86
|
return Object.keys(metadata).reduce((acc: Record<string, any>, key: string) => {
|
|
87
|
-
if (metadata[key]) {
|
|
87
|
+
if (metadata[key] !== '') {
|
|
88
88
|
acc[key] = metadata[key];
|
|
89
89
|
}
|
|
90
90
|
return acc;
|
package/api/src/routes/prices.ts
CHANGED
|
@@ -12,7 +12,33 @@ const router = Router();
|
|
|
12
12
|
|
|
13
13
|
const auth = authenticate<Price>({ component: true, roles: ['owner', 'admin'] });
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
export async function getExpandedPrice(id: string) {
|
|
16
|
+
const price = await Price.findByPkOrLookupKey(id, {
|
|
17
|
+
include: [
|
|
18
|
+
{ model: Product, as: 'product' },
|
|
19
|
+
{ model: PaymentCurrency, as: 'currency' },
|
|
20
|
+
],
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (price) {
|
|
24
|
+
const currencies = await PaymentCurrency.findAll();
|
|
25
|
+
const doc = Price.formatAfterRead(price.toJSON(), currencies);
|
|
26
|
+
|
|
27
|
+
if (doc.upsell?.upsells_to_id) {
|
|
28
|
+
const to = await Price.findByPk(doc.upsell.upsells_to_id);
|
|
29
|
+
if (to) {
|
|
30
|
+
// @ts-ignore
|
|
31
|
+
doc.upsell.upsells_to = Price.formatAfterRead(to.toJSON(), currencies);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return doc;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// FIXME: @wangshijun use schema validation
|
|
16
42
|
// create price
|
|
17
43
|
// eslint-disable-next-line consistent-return
|
|
18
44
|
router.post('/', auth, async (req, res) => {
|
|
@@ -23,14 +49,17 @@ router.post('/', auth, async (req, res) => {
|
|
|
23
49
|
raw.currency_id = raw.currency_id || req.currency.id;
|
|
24
50
|
raw.created_via = req.user?.via as string;
|
|
25
51
|
|
|
52
|
+
if (!raw.product_id) {
|
|
53
|
+
return res.status(400).json({ error: 'product_id is required to create a price' });
|
|
54
|
+
}
|
|
55
|
+
|
|
26
56
|
if (!raw.unit_amount) {
|
|
27
57
|
return res.status(400).json({ error: 'price unit_amount is required' });
|
|
28
58
|
}
|
|
29
59
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
];
|
|
60
|
+
const product = await Product.findByPk(raw.product_id);
|
|
61
|
+
if (!product) {
|
|
62
|
+
return res.status(400).json({ error: `product ${raw.product_id} not found for price` });
|
|
34
63
|
}
|
|
35
64
|
|
|
36
65
|
const currencies = await PaymentCurrency.findAll({ where: { active: true } });
|
|
@@ -38,39 +67,29 @@ router.post('/', auth, async (req, res) => {
|
|
|
38
67
|
if (!currency) {
|
|
39
68
|
return res.status(400).json({ error: `currency used in price or not active: ${raw.currency_id}` });
|
|
40
69
|
}
|
|
41
|
-
|
|
70
|
+
|
|
71
|
+
if (Array.isArray(raw.currency_options) === false) {
|
|
72
|
+
raw.currency_options = [];
|
|
73
|
+
}
|
|
74
|
+
if (raw.currency_options.some((x) => x.currency_id === raw.currency_id) === false) {
|
|
75
|
+
raw.currency_options.unshift({
|
|
76
|
+
currency_id: raw.currency_id,
|
|
77
|
+
unit_amount: raw.unit_amount,
|
|
78
|
+
tiers: null,
|
|
79
|
+
custom_unit_amount: null,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
42
83
|
raw.currency_options = Price.formatCurrencies(raw.currency_options, currencies);
|
|
84
|
+
raw.unit_amount = fromTokenToUnit(raw.unit_amount, currency.decimal).toString();
|
|
43
85
|
|
|
44
86
|
const price = await Price.insert(raw);
|
|
45
|
-
|
|
46
|
-
res.json(price);
|
|
87
|
+
res.json(await getExpandedPrice(price.id as string));
|
|
47
88
|
});
|
|
48
89
|
|
|
49
90
|
// get price detail
|
|
50
91
|
router.get('/:id', auth, async (req, res) => {
|
|
51
|
-
|
|
52
|
-
include: [
|
|
53
|
-
{ model: Product, as: 'product' },
|
|
54
|
-
{ model: PaymentCurrency, as: 'currency' },
|
|
55
|
-
],
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
if (price) {
|
|
59
|
-
const currencies = await PaymentCurrency.findAll();
|
|
60
|
-
const doc = Price.formatAfterRead(price.toJSON(), currencies);
|
|
61
|
-
|
|
62
|
-
if (doc.upsell?.upsells_to_id) {
|
|
63
|
-
const to = await Price.findByPk(doc.upsell.upsells_to_id);
|
|
64
|
-
if (to) {
|
|
65
|
-
// @ts-ignore
|
|
66
|
-
doc.upsell.upsells_to = Price.formatAfterRead(to.toJSON(), currencies);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
res.json(doc);
|
|
71
|
-
} else {
|
|
72
|
-
res.json(null);
|
|
73
|
-
}
|
|
92
|
+
res.json(await getExpandedPrice(req.params.id as string));
|
|
74
93
|
});
|
|
75
94
|
|
|
76
95
|
router.get('/:id/upsell', auth, async (req, res) => {
|
|
@@ -107,7 +126,7 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
107
126
|
pick(
|
|
108
127
|
req.body,
|
|
109
128
|
price.locked
|
|
110
|
-
? ['nickname', 'description', 'metadata', 'upsell']
|
|
129
|
+
? ['nickname', 'description', 'metadata', 'currency_options', 'upsell']
|
|
111
130
|
: ['type', 'model', 'active', 'livemode', 'nickname', 'recurring', 'description', 'tiers', 'unit_amount', 'transform_quantity', 'metadata', 'lookup_key', 'currency_options', 'upsell'] // prettier-ignore
|
|
112
131
|
)
|
|
113
132
|
);
|
|
@@ -120,17 +139,16 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
120
139
|
}
|
|
121
140
|
|
|
122
141
|
const currencies = await PaymentCurrency.findAll({ where: { active: true } });
|
|
142
|
+
const currency = currencies.find((x) => x.id === price.currency_id);
|
|
143
|
+
if (!currency) {
|
|
144
|
+
return res.status(400).json({ error: `currency used in price not found or not active: ${price.currency_id}` });
|
|
145
|
+
}
|
|
123
146
|
if (updates.unit_amount) {
|
|
124
|
-
const currency = currencies.find((x) => x.id === price.currency_id);
|
|
125
|
-
if (!currency) {
|
|
126
|
-
return res.status(400).json({ error: `currency used in price not found or not active: ${price.currency_id}` });
|
|
127
|
-
}
|
|
128
147
|
updates.unit_amount = fromTokenToUnit(updates.unit_amount, currency.decimal).toString();
|
|
129
148
|
}
|
|
130
149
|
if (updates.currency_options) {
|
|
131
150
|
updates.currency_options = Price.formatCurrencies(updates.currency_options, currencies);
|
|
132
|
-
|
|
133
|
-
if (!base) {
|
|
151
|
+
if (updates.currency_options.some((x) => x.currency_id === price.currency_id) === false) {
|
|
134
152
|
updates.currency_options.unshift({
|
|
135
153
|
currency_id: price.currency_id,
|
|
136
154
|
unit_amount: price.unit_amount,
|
|
@@ -139,21 +157,16 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
139
157
|
});
|
|
140
158
|
}
|
|
141
159
|
if (updates.unit_amount) {
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
144
|
-
|
|
160
|
+
const base = price.currency_options.find((x) => x.currency_id === price.currency_id);
|
|
161
|
+
if (base) {
|
|
162
|
+
base.unit_amount = updates.unit_amount;
|
|
145
163
|
}
|
|
146
164
|
}
|
|
147
|
-
} else if (updates.unit_amount) {
|
|
148
|
-
const exist = price.currency_options.find((x) => x.currency_id === price.currency_id);
|
|
149
|
-
if (exist) {
|
|
150
|
-
exist.unit_amount = updates.unit_amount;
|
|
151
|
-
}
|
|
152
165
|
}
|
|
153
166
|
|
|
154
167
|
await price.update(Price.formatBeforeSave(updates));
|
|
155
168
|
|
|
156
|
-
return res.json(
|
|
169
|
+
return res.json(await getExpandedPrice(req.params.id as string));
|
|
157
170
|
});
|
|
158
171
|
|
|
159
172
|
// archive
|
|
@@ -173,7 +186,8 @@ router.put('/:id/archive', auth, async (req, res) => {
|
|
|
173
186
|
}
|
|
174
187
|
|
|
175
188
|
await price.update({ active: false });
|
|
176
|
-
|
|
189
|
+
|
|
190
|
+
return res.json(await getExpandedPrice(req.params.id as string));
|
|
177
191
|
});
|
|
178
192
|
|
|
179
193
|
// delete price
|
|
@@ -63,12 +63,14 @@ router.post('/', auth, async (req, res) => {
|
|
|
63
63
|
} else {
|
|
64
64
|
price.currency_options = [];
|
|
65
65
|
}
|
|
66
|
-
price.currency_options.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
if (price.currency_options.some((x) => x.currency_id === price.currency_id) === false) {
|
|
67
|
+
price.currency_options.unshift({
|
|
68
|
+
currency_id: price.currency_id,
|
|
69
|
+
unit_amount: price.unit_amount,
|
|
70
|
+
tiers: null,
|
|
71
|
+
custom_unit_amount: null,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
72
74
|
|
|
73
75
|
return price;
|
|
74
76
|
});
|
|
@@ -138,10 +140,9 @@ router.get('/', auth, async (req, res) => {
|
|
|
138
140
|
res.json({ count, list });
|
|
139
141
|
});
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
router.get('/:id', auth, async (req, res) => {
|
|
143
|
+
export async function getExpandedProduct(id: string) {
|
|
143
144
|
const product = await Product.findOne({
|
|
144
|
-
where: { id
|
|
145
|
+
where: { id },
|
|
145
146
|
include: [
|
|
146
147
|
{ model: Price, as: 'prices', include: [{ model: PaymentCurrency, as: 'currency' }] },
|
|
147
148
|
{ model: Price, as: 'default_price' },
|
|
@@ -169,10 +170,16 @@ router.get('/:id', auth, async (req, res) => {
|
|
|
169
170
|
];
|
|
170
171
|
}
|
|
171
172
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
res.json(null);
|
|
173
|
+
|
|
174
|
+
return doc;
|
|
175
175
|
}
|
|
176
|
+
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// get product detail
|
|
181
|
+
router.get('/:id', auth, async (req, res) => {
|
|
182
|
+
res.json(await getExpandedProduct(req.params.id as string));
|
|
176
183
|
});
|
|
177
184
|
|
|
178
185
|
// update product
|
|
@@ -204,7 +211,7 @@ router.put('/:id', auth, async (req, res) => {
|
|
|
204
211
|
}
|
|
205
212
|
await product.update(updates);
|
|
206
213
|
|
|
207
|
-
return res.json(
|
|
214
|
+
return res.json(await getExpandedProduct(req.params.id as string));
|
|
208
215
|
});
|
|
209
216
|
|
|
210
217
|
// archive
|
|
@@ -221,8 +228,7 @@ router.put('/:id/archive', auth, async (req, res) => {
|
|
|
221
228
|
await product.update({ active: !product.active });
|
|
222
229
|
|
|
223
230
|
// FIXME: deactivate payment-links, pricing-tables
|
|
224
|
-
|
|
225
|
-
return res.json(product);
|
|
231
|
+
return res.json(await getExpandedProduct(req.params.id as string));
|
|
226
232
|
});
|
|
227
233
|
|
|
228
234
|
// delete product
|
|
@@ -355,7 +355,7 @@ export class Price extends Model<InferAttributes<Price>, InferCreationAttributes
|
|
|
355
355
|
}
|
|
356
356
|
|
|
357
357
|
// @ts-ignore
|
|
358
|
-
return this.create(this.formatBeforeSave(price));
|
|
358
|
+
return this.create(this.formatBeforeSave({ model: 'standard', ...price }));
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
public static findByPkOrLookupKey(id: string, options: FindOptions<Price> = {}) {
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.34",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"@abtnode/types": "1.16.17-beta-952ef53d",
|
|
104
104
|
"@arcblock/eslint-config": "^0.2.4",
|
|
105
105
|
"@arcblock/eslint-config-ts": "^0.2.4",
|
|
106
|
-
"@did-pay/types": "1.13.
|
|
106
|
+
"@did-pay/types": "1.13.34",
|
|
107
107
|
"@types/cookie-parser": "^1.4.4",
|
|
108
108
|
"@types/cors": "^2.8.14",
|
|
109
109
|
"@types/dotenv-flow": "^3.3.1",
|
|
@@ -140,5 +140,5 @@
|
|
|
140
140
|
"parser": "typescript"
|
|
141
141
|
}
|
|
142
142
|
},
|
|
143
|
-
"gitHead": "
|
|
143
|
+
"gitHead": "136d14107082c64e9fdd822dc3f815cb5c8497b4"
|
|
144
144
|
}
|
|
@@ -61,12 +61,12 @@ export default function ProductItem({ item, session, currency, onUpsell, onDowns
|
|
|
61
61
|
onChange={() => onUpsell(item.price_id, item.price.upsell?.upsells_to_id)}
|
|
62
62
|
/>
|
|
63
63
|
{t('checkout.upsell.save', {
|
|
64
|
-
recurring: formatRecurring(item.price.upsell.upsells_to.recurring as PriceRecurring),
|
|
64
|
+
recurring: t(`common.${formatRecurring(item.price.upsell.upsells_to.recurring as PriceRecurring)}`),
|
|
65
65
|
})}
|
|
66
|
-
<Status label={t('checkout.upsell.off', { saving })} color="primary" variant="outlined" sx={{ ml:
|
|
66
|
+
<Status label={t('checkout.upsell.off', { saving })} color="primary" variant="outlined" sx={{ ml: 1 }} />
|
|
67
67
|
</Typography>
|
|
68
68
|
<Typography component="span" sx={{ fontSize: 12 }}>
|
|
69
|
-
{formatPrice(item.price.upsell.upsells_to, currency)}
|
|
69
|
+
{formatPrice(item.price.upsell.upsells_to, currency, item.price.product?.unit_label)}
|
|
70
70
|
</Typography>
|
|
71
71
|
</Stack>
|
|
72
72
|
)}
|
|
@@ -88,11 +88,11 @@ export default function ProductItem({ item, session, currency, onUpsell, onDowns
|
|
|
88
88
|
onChange={() => onDownsell(item.upsell_price_id)}
|
|
89
89
|
/>
|
|
90
90
|
{t('checkout.upsell.revert', {
|
|
91
|
-
recurring: formatRecurring(item.price.recurring as PriceRecurring),
|
|
91
|
+
recurring: t(`common.${formatRecurring(item.price.recurring as PriceRecurring)}`),
|
|
92
92
|
})}
|
|
93
93
|
</Typography>
|
|
94
94
|
<Typography component="span" sx={{ fontSize: 12 }}>
|
|
95
|
-
{formatPrice(item.price, currency)}
|
|
95
|
+
{formatPrice(item.price, currency, item.price.product?.unit_label)}
|
|
96
96
|
</Typography>
|
|
97
97
|
</Stack>
|
|
98
98
|
)}
|
|
@@ -20,8 +20,6 @@ export default function MetadataEditor({
|
|
|
20
20
|
const metadata = data.metadata || {};
|
|
21
21
|
const methods = useForm<any>({
|
|
22
22
|
defaultValues: {
|
|
23
|
-
...data,
|
|
24
|
-
// @ts-ignore
|
|
25
23
|
metadata: Object.keys(metadata).map((key: string) => ({ key, value: metadata[key] })),
|
|
26
24
|
},
|
|
27
25
|
});
|
|
@@ -12,13 +12,14 @@ import AddPrice from '../product/add-price';
|
|
|
12
12
|
type Props = {
|
|
13
13
|
price: TPriceExpanded;
|
|
14
14
|
onSelect: Function;
|
|
15
|
+
onAdd: Function;
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
const fetchData = (id: string): Promise<TPriceExpanded[]> => {
|
|
18
19
|
return api.get(`/api/prices/${id}/upsell`).then((res) => res.data);
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
export default function UpsellSelect({ price, onSelect }: Props) {
|
|
22
|
+
export default function UpsellSelect({ price, onSelect, onAdd }: Props) {
|
|
22
23
|
const { t } = useLocaleContext();
|
|
23
24
|
const [state, setState] = useSetState({ loading: false, adding: false, action: '' });
|
|
24
25
|
|
|
@@ -47,6 +48,7 @@ export default function UpsellSelect({ price, onSelect }: Props) {
|
|
|
47
48
|
setState({ adding: true });
|
|
48
49
|
await api.post('/api/prices', { ...formData, product_id: price.product_id });
|
|
49
50
|
Toast.success(t('common.saved'));
|
|
51
|
+
onAdd();
|
|
50
52
|
} catch (err) {
|
|
51
53
|
console.error(err);
|
|
52
54
|
Toast.error(formatError(err));
|
|
@@ -58,7 +58,7 @@ export function UpsellForm({ data, onChange }: { data: TPriceExpanded; onChange:
|
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
return <UpsellSelect price={data} onSelect={onSelectUpsell} />;
|
|
61
|
+
return <UpsellSelect price={data} onSelect={onSelectUpsell} onAdd={onChange} />;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
export default function PriceUpsell({ data, onChange }: { data: TPriceExpanded; onChange: Function }) {
|
package/src/libs/util.ts
CHANGED
|
@@ -332,9 +332,36 @@ export function formatPaymentLinkPricing(link: TPaymentLinkExpanded, currency: T
|
|
|
332
332
|
currency
|
|
333
333
|
);
|
|
334
334
|
}
|
|
335
|
+
export function getRecurringPeriod(recurring: PriceRecurring) {
|
|
336
|
+
const { interval } = recurring;
|
|
337
|
+
const count = +recurring.interval_count || 1;
|
|
338
|
+
const dayInMs = 24 * 60 * 60 * 1000;
|
|
339
|
+
|
|
340
|
+
switch (interval) {
|
|
341
|
+
case 'hour':
|
|
342
|
+
return 60 * 60 * 1000;
|
|
343
|
+
case 'day':
|
|
344
|
+
return count * dayInMs;
|
|
345
|
+
case 'week':
|
|
346
|
+
return count * 7 * dayInMs;
|
|
347
|
+
case 'month':
|
|
348
|
+
return count * 30 * dayInMs;
|
|
349
|
+
case 'year':
|
|
350
|
+
return count * 365 * dayInMs;
|
|
351
|
+
default:
|
|
352
|
+
throw new Error(`Unsupported recurring interval: ${interval}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
335
355
|
|
|
336
356
|
export function formatUpsellSaving(session: TCheckoutSessionExpanded, currency: TPaymentCurrency) {
|
|
337
357
|
const items = session.line_items as TLineItemExpanded[];
|
|
358
|
+
if (items[0]?.upsell_price_id) {
|
|
359
|
+
return '0';
|
|
360
|
+
}
|
|
361
|
+
if (!items[0]?.price.upsell?.upsells_to) {
|
|
362
|
+
return '0';
|
|
363
|
+
}
|
|
364
|
+
|
|
338
365
|
const from = getCheckoutAmount(items, currency, false, false);
|
|
339
366
|
const to = getCheckoutAmount(
|
|
340
367
|
items.map((x) => ({
|
|
@@ -347,11 +374,18 @@ export function formatUpsellSaving(session: TCheckoutSessionExpanded, currency:
|
|
|
347
374
|
true
|
|
348
375
|
);
|
|
349
376
|
|
|
350
|
-
const
|
|
377
|
+
const fromRecurring = items[0].price?.recurring as PriceRecurring;
|
|
378
|
+
const toRecurring = items[0].price?.upsell?.upsells_to?.recurring as PriceRecurring;
|
|
379
|
+
if (!fromRecurring || !toRecurring) {
|
|
380
|
+
return '0';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const factor = Math.floor(getRecurringPeriod(toRecurring) / getRecurringPeriod(fromRecurring));
|
|
384
|
+
|
|
351
385
|
const before = new BN(from.total).mul(new BN(factor));
|
|
352
386
|
const after = new BN(to.total);
|
|
353
387
|
|
|
354
|
-
return Number(before.sub(after).mul(new BN(100)).div(
|
|
388
|
+
return Number(before.sub(after).mul(new BN(100)).div(before).toString()).toFixed(0);
|
|
355
389
|
}
|
|
356
390
|
|
|
357
391
|
export function formatCheckoutHeadlines(
|
package/src/locales/zh.tsx
CHANGED
|
@@ -501,15 +501,15 @@ export default flat({
|
|
|
501
501
|
},
|
|
502
502
|
customer: {
|
|
503
503
|
name: '姓名',
|
|
504
|
-
email: '
|
|
504
|
+
email: '邮件',
|
|
505
505
|
phone: '电话',
|
|
506
506
|
phonePlaceholder: '电话号码',
|
|
507
507
|
phoneTip: '以防需要与您联系有关您的订单',
|
|
508
508
|
},
|
|
509
509
|
upsell: {
|
|
510
|
-
save: '使用{recurring}
|
|
510
|
+
save: '使用{recurring}计费方式',
|
|
511
511
|
revert: '切换到{recurring}计费方式',
|
|
512
|
-
off: '{saving}
|
|
512
|
+
off: '省 {saving}%',
|
|
513
513
|
},
|
|
514
514
|
expired: {
|
|
515
515
|
title: '链接已过期',
|
|
@@ -26,7 +26,7 @@ export default function PriceActions(props: Props) {
|
|
|
26
26
|
|
|
27
27
|
const canEdit = props.data.active;
|
|
28
28
|
const canArchive = props.data.active;
|
|
29
|
-
const canRemove = !props.data.locked && props.setAsDefault;
|
|
29
|
+
const canRemove = !props.data.locked && !props.setAsDefault;
|
|
30
30
|
|
|
31
31
|
const [state, setState] = useSetState({
|
|
32
32
|
action: '',
|