payment-kit 1.23.8 → 1.23.10
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/env.ts +7 -0
- package/api/src/queues/auto-recharge.ts +35 -0
- package/api/src/routes/auto-recharge-configs.ts +11 -0
- package/api/src/routes/checkout-sessions.ts +0 -4
- package/api/src/routes/credit-grants.ts +241 -51
- package/api/src/routes/meter-events.ts +11 -4
- package/api/tests/routes/credit-grants.spec.ts +1260 -0
- package/blocklet.yml +1 -1
- package/package.json +6 -6
package/api/src/libs/env.ts
CHANGED
|
@@ -43,6 +43,13 @@ export const sequelizeOptionsPoolIdle: number = process.env.SEQUELIZE_OPTIONS_PO
|
|
|
43
43
|
export const updateDataConcurrency: number = process.env.UPDATE_DATA_CONCURRENCY
|
|
44
44
|
? +process.env.UPDATE_DATA_CONCURRENCY
|
|
45
45
|
: 5; // 默认并发数为 5
|
|
46
|
+
|
|
47
|
+
// System-level maximum pending amount limit (in token format, e.g., "10")
|
|
48
|
+
// Default is 0 (disabled). Set PAYMENT_KIT_MAX_PENDING_AMOUNT to enable this limit.
|
|
49
|
+
export const systemMaxPendingAmount: number = process.env.PAYMENT_KIT_MAX_PENDING_AMOUNT
|
|
50
|
+
? +process.env.PAYMENT_KIT_MAX_PENDING_AMOUNT
|
|
51
|
+
: 5;
|
|
52
|
+
|
|
46
53
|
export default {
|
|
47
54
|
...env,
|
|
48
55
|
};
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
CreditGrant,
|
|
8
8
|
Customer,
|
|
9
9
|
Invoice,
|
|
10
|
+
Meter,
|
|
10
11
|
PaymentCurrency,
|
|
11
12
|
PaymentMethod,
|
|
12
13
|
Price,
|
|
@@ -51,6 +52,22 @@ export async function processAutoRecharge(job: AutoRechargeJobData) {
|
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
// Check if the associated meter is inactive
|
|
56
|
+
if (currency.type === 'credit') {
|
|
57
|
+
// Find meter by currency_id (meter.currency_id -> PaymentCurrency) or by metadata.meter_id
|
|
58
|
+
const meter = await Meter.findOne({
|
|
59
|
+
where: { currency_id: currencyId },
|
|
60
|
+
});
|
|
61
|
+
if (meter && meter.status === 'inactive') {
|
|
62
|
+
logger.info('Meter is inactive, skipping auto recharge', {
|
|
63
|
+
customerId,
|
|
64
|
+
currencyId,
|
|
65
|
+
meterId: meter.id,
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
// 1. find auto recharge config
|
|
55
72
|
const config = (await AutoRechargeConfig.findOne({
|
|
56
73
|
where: {
|
|
@@ -302,6 +319,24 @@ export async function checkAndTriggerAutoRecharge(
|
|
|
302
319
|
currencyId,
|
|
303
320
|
currentBalance,
|
|
304
321
|
});
|
|
322
|
+
|
|
323
|
+
// Check if the associated meter is inactive
|
|
324
|
+
const currency = await PaymentCurrency.findByPk(currencyId);
|
|
325
|
+
if (currency?.type === 'credit') {
|
|
326
|
+
// Find meter by currency_id (meter.currency_id -> PaymentCurrency)
|
|
327
|
+
const meter = await Meter.findOne({
|
|
328
|
+
where: { currency_id: currencyId },
|
|
329
|
+
});
|
|
330
|
+
if (meter && meter.status === 'inactive') {
|
|
331
|
+
logger.info('Meter is inactive, skipping auto recharge check', {
|
|
332
|
+
customerId: customer.id,
|
|
333
|
+
currencyId,
|
|
334
|
+
meterId: meter.id,
|
|
335
|
+
});
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
305
340
|
const config = await AutoRechargeConfig.findOne({
|
|
306
341
|
where: {
|
|
307
342
|
customer_id: customer.id,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
AutoRechargeConfig,
|
|
11
11
|
Customer,
|
|
12
12
|
EVMChainType,
|
|
13
|
+
Meter,
|
|
13
14
|
PaymentCurrency,
|
|
14
15
|
PaymentMethod,
|
|
15
16
|
Price,
|
|
@@ -339,6 +340,16 @@ router.post('/submit', async (req, res) => {
|
|
|
339
340
|
throw new CustomError(400, `Currency not found: ${value.currency_id}`);
|
|
340
341
|
}
|
|
341
342
|
|
|
343
|
+
// Check if the associated meter is active when enabling auto-recharge
|
|
344
|
+
if (configData.enabled && currency.type === 'credit') {
|
|
345
|
+
const meter = await Meter.findOne({
|
|
346
|
+
where: { currency_id: value.currency_id },
|
|
347
|
+
});
|
|
348
|
+
if (meter && meter.status === 'inactive') {
|
|
349
|
+
throw new CustomError(400, 'Cannot enable auto top-up: the associated meter is inactive');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
342
353
|
if (currency.recharge_config?.base_price_id && currency.recharge_config?.base_price_id !== value.price_id) {
|
|
343
354
|
throw new CustomError(400, 'Price is not the base price');
|
|
344
355
|
}
|
|
@@ -1555,8 +1555,6 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
1555
1555
|
});
|
|
1556
1556
|
|
|
1557
1557
|
try {
|
|
1558
|
-
// eslint-disable-next-line no-console
|
|
1559
|
-
console.time('updateUserAddress');
|
|
1560
1558
|
await blocklet.updateUserAddress(
|
|
1561
1559
|
{
|
|
1562
1560
|
did: customer.did,
|
|
@@ -1570,8 +1568,6 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
1570
1568
|
},
|
|
1571
1569
|
}
|
|
1572
1570
|
);
|
|
1573
|
-
// eslint-disable-next-line no-console
|
|
1574
|
-
console.timeEnd('updateUserAddress');
|
|
1575
1571
|
logger.info('updateUserAddress success', {
|
|
1576
1572
|
did: customer.did,
|
|
1577
1573
|
});
|
|
@@ -2,7 +2,7 @@ import { Router } from 'express';
|
|
|
2
2
|
import Joi from 'joi';
|
|
3
3
|
import { BN, fromTokenToUnit } from '@ocap/util';
|
|
4
4
|
|
|
5
|
-
import { literal, OrderItem } from 'sequelize';
|
|
5
|
+
import { literal, OrderItem, fn, col, Op } from 'sequelize';
|
|
6
6
|
import pick from 'lodash/pick';
|
|
7
7
|
import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
|
|
8
8
|
import logger from '../libs/logger';
|
|
@@ -25,6 +25,8 @@ import { blocklet } from '../libs/auth';
|
|
|
25
25
|
import { formatMetadata } from '../libs/util';
|
|
26
26
|
import { getPriceUintAmountByCurrency } from '../libs/price';
|
|
27
27
|
import { checkTokenBalance } from '../libs/payment';
|
|
28
|
+
import { trimDecimals } from '../libs/math-utils';
|
|
29
|
+
import { systemMaxPendingAmount } from '../libs/env';
|
|
28
30
|
|
|
29
31
|
const router = Router();
|
|
30
32
|
const auth = authenticate<CreditGrant>({ component: true, roles: ['owner', 'admin'] });
|
|
@@ -163,10 +165,97 @@ router.get('/summary', authMine, async (req, res) => {
|
|
|
163
165
|
}
|
|
164
166
|
});
|
|
165
167
|
|
|
168
|
+
const holdersSchema = Joi.object({
|
|
169
|
+
currency_id: Joi.string().required(),
|
|
170
|
+
page: Joi.number().integer().min(1).default(1),
|
|
171
|
+
pageSize: Joi.number().integer().min(0).optional(), // 0 or undefined = return all
|
|
172
|
+
livemode: Joi.boolean().optional(),
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Get all holders (customers with balance) for a specific credit currency
|
|
176
|
+
router.get('/holders', auth, async (req, res) => {
|
|
177
|
+
try {
|
|
178
|
+
const { error, value } = holdersSchema.validate(req.query, { stripUnknown: true });
|
|
179
|
+
if (error) {
|
|
180
|
+
return res.status(400).json({ error: error.message });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const { currency_id: currencyId, page, pageSize, livemode } = value;
|
|
184
|
+
|
|
185
|
+
const currency = await PaymentCurrency.findByPk(currencyId);
|
|
186
|
+
if (!currency) {
|
|
187
|
+
return res.status(404).json({ error: `PaymentCurrency ${currencyId} not found` });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (currency.type !== 'credit') {
|
|
191
|
+
return res.status(400).json({ error: 'Currency must be of type credit' });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Build where clause for credit grants
|
|
195
|
+
const grantWhere: any = {
|
|
196
|
+
currency_id: currencyId,
|
|
197
|
+
status: { [Op.in]: ['granted', 'pending'] }, // Only active grants
|
|
198
|
+
};
|
|
199
|
+
if (typeof livemode === 'boolean') {
|
|
200
|
+
grantWhere.livemode = livemode;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Use database aggregation - only customer_id, no JOIN needed
|
|
204
|
+
const aggregatedData = (await CreditGrant.findAll({
|
|
205
|
+
where: grantWhere,
|
|
206
|
+
attributes: [
|
|
207
|
+
'customer_id',
|
|
208
|
+
[fn('COUNT', col('id')), 'grantCount'],
|
|
209
|
+
[fn('SUM', literal('CAST(remaining_amount AS DECIMAL(40,0))')), 'totalBalance'],
|
|
210
|
+
],
|
|
211
|
+
group: ['customer_id'], // Only group by customer_id - much faster!
|
|
212
|
+
order: [[literal('totalBalance'), 'DESC']],
|
|
213
|
+
raw: true,
|
|
214
|
+
})) as any[];
|
|
215
|
+
|
|
216
|
+
const totalCount = aggregatedData.length;
|
|
217
|
+
|
|
218
|
+
// Paginate (pageSize = 0 or undefined means return all)
|
|
219
|
+
const shouldPaginate = pageSize && pageSize > 0;
|
|
220
|
+
const paginatedData = shouldPaginate
|
|
221
|
+
? aggregatedData.slice((page - 1) * pageSize, page * pageSize)
|
|
222
|
+
: aggregatedData;
|
|
223
|
+
|
|
224
|
+
const holders = paginatedData.map((item) => ({
|
|
225
|
+
customer_id: item.customer_id,
|
|
226
|
+
balance: (item.totalBalance || '0').toString(),
|
|
227
|
+
grantCount: parseInt(item.grantCount || '0', 10),
|
|
228
|
+
}));
|
|
229
|
+
|
|
230
|
+
return res.json({
|
|
231
|
+
holders,
|
|
232
|
+
currency: {
|
|
233
|
+
id: currency.id,
|
|
234
|
+
name: currency.name,
|
|
235
|
+
symbol: currency.symbol,
|
|
236
|
+
decimal: currency.decimal,
|
|
237
|
+
},
|
|
238
|
+
paging: {
|
|
239
|
+
page: shouldPaginate ? page : 1,
|
|
240
|
+
pageSize: shouldPaginate ? pageSize : totalCount,
|
|
241
|
+
total: totalCount,
|
|
242
|
+
totalPages: shouldPaginate ? Math.ceil(totalCount / pageSize) : 1,
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
} catch (err: any) {
|
|
246
|
+
logger.error('Error getting credit holders', { error: err.message });
|
|
247
|
+
return res.status(400).json({ error: err.message });
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
166
251
|
const checkAutoRechargeSchema = Joi.object({
|
|
167
252
|
customer_id: Joi.string().required(),
|
|
168
253
|
currency_id: Joi.string().required(),
|
|
169
254
|
pending_amount: Joi.string().optional(),
|
|
255
|
+
max_recharge_times: Joi.number().integer().min(1).max(10).optional().default(3),
|
|
256
|
+
max_pending_amount: Joi.string()
|
|
257
|
+
.pattern(/^\d+(\.\d+)?$/)
|
|
258
|
+
.optional(),
|
|
170
259
|
});
|
|
171
260
|
|
|
172
261
|
router.get('/verify-availability', authMine, async (req, res) => {
|
|
@@ -255,6 +344,7 @@ router.get('/verify-availability', authMine, async (req, res) => {
|
|
|
255
344
|
});
|
|
256
345
|
}
|
|
257
346
|
|
|
347
|
+
// totalAmount: payment amount required for one auto recharge (priceAmount * quantity)
|
|
258
348
|
const totalAmount = new BN(priceAmount).mul(new BN(config.quantity ?? 1));
|
|
259
349
|
|
|
260
350
|
// 4. Get pending amount if not provided
|
|
@@ -267,46 +357,45 @@ router.get('/verify-availability', authMine, async (req, res) => {
|
|
|
267
357
|
pendingAmount = pendingSummary?.[currencyId] || '0';
|
|
268
358
|
}
|
|
269
359
|
|
|
270
|
-
// 5. Check daily limit
|
|
271
360
|
const pendingAmountBN = new BN(pendingAmount);
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
reason: 'daily_limit_reached',
|
|
289
|
-
detail: 'attempt_limit_exceeded',
|
|
290
|
-
});
|
|
291
|
-
}
|
|
361
|
+
|
|
362
|
+
// 5. Check system-level maximum pending amount limit (highest priority, cannot be bypassed)
|
|
363
|
+
// systemMaxPendingAmount is configured via PAYMENT_KIT_MAX_PENDING_AMOUNT (in token format)
|
|
364
|
+
if (systemMaxPendingAmount > 0 && pendingAmountBN.gt(new BN(0))) {
|
|
365
|
+
const systemMaxPendingAmountBN = fromTokenToUnit(
|
|
366
|
+
trimDecimals(String(systemMaxPendingAmount), currency.decimal),
|
|
367
|
+
currency.decimal
|
|
368
|
+
);
|
|
369
|
+
if (pendingAmountBN.gt(systemMaxPendingAmountBN)) {
|
|
370
|
+
return res.json({
|
|
371
|
+
can_continue: false,
|
|
372
|
+
reason: 'system_pending_limit_exceeded',
|
|
373
|
+
pending_amount: pendingAmount,
|
|
374
|
+
system_max_pending_amount: systemMaxPendingAmountBN.toString(),
|
|
375
|
+
detail: 'Pending amount exceeds system maximum limit configured in Payment Kit',
|
|
376
|
+
});
|
|
292
377
|
}
|
|
378
|
+
}
|
|
293
379
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
380
|
+
// 6. Check caller-specified max pending amount limit (if provided)
|
|
381
|
+
// Note: max_pending_amount is in token format (e.g., "100"), need to convert to unit format for comparison
|
|
382
|
+
if (value.max_pending_amount && pendingAmountBN.gt(new BN(0))) {
|
|
383
|
+
const maxPendingAmountBN = fromTokenToUnit(
|
|
384
|
+
trimDecimals(value.max_pending_amount, currency.decimal),
|
|
385
|
+
currency.decimal
|
|
386
|
+
);
|
|
387
|
+
if (pendingAmountBN.gt(maxPendingAmountBN)) {
|
|
388
|
+
return res.json({
|
|
389
|
+
can_continue: false,
|
|
390
|
+
reason: 'pending_amount_exceeds_limit',
|
|
391
|
+
pending_amount: pendingAmount,
|
|
392
|
+
max_pending_amount: maxPendingAmountBN.toString(),
|
|
393
|
+
detail: 'Current pending amount exceeds the maximum allowed limit',
|
|
394
|
+
});
|
|
306
395
|
}
|
|
307
396
|
}
|
|
308
397
|
|
|
309
|
-
//
|
|
398
|
+
// 7. Get payer
|
|
310
399
|
const payer =
|
|
311
400
|
config.payment_settings?.payment_method_options?.[
|
|
312
401
|
config.paymentMethod.type as keyof typeof config.payment_settings.payment_method_options
|
|
@@ -319,29 +408,130 @@ router.get('/verify-availability', authMine, async (req, res) => {
|
|
|
319
408
|
});
|
|
320
409
|
}
|
|
321
410
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
411
|
+
let balanceResult: { sufficient: boolean; token?: { balance: string } } | null = null;
|
|
412
|
+
// 8. If user has pending amount, check if they can pay it off
|
|
413
|
+
if (pendingAmountBN.gt(new BN(0))) {
|
|
414
|
+
// Get credit amount per recharge from price metadata
|
|
415
|
+
const creditConfig = config.price.metadata?.credit_config;
|
|
416
|
+
if (!creditConfig || !creditConfig.credit_amount) {
|
|
417
|
+
return res.json({
|
|
418
|
+
can_continue: false,
|
|
419
|
+
reason: 'credit_config_not_found',
|
|
420
|
+
});
|
|
421
|
+
}
|
|
331
422
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
423
|
+
// Convert credit_amount from token to unit, then multiply by quantity
|
|
424
|
+
// creditAmountPerRecharge: credits amount obtained from one auto recharge
|
|
425
|
+
const creditAmountPerUnit = fromTokenToUnit(creditConfig.credit_amount, currency.decimal);
|
|
426
|
+
const creditAmountPerRecharge = new BN(creditAmountPerUnit).mul(new BN(config.quantity ?? 1));
|
|
427
|
+
|
|
428
|
+
// Calculate how many recharges are needed to pay off the pending amount
|
|
429
|
+
// Formula: ceil(pendingAmount / creditAmountPerRecharge)
|
|
430
|
+
const requiredRechargeTimesBN = pendingAmountBN
|
|
431
|
+
.add(creditAmountPerRecharge)
|
|
432
|
+
.sub(new BN(1))
|
|
433
|
+
.div(creditAmountPerRecharge);
|
|
434
|
+
|
|
435
|
+
// Check if required recharge times exceeds limit
|
|
436
|
+
const maxRechargeTimes = value.max_recharge_times;
|
|
437
|
+
if (requiredRechargeTimesBN.gt(new BN(maxRechargeTimes))) {
|
|
438
|
+
return res.json({
|
|
439
|
+
can_continue: false,
|
|
440
|
+
reason: 'too_many_recharges_required',
|
|
441
|
+
pending_amount: pendingAmount,
|
|
442
|
+
required_recharge_times: requiredRechargeTimesBN.toString(),
|
|
443
|
+
max_allowed_times: maxRechargeTimes,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Calculate required payment amount to pay off pending
|
|
448
|
+
// requiredPaymentAmount = totalAmount (one recharge cost) * requiredRechargeTimes
|
|
449
|
+
const requiredPaymentAmount = totalAmount.mul(requiredRechargeTimesBN);
|
|
450
|
+
|
|
451
|
+
// Check daily limit
|
|
452
|
+
const today = new Date().toISOString().split('T')[0];
|
|
453
|
+
const isNewDay = config.last_recharge_date !== today;
|
|
454
|
+
|
|
455
|
+
if (!isNewDay && config.daily_stats && config.daily_limits) {
|
|
456
|
+
const { max_attempts: maxAttemptsRaw, max_amount: maxAmountRaw } = config.daily_limits;
|
|
457
|
+
const { attempt_count: attemptCount, total_amount: totalAmountStats } = config.daily_stats;
|
|
458
|
+
|
|
459
|
+
// Check attempt limit
|
|
460
|
+
const maxAttempts = Number(maxAttemptsRaw);
|
|
461
|
+
if (maxAttempts > 0) {
|
|
462
|
+
// Safe to convert to number since requiredRechargeTimesBN is already checked to be <= 3
|
|
463
|
+
const requiredRechargeTimes = requiredRechargeTimesBN.toNumber();
|
|
464
|
+
const remainingAttempts = maxAttempts - Number(attemptCount);
|
|
465
|
+
if (requiredRechargeTimes > remainingAttempts) {
|
|
466
|
+
return res.json({
|
|
467
|
+
can_continue: false,
|
|
468
|
+
reason: 'daily_limit_reached',
|
|
469
|
+
detail: 'attempt_limit_exceeded',
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Check amount limit
|
|
475
|
+
const maxAmount = new BN(maxAmountRaw || '0');
|
|
476
|
+
if (maxAmount.gt(new BN(0))) {
|
|
477
|
+
const remainingAmount = maxAmount.sub(new BN(totalAmountStats || '0'));
|
|
478
|
+
if (requiredPaymentAmount.gt(remainingAmount)) {
|
|
479
|
+
return res.json({
|
|
480
|
+
can_continue: false,
|
|
481
|
+
reason: 'daily_limit_reached',
|
|
482
|
+
detail: 'amount_limit_exceeded',
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Check payment account balance: balance must be sufficient to pay off pending AND cover at least one more recharge
|
|
489
|
+
// This ensures user can pay off pending amount and still have balance for continued usage
|
|
490
|
+
// minimumRequiredBalance = requiredPaymentAmount (to pay off pending) + totalAmount (for one more recharge)
|
|
491
|
+
const minimumRequiredBalance = requiredPaymentAmount.add(totalAmount);
|
|
492
|
+
balanceResult = await checkTokenBalance({
|
|
493
|
+
paymentMethod: config.paymentMethod,
|
|
494
|
+
paymentCurrency: config.rechargeCurrency,
|
|
495
|
+
userDid: payer,
|
|
496
|
+
amount: minimumRequiredBalance.toString(),
|
|
497
|
+
skipUserCheck: true,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
if (!balanceResult.sufficient) {
|
|
501
|
+
return res.json({
|
|
502
|
+
can_continue: false,
|
|
503
|
+
reason: 'insufficient_balance',
|
|
504
|
+
payment_account_balance: balanceResult.token?.balance || '0',
|
|
505
|
+
pending_amount: pendingAmount,
|
|
506
|
+
required_amount: minimumRequiredBalance.toString(),
|
|
507
|
+
detail: 'balance_must_cover_pending_plus_one_recharge',
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
} else {
|
|
511
|
+
// No pending amount: check if balance can cover at least one recharge
|
|
512
|
+
balanceResult = await checkTokenBalance({
|
|
513
|
+
paymentMethod: config.paymentMethod,
|
|
514
|
+
paymentCurrency: config.rechargeCurrency,
|
|
515
|
+
userDid: payer,
|
|
516
|
+
amount: totalAmount.toString(),
|
|
517
|
+
skipUserCheck: true,
|
|
338
518
|
});
|
|
519
|
+
|
|
520
|
+
if (!balanceResult.sufficient) {
|
|
521
|
+
return res.json({
|
|
522
|
+
can_continue: false,
|
|
523
|
+
reason: 'insufficient_balance',
|
|
524
|
+
payment_account_balance: balanceResult.token?.balance || '0',
|
|
525
|
+
pending_amount: pendingAmount,
|
|
526
|
+
required_amount: totalAmount.toString(),
|
|
527
|
+
});
|
|
528
|
+
}
|
|
339
529
|
}
|
|
340
530
|
|
|
341
531
|
return res.json({
|
|
342
532
|
can_continue: true,
|
|
343
533
|
payment_account_sufficient: true,
|
|
344
|
-
payment_account_balance: balanceResult
|
|
534
|
+
payment_account_balance: balanceResult?.token?.balance || '0',
|
|
345
535
|
pending_amount: pendingAmount,
|
|
346
536
|
});
|
|
347
537
|
} catch (err: any) {
|
|
@@ -59,12 +59,14 @@ const listSchema = createListParamSchema<{
|
|
|
59
59
|
customer_id?: string;
|
|
60
60
|
start?: number;
|
|
61
61
|
end?: number;
|
|
62
|
+
status?: string;
|
|
62
63
|
}>({
|
|
63
64
|
event_name: Joi.string().empty(''),
|
|
64
65
|
meter_id: Joi.string().empty(''),
|
|
65
66
|
customer_id: Joi.string().empty(''),
|
|
66
67
|
start: Joi.number().integer().optional(),
|
|
67
68
|
end: Joi.number().integer().optional(),
|
|
69
|
+
status: Joi.string().empty(''),
|
|
68
70
|
});
|
|
69
71
|
|
|
70
72
|
const statsSchema = Joi.object({
|
|
@@ -111,6 +113,15 @@ router.get('/', authMine, async (req, res) => {
|
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
|
|
116
|
+
if (query.status) {
|
|
117
|
+
where.status = {
|
|
118
|
+
[Op.in]: query.status
|
|
119
|
+
?.split(',')
|
|
120
|
+
.map((x) => x.trim())
|
|
121
|
+
.filter(Boolean),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
114
125
|
const { rows: list, count } = await MeterEvent.findAndCountAll({
|
|
115
126
|
where,
|
|
116
127
|
order: getOrder(query, [['created_at', query.o === 'asc' ? 'ASC' : 'DESC']]),
|
|
@@ -335,11 +346,7 @@ router.get('/pending-amount', authMine, async (req, res) => {
|
|
|
335
346
|
}
|
|
336
347
|
params.customerId = customer.id;
|
|
337
348
|
}
|
|
338
|
-
// eslint-disable-next-line no-console
|
|
339
|
-
console.time('pending-amount: getPendingAmounts');
|
|
340
349
|
const [summary] = await MeterEvent.getPendingAmounts(params);
|
|
341
|
-
// eslint-disable-next-line no-console
|
|
342
|
-
console.timeEnd('pending-amount: getPendingAmounts');
|
|
343
350
|
return res.json(summary);
|
|
344
351
|
} catch (err) {
|
|
345
352
|
logger.error('Error getting meter event pending amount', err);
|