payment-kit 1.18.19 → 1.18.21
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/integrations/blocklet/user.ts +1 -0
- package/api/src/queues/payment.ts +2 -2
- package/api/src/routes/checkout-sessions.ts +36 -4
- package/api/src/routes/connect/shared.ts +1 -0
- package/api/src/routes/customers.ts +2 -2
- package/api/src/routes/settings.ts +3 -3
- package/api/src/store/models/customer.ts +22 -0
- package/blocklet.yml +1 -1
- package/package.json +8 -8
- package/src/components/layout/user.tsx +10 -0
- package/src/libs/dayjs.ts +4 -3
- package/src/pages/customer/index.tsx +32 -23
- package/src/pages/customer/recharge/account.tsx +28 -1
- package/src/pages/customer/recharge/subscription.tsx +5 -1
|
@@ -125,8 +125,8 @@ export const handlePaymentSucceed = async (
|
|
|
125
125
|
did: user.did,
|
|
126
126
|
name: user.fullName,
|
|
127
127
|
email: user.email,
|
|
128
|
-
phone:
|
|
129
|
-
address:
|
|
128
|
+
phone: user.phone,
|
|
129
|
+
address: Customer.formatAddressFromUser(user),
|
|
130
130
|
description: user.remark,
|
|
131
131
|
metadata: {},
|
|
132
132
|
balance: '0',
|
|
@@ -76,6 +76,7 @@ import { ensureInvoiceForCheckout } from './connect/shared';
|
|
|
76
76
|
import { isCreditSufficientForPayment, isDelegationSufficientForPayment } from '../libs/payment';
|
|
77
77
|
import { handleStripeSubscriptionSucceed } from '../integrations/stripe/handlers/subscription';
|
|
78
78
|
import { CHARGE_SUPPORTED_CHAIN_TYPES } from '../libs/constants';
|
|
79
|
+
import { blocklet } from '../libs/auth';
|
|
79
80
|
|
|
80
81
|
const router = Router();
|
|
81
82
|
|
|
@@ -838,14 +839,15 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
838
839
|
|
|
839
840
|
let customer = await Customer.findOne({ where: { did: req.user.did } });
|
|
840
841
|
if (!customer) {
|
|
842
|
+
const { user: userInfo } = await blocklet.getUser(req.user.did);
|
|
841
843
|
customer = await Customer.create({
|
|
842
844
|
livemode: !!checkoutSession.livemode,
|
|
843
845
|
did: req.user.did,
|
|
844
846
|
name: req.body.customer_name,
|
|
845
|
-
email: req.body.customer_email,
|
|
846
|
-
phone: req.body.customer_phone,
|
|
847
|
-
address: req.body.billing_address,
|
|
848
|
-
description: '',
|
|
847
|
+
email: req.body.customer_email || userInfo?.email || '',
|
|
848
|
+
phone: req.body.customer_phone || userInfo?.phone || '',
|
|
849
|
+
address: req.body.billing_address || Customer.formatAddressFromUser(userInfo),
|
|
850
|
+
description: userInfo?.remark || '',
|
|
849
851
|
metadata: {},
|
|
850
852
|
balance: '0',
|
|
851
853
|
next_invoice_sequence: 1,
|
|
@@ -853,6 +855,20 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
853
855
|
invoice_prefix: Customer.getInvoicePrefix(),
|
|
854
856
|
});
|
|
855
857
|
logger.info('customer created on checkout session submit', { did: req.user.did, id: customer.id });
|
|
858
|
+
try {
|
|
859
|
+
await blocklet.updateUserAddress({
|
|
860
|
+
did: customer.did,
|
|
861
|
+
address: Customer.formatAddressFromCustomer(customer),
|
|
862
|
+
});
|
|
863
|
+
logger.info('updateUserAddress success', {
|
|
864
|
+
did: customer.did,
|
|
865
|
+
});
|
|
866
|
+
} catch (err) {
|
|
867
|
+
logger.error('updateUserAddress failed', {
|
|
868
|
+
error: err,
|
|
869
|
+
customerId: customer.id,
|
|
870
|
+
});
|
|
871
|
+
}
|
|
856
872
|
} else {
|
|
857
873
|
const updates: Record<string, string> = {};
|
|
858
874
|
if (checkoutSession.customer_update?.name) {
|
|
@@ -868,6 +884,22 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
868
884
|
}
|
|
869
885
|
|
|
870
886
|
await customer.update(updates);
|
|
887
|
+
try {
|
|
888
|
+
await blocklet.updateUserAddress({
|
|
889
|
+
did: customer.did,
|
|
890
|
+
address: Customer.formatAddressFromCustomer(customer),
|
|
891
|
+
// @ts-ignore
|
|
892
|
+
phone: customer.phone,
|
|
893
|
+
});
|
|
894
|
+
logger.info('updateUserAddress success', {
|
|
895
|
+
did: customer.did,
|
|
896
|
+
});
|
|
897
|
+
} catch (err) {
|
|
898
|
+
logger.error('updateUserAddress failed', {
|
|
899
|
+
error: err,
|
|
900
|
+
customerId: customer.id,
|
|
901
|
+
});
|
|
902
|
+
}
|
|
871
903
|
}
|
|
872
904
|
|
|
873
905
|
// check if customer can make new purchase
|
|
@@ -117,6 +117,7 @@ export async function ensurePaymentIntent(checkoutSessionId: string, userDid?: s
|
|
|
117
117
|
metadata: { fromDonation: true },
|
|
118
118
|
livemode: checkoutSession.livemode,
|
|
119
119
|
phone: user.phone,
|
|
120
|
+
address: Customer.formatAddressFromUser(user),
|
|
120
121
|
delinquent: false,
|
|
121
122
|
balance: '0',
|
|
122
123
|
next_invoice_sequence: 1,
|
|
@@ -115,7 +115,7 @@ router.get('/me', sessionMiddleware(), async (req, res) => {
|
|
|
115
115
|
name: user.fullName,
|
|
116
116
|
email: user.email,
|
|
117
117
|
phone: user.phone,
|
|
118
|
-
address:
|
|
118
|
+
address: Customer.formatAddressFromUser(user),
|
|
119
119
|
description: user.remark,
|
|
120
120
|
metadata: {},
|
|
121
121
|
balance: '0',
|
|
@@ -169,7 +169,7 @@ router.get('/me', sessionMiddleware(), async (req, res) => {
|
|
|
169
169
|
});
|
|
170
170
|
|
|
171
171
|
// get overdue invoices
|
|
172
|
-
router.get('/:id/overdue/invoices',
|
|
172
|
+
router.get('/:id/overdue/invoices', sessionMiddleware(), async (req, res) => {
|
|
173
173
|
if (!req.user) {
|
|
174
174
|
return res.status(403).json({ error: 'Unauthorized' });
|
|
175
175
|
}
|
|
@@ -140,7 +140,7 @@ router.post('/', async (req, res) => {
|
|
|
140
140
|
const defaultSetting = {
|
|
141
141
|
amount: {
|
|
142
142
|
presets: ['1', '5', '10'],
|
|
143
|
-
preset: '
|
|
143
|
+
preset: '5',
|
|
144
144
|
minimum: '0.01',
|
|
145
145
|
maximum: '100',
|
|
146
146
|
custom: true,
|
|
@@ -154,7 +154,7 @@ router.post('/', async (req, res) => {
|
|
|
154
154
|
presets: Joi.array()
|
|
155
155
|
.items(Joi.string())
|
|
156
156
|
.when('custom', { is: false, then: Joi.required(), otherwise: Joi.optional() }),
|
|
157
|
-
preset: Joi.string().optional(),
|
|
157
|
+
preset: Joi.string().empty('').optional(),
|
|
158
158
|
minimum: Joi.string().when('custom', { is: true, then: Joi.required() }),
|
|
159
159
|
maximum: Joi.string().when('custom', { is: true, then: Joi.required() }),
|
|
160
160
|
custom: Joi.boolean().required(),
|
|
@@ -238,7 +238,7 @@ router.put('/:mountLocationOrId', authAdmin, async (req, res) => {
|
|
|
238
238
|
presets: Joi.array()
|
|
239
239
|
.items(Joi.string())
|
|
240
240
|
.when('custom', { is: false, then: Joi.required(), otherwise: Joi.optional() }),
|
|
241
|
-
preset: Joi.string().optional(),
|
|
241
|
+
preset: Joi.string().empty('').optional(),
|
|
242
242
|
minimum: Joi.string().when('custom', { is: true, then: Joi.required() }),
|
|
243
243
|
maximum: Joi.string().when('custom', { is: true, then: Joi.required() }),
|
|
244
244
|
custom: Joi.boolean().required(),
|
|
@@ -282,6 +282,28 @@ export class Customer extends Model<InferAttributes<Customer>, InferCreationAttr
|
|
|
282
282
|
public static getInvoicePrefix() {
|
|
283
283
|
return nextInvoicePrefix();
|
|
284
284
|
}
|
|
285
|
+
|
|
286
|
+
public static formatAddressFromUser(user: any) {
|
|
287
|
+
return {
|
|
288
|
+
country: user.address?.country || '',
|
|
289
|
+
state: user.address?.province || '',
|
|
290
|
+
city: user.address?.city || '',
|
|
291
|
+
line1: user.address?.line1 || '',
|
|
292
|
+
line2: user.address?.line2 || '',
|
|
293
|
+
postal_code: user.address?.postalCode || '',
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
public static formatAddressFromCustomer(customer: Customer) {
|
|
298
|
+
return {
|
|
299
|
+
country: customer.address?.country || '',
|
|
300
|
+
province: customer.address?.state || '',
|
|
301
|
+
city: customer.address?.city || '',
|
|
302
|
+
line1: customer.address?.line1 || '',
|
|
303
|
+
line2: customer.address?.line2 || '',
|
|
304
|
+
postalCode: customer.address?.postal_code || '',
|
|
305
|
+
};
|
|
306
|
+
}
|
|
285
307
|
}
|
|
286
308
|
|
|
287
309
|
export type TCustomer = InferAttributes<Customer>;
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.18.
|
|
3
|
+
"version": "1.18.21",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -46,16 +46,16 @@
|
|
|
46
46
|
"@abtnode/cron": "^1.16.40",
|
|
47
47
|
"@arcblock/did": "^1.19.15",
|
|
48
48
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
49
|
-
"@arcblock/did-connect": "^2.12.
|
|
49
|
+
"@arcblock/did-connect": "^2.12.43",
|
|
50
50
|
"@arcblock/did-util": "^1.19.15",
|
|
51
51
|
"@arcblock/jwt": "^1.19.15",
|
|
52
|
-
"@arcblock/ux": "^2.12.
|
|
52
|
+
"@arcblock/ux": "^2.12.43",
|
|
53
53
|
"@arcblock/validator": "^1.19.15",
|
|
54
54
|
"@blocklet/js-sdk": "^1.16.40",
|
|
55
55
|
"@blocklet/logger": "^1.16.40",
|
|
56
|
-
"@blocklet/payment-react": "1.18.
|
|
56
|
+
"@blocklet/payment-react": "1.18.21",
|
|
57
57
|
"@blocklet/sdk": "^1.16.40",
|
|
58
|
-
"@blocklet/ui-react": "^2.12.
|
|
58
|
+
"@blocklet/ui-react": "^2.12.43",
|
|
59
59
|
"@blocklet/uploader": "^0.1.79",
|
|
60
60
|
"@blocklet/xss": "^0.1.30",
|
|
61
61
|
"@mui/icons-material": "^5.16.6",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"devDependencies": {
|
|
122
122
|
"@abtnode/types": "^1.16.40",
|
|
123
123
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
124
|
-
"@blocklet/payment-types": "1.18.
|
|
124
|
+
"@blocklet/payment-types": "1.18.21",
|
|
125
125
|
"@types/cookie-parser": "^1.4.7",
|
|
126
126
|
"@types/cors": "^2.8.17",
|
|
127
127
|
"@types/debug": "^4.1.12",
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
"vite": "^5.3.5",
|
|
152
152
|
"vite-node": "^2.0.4",
|
|
153
153
|
"vite-plugin-babel-import": "^2.0.5",
|
|
154
|
-
"vite-plugin-blocklet": "^0.9.
|
|
154
|
+
"vite-plugin-blocklet": "^0.9.27",
|
|
155
155
|
"vite-plugin-node-polyfills": "^0.21.0",
|
|
156
156
|
"vite-plugin-svgr": "^4.2.0",
|
|
157
157
|
"vite-tsconfig-paths": "^4.3.2",
|
|
@@ -167,5 +167,5 @@
|
|
|
167
167
|
"parser": "typescript"
|
|
168
168
|
}
|
|
169
169
|
},
|
|
170
|
-
"gitHead": "
|
|
170
|
+
"gitHead": "cf08902482dded20ee91402ed6d4678383965daf"
|
|
171
171
|
}
|
|
@@ -3,10 +3,19 @@ import { PaymentProvider } from '@blocklet/payment-react';
|
|
|
3
3
|
import { UserCenter } from '@blocklet/ui-react';
|
|
4
4
|
import { useEffect } from 'react';
|
|
5
5
|
|
|
6
|
+
import { useSearchParams } from 'react-router-dom';
|
|
6
7
|
import { useSessionContext } from '../../contexts/session';
|
|
7
8
|
|
|
8
9
|
export default function UserLayout(props: any) {
|
|
9
10
|
const { session, connectApi, events } = useSessionContext();
|
|
11
|
+
const [params] = useSearchParams();
|
|
12
|
+
const embed = params.get('embed') || sessionStorage.getItem('embed');
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (embed) {
|
|
16
|
+
sessionStorage.setItem('embed', embed);
|
|
17
|
+
}
|
|
18
|
+
}, [embed]);
|
|
10
19
|
|
|
11
20
|
useEffect(() => {
|
|
12
21
|
events.once('logout', () => {
|
|
@@ -28,6 +37,7 @@ export default function UserLayout(props: any) {
|
|
|
28
37
|
currentTab={`${window.blocklet.prefix}customer`}
|
|
29
38
|
userDid={session.user.did}
|
|
30
39
|
hideFooter
|
|
40
|
+
embed={embed === '1'}
|
|
31
41
|
notLoginContent="undefined">
|
|
32
42
|
{props.children}
|
|
33
43
|
</UserCenter>
|
package/src/libs/dayjs.ts
CHANGED
|
@@ -54,8 +54,9 @@ export const formatSmartDuration = (
|
|
|
54
54
|
{ t, separator = ' ' }: FormatDurationOptions
|
|
55
55
|
): string => {
|
|
56
56
|
// Format single unit
|
|
57
|
-
const formatUnit = (val: number, unitType: TimeUnit): string =>
|
|
58
|
-
`${val} ${t(`common.${unitType}${val > 1 ? 's' : ''}`).toLowerCase()}`;
|
|
57
|
+
const formatUnit = (val: number, unitType: TimeUnit): string => {
|
|
58
|
+
return `${val || 0} ${t(`common.${unitType}${val > 1 ? 's' : ''}`).toLowerCase()}`;
|
|
59
|
+
};
|
|
59
60
|
|
|
60
61
|
// Convert to largest possible unit
|
|
61
62
|
const convertToLargest = (val: number, fromUnit: TimeUnit): [TimeUnit, number][] => {
|
|
@@ -129,7 +130,7 @@ export const formatSmartDuration = (
|
|
|
129
130
|
|
|
130
131
|
// Get units and filter out zero values
|
|
131
132
|
const units = convertToLargest(value, unit).filter(([, val]) => val > 0);
|
|
132
|
-
|
|
133
|
+
if (units.length === 0) return formatUnit(0, unit);
|
|
133
134
|
// Format all units
|
|
134
135
|
return units.map(([u, val]) => formatUnit(val, u)).join(separator);
|
|
135
136
|
};
|
|
@@ -35,7 +35,7 @@ import { styled } from '@mui/system';
|
|
|
35
35
|
import { useRequest, useSetState } from 'ahooks';
|
|
36
36
|
import { flatten, isEmpty } from 'lodash';
|
|
37
37
|
import { memo, useEffect, useState } from 'react';
|
|
38
|
-
import { useNavigate } from 'react-router-dom';
|
|
38
|
+
import { useNavigate, useSearchParams } from 'react-router-dom';
|
|
39
39
|
import { joinURL } from 'ufo';
|
|
40
40
|
|
|
41
41
|
import { useTransitionContext } from '../../components/progress-bar';
|
|
@@ -178,7 +178,7 @@ type CardType = 'balance' | 'spent' | 'stake' | 'refund' | 'due';
|
|
|
178
178
|
|
|
179
179
|
// 定义一个统一的卡片配置
|
|
180
180
|
const CARD_CONFIG: Record<CardType, { key: keyof typeof summaryKeyMap; alwaysShow?: boolean }> = {
|
|
181
|
-
balance: { key: 'token'
|
|
181
|
+
balance: { key: 'token' },
|
|
182
182
|
spent: { key: 'paid', alwaysShow: true },
|
|
183
183
|
stake: { key: 'stake' },
|
|
184
184
|
refund: { key: 'refund' },
|
|
@@ -194,16 +194,22 @@ const summaryKeyMap = {
|
|
|
194
194
|
due: 'due',
|
|
195
195
|
} as const;
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
const
|
|
197
|
+
const isCardVisible = (type: string, config: any, data: any, currency: any, method: any) => {
|
|
198
|
+
const summaryKey = config.key;
|
|
199
|
+
const hasSummaryValue =
|
|
200
|
+
data?.summary?.[summaryKey]?.[currency.id] && data?.summary?.[summaryKey]?.[currency.id] !== '0';
|
|
201
|
+
|
|
202
|
+
if (type === 'balance') {
|
|
203
|
+
return method?.type === 'arcblock' && (config.alwaysShow || hasSummaryValue);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return config.alwaysShow || hasSummaryValue;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const getCardVisibility = (currency: any, data: any, method: any) => {
|
|
199
210
|
return Object.entries(CARD_CONFIG).reduce(
|
|
200
211
|
(acc, [type, config]) => {
|
|
201
|
-
|
|
202
|
-
const isVisible =
|
|
203
|
-
config.alwaysShow ||
|
|
204
|
-
(data?.summary?.[summaryKey]?.[currency.id] && data?.summary?.[summaryKey]?.[currency.id] !== '0');
|
|
205
|
-
|
|
206
|
-
if (isVisible) {
|
|
212
|
+
if (isCardVisible(type, config, data, currency, method)) {
|
|
207
213
|
acc.visibleCards.push(type as CardType);
|
|
208
214
|
}
|
|
209
215
|
return acc;
|
|
@@ -213,9 +219,10 @@ const getCardVisibility = (currency: any, data: any) => {
|
|
|
213
219
|
};
|
|
214
220
|
|
|
215
221
|
// 计算最大卡片数量
|
|
216
|
-
const calculateMaxCardCount = (currencies: any[], data: any) => {
|
|
222
|
+
const calculateMaxCardCount = (currencies: any[], data: any, settings: any) => {
|
|
217
223
|
return currencies.reduce((maxCount, currency) => {
|
|
218
|
-
const
|
|
224
|
+
const method = settings?.paymentMethods?.find((m: any) => m.id === currency.payment_method_id);
|
|
225
|
+
const { visibleCards } = getCardVisibility(currency, data, method);
|
|
219
226
|
return Math.max(maxCount, visibleCards.length);
|
|
220
227
|
}, 0);
|
|
221
228
|
};
|
|
@@ -244,6 +251,8 @@ export default function CustomerHome() {
|
|
|
244
251
|
}))
|
|
245
252
|
)
|
|
246
253
|
);
|
|
254
|
+
const [params] = useSearchParams();
|
|
255
|
+
const embed = params.get('embed') || sessionStorage.getItem('embed');
|
|
247
256
|
|
|
248
257
|
const { livemode, setLivemode } = usePaymentContext();
|
|
249
258
|
const [state, setState] = useSetState({
|
|
@@ -378,7 +387,7 @@ export default function CustomerHome() {
|
|
|
378
387
|
</Box>
|
|
379
388
|
);
|
|
380
389
|
|
|
381
|
-
const maxCardCount = calculateMaxCardCount(currencies, data);
|
|
390
|
+
const maxCardCount = calculateMaxCardCount(currencies, data, settings);
|
|
382
391
|
|
|
383
392
|
const responsiveColumns = {
|
|
384
393
|
xs: Math.min(2, maxCardCount),
|
|
@@ -397,6 +406,10 @@ export default function CustomerHome() {
|
|
|
397
406
|
</Box>
|
|
398
407
|
<Stack gap={2} mt={2}>
|
|
399
408
|
{currencies.map((c) => {
|
|
409
|
+
const method = settings?.paymentMethods?.find((m) => m.id === c.payment_method_id);
|
|
410
|
+
if (method?.type !== 'arcblock') {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
400
413
|
return isSummaryEmpty(data?.summary, c.id) && c.id !== settings.baseCurrency.id ? null : (
|
|
401
414
|
<Stack
|
|
402
415
|
key={c.id}
|
|
@@ -444,12 +457,9 @@ export default function CustomerHome() {
|
|
|
444
457
|
}}>
|
|
445
458
|
{/* 使用配置渲染卡片 */}
|
|
446
459
|
{Object.entries(CARD_CONFIG).map(([type, config]) => {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
(data?.summary?.[summaryKey]?.[c.id] && data?.summary?.[summaryKey]?.[c.id] !== '0');
|
|
451
|
-
|
|
452
|
-
if (!isVisible) return null;
|
|
460
|
+
if (!isCardVisible(type, config, data, c, method)) {
|
|
461
|
+
return null;
|
|
462
|
+
}
|
|
453
463
|
|
|
454
464
|
return (
|
|
455
465
|
<Box
|
|
@@ -467,7 +477,6 @@ export default function CustomerHome() {
|
|
|
467
477
|
backgroundColor: 'var(--stroke-border-base, #EFF1F5)',
|
|
468
478
|
zIndex: 1,
|
|
469
479
|
},
|
|
470
|
-
|
|
471
480
|
'&:has(+ .placeholder)::after': {
|
|
472
481
|
display: 'none',
|
|
473
482
|
},
|
|
@@ -477,7 +486,7 @@ export default function CustomerHome() {
|
|
|
477
486
|
}}>
|
|
478
487
|
<CurrencyCard
|
|
479
488
|
label={t(`admin.customer.summary.${type}`)}
|
|
480
|
-
data={data?.summary?.[
|
|
489
|
+
data={data?.summary?.[config.key] || emptyObject}
|
|
481
490
|
currency={c}
|
|
482
491
|
type={type as CardType}
|
|
483
492
|
icon={getCardIcon(type as CardType)}
|
|
@@ -501,14 +510,14 @@ export default function CustomerHome() {
|
|
|
501
510
|
<Box className="base-card section section-invoices">
|
|
502
511
|
<Box className="section-header">
|
|
503
512
|
<Typography variant="h3">{t('customer.invoiceHistory')}</Typography>
|
|
504
|
-
{isEmpty(data?.summary?.due)
|
|
513
|
+
{isEmpty(data?.summary?.due) !== false && (
|
|
505
514
|
<Tooltip title={t('payment.customer.pastDue.warning')}>
|
|
506
515
|
<Button
|
|
507
516
|
variant="text"
|
|
508
517
|
color="error"
|
|
509
518
|
component="a"
|
|
510
519
|
size="small"
|
|
511
|
-
href={joinURL(getPrefix(), '/customer/invoice/past-due')}
|
|
520
|
+
href={joinURL(getPrefix(), '/customer/invoice/past-due', `${embed === '1' ? '?embed=1' : ''}`)}
|
|
512
521
|
target="_blank"
|
|
513
522
|
rel="noreferrer"
|
|
514
523
|
style={{ textDecoration: 'none', fontSize: 13 }}>
|
|
@@ -83,6 +83,15 @@ export default function BalanceRechargePage() {
|
|
|
83
83
|
const [loading, setLoading] = useState(true);
|
|
84
84
|
const [error, setError] = useState('');
|
|
85
85
|
const customInputRef = useRef<HTMLInputElement>(null);
|
|
86
|
+
const [unitCycle, setUnitCycle] = useState<{
|
|
87
|
+
amount: string;
|
|
88
|
+
interval: TimeUnit;
|
|
89
|
+
cycle: number;
|
|
90
|
+
}>({
|
|
91
|
+
amount: '0',
|
|
92
|
+
interval: 'month',
|
|
93
|
+
cycle: 1,
|
|
94
|
+
});
|
|
86
95
|
const [payerValue, setPayerValue] = useState<{
|
|
87
96
|
paymentAddress: string;
|
|
88
97
|
token: string;
|
|
@@ -108,9 +117,14 @@ export default function BalanceRechargePage() {
|
|
|
108
117
|
setCurrency(data.currency);
|
|
109
118
|
setRelatedSubscriptions(data.relatedSubscriptions || []);
|
|
110
119
|
|
|
111
|
-
if (data.recommendedRecharge && data.recommendedRecharge.amount) {
|
|
120
|
+
if (data.recommendedRecharge && data.recommendedRecharge.amount && data.recommendedRecharge.amount !== '0') {
|
|
112
121
|
const baseAmount = data.recommendedRecharge.amount;
|
|
113
122
|
const decimal = data.currency.decimal || 0;
|
|
123
|
+
setUnitCycle({
|
|
124
|
+
amount: parseFloat(formatBNStr(baseAmount, decimal, 6, true)).toString(),
|
|
125
|
+
interval: data.recommendedRecharge.interval as TimeUnit,
|
|
126
|
+
cycle: data.recommendedRecharge.cycle,
|
|
127
|
+
});
|
|
114
128
|
setPresetAmounts([
|
|
115
129
|
{
|
|
116
130
|
amount: Math.ceil(parseFloat(formatBNStr(baseAmount, decimal, 6, true))).toString(),
|
|
@@ -494,6 +508,19 @@ export default function BalanceRechargePage() {
|
|
|
494
508
|
}}
|
|
495
509
|
inputRef={customInputRef}
|
|
496
510
|
/>
|
|
511
|
+
{amount && Number(amount) > 0 && Number(unitCycle.amount) > 0 && !amountError && (
|
|
512
|
+
<Typography variant="body2" sx={{ color: 'text.lighter', mt: 1 }} fontSize={12}>
|
|
513
|
+
{t('common.estimatedDuration', {
|
|
514
|
+
duration: formatSmartDuration(
|
|
515
|
+
Math.floor(Number(amount) / Number(unitCycle.amount)) < 1
|
|
516
|
+
? parseFloat((Number(amount) / Number(unitCycle.amount)).toFixed(1))
|
|
517
|
+
: Math.floor(Number(amount) / Number(unitCycle.amount)),
|
|
518
|
+
unitCycle.interval,
|
|
519
|
+
{ t }
|
|
520
|
+
),
|
|
521
|
+
})}
|
|
522
|
+
</Typography>
|
|
523
|
+
)}
|
|
497
524
|
</Box>
|
|
498
525
|
)}
|
|
499
526
|
|
|
@@ -479,7 +479,11 @@ export default function RechargePage() {
|
|
|
479
479
|
/>
|
|
480
480
|
{amount && Number(amount) > 0 && Number(cycleAmount) > 0 && !amountError && (
|
|
481
481
|
<Typography variant="body2" sx={{ color: 'text.lighter', mt: '8px !important' }} fontSize={12}>
|
|
482
|
-
{formatEstimatedDuration(
|
|
482
|
+
{formatEstimatedDuration(
|
|
483
|
+
Math.floor(Number(amount) / Number(cycleAmount)) < 1
|
|
484
|
+
? parseFloat((Number(amount) / Number(cycleAmount)).toFixed(1))
|
|
485
|
+
: Math.floor(Number(amount) / Number(cycleAmount))
|
|
486
|
+
)}
|
|
483
487
|
</Typography>
|
|
484
488
|
)}
|
|
485
489
|
</Box>
|