payment-kit 1.22.31 → 1.23.0
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/index.ts +4 -0
- package/api/src/integrations/arcblock/token.ts +599 -0
- package/api/src/libs/credit-grant.ts +7 -6
- package/api/src/queues/credit-consume.ts +29 -4
- package/api/src/queues/credit-grant.ts +245 -50
- package/api/src/queues/credit-reconciliation.ts +253 -0
- package/api/src/queues/refund.ts +263 -30
- package/api/src/queues/token-transfer.ts +331 -0
- package/api/src/routes/checkout-sessions.ts +1 -1
- package/api/src/routes/credit-grants.ts +27 -7
- package/api/src/routes/credit-tokens.ts +38 -0
- package/api/src/routes/index.ts +2 -0
- package/api/src/routes/meter-events.ts +1 -1
- package/api/src/routes/meters.ts +32 -10
- package/api/src/routes/payment-currencies.ts +103 -0
- package/api/src/routes/products.ts +2 -2
- package/api/src/routes/settings.ts +4 -3
- package/api/src/store/migrations/20251120-add-token-config-to-currencies.ts +20 -0
- package/api/src/store/migrations/20251204-add-chain-fields.ts +74 -0
- package/api/src/store/models/credit-grant.ts +57 -10
- package/api/src/store/models/credit-transaction.ts +18 -1
- package/api/src/store/models/meter-event.ts +48 -25
- package/api/src/store/models/payment-currency.ts +31 -4
- package/api/src/store/models/refund.ts +12 -2
- package/api/src/store/models/types.ts +48 -0
- package/api/third.d.ts +2 -0
- package/blocklet.yml +1 -1
- package/package.json +7 -6
- package/src/components/customer/credit-overview.tsx +1 -1
- package/src/components/meter/form.tsx +191 -18
- package/src/components/price/form.tsx +49 -37
- package/src/locales/en.tsx +24 -0
- package/src/locales/zh.tsx +26 -0
- package/src/pages/admin/billing/meters/create.tsx +42 -13
- package/src/pages/admin/billing/meters/detail.tsx +56 -5
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DataTypes } from 'sequelize';
|
|
2
|
+
import { safeApplyColumnChanges, type Migration } from '../migrate';
|
|
3
|
+
|
|
4
|
+
export const up: Migration = async ({ context }) => {
|
|
5
|
+
await safeApplyColumnChanges(context, {
|
|
6
|
+
payment_currencies: [
|
|
7
|
+
{
|
|
8
|
+
name: 'token_config',
|
|
9
|
+
field: {
|
|
10
|
+
type: DataTypes.JSON,
|
|
11
|
+
allowNull: true,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const down: Migration = async ({ context }) => {
|
|
19
|
+
await context.removeColumn('payment_currencies', 'token_config');
|
|
20
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { DataTypes } from 'sequelize';
|
|
2
|
+
import { createIndexIfNotExists, safeApplyColumnChanges, type Migration } from '../migrate';
|
|
3
|
+
|
|
4
|
+
export const up: Migration = async ({ context }) => {
|
|
5
|
+
// Add new columns to credit_transactions table
|
|
6
|
+
await safeApplyColumnChanges(context, {
|
|
7
|
+
credit_transactions: [
|
|
8
|
+
{
|
|
9
|
+
name: 'transfer_status',
|
|
10
|
+
field: {
|
|
11
|
+
type: DataTypes.ENUM('pending', 'completed', 'failed'),
|
|
12
|
+
allowNull: true,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'transfer_hash',
|
|
17
|
+
field: {
|
|
18
|
+
type: DataTypes.STRING(255),
|
|
19
|
+
allowNull: true,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Add new columns to credit_grants table for on-chain operations
|
|
26
|
+
await safeApplyColumnChanges(context, {
|
|
27
|
+
credit_grants: [
|
|
28
|
+
{
|
|
29
|
+
name: 'chain_status',
|
|
30
|
+
field: {
|
|
31
|
+
type: DataTypes.ENUM(
|
|
32
|
+
'mint_pending',
|
|
33
|
+
'mint_completed',
|
|
34
|
+
'mint_failed',
|
|
35
|
+
'burn_pending',
|
|
36
|
+
'burn_completed',
|
|
37
|
+
'burn_failed'
|
|
38
|
+
),
|
|
39
|
+
allowNull: true,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'chain_detail',
|
|
44
|
+
field: {
|
|
45
|
+
type: DataTypes.JSON,
|
|
46
|
+
allowNull: true,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Add indexes for efficient querying
|
|
53
|
+
await createIndexIfNotExists(
|
|
54
|
+
context,
|
|
55
|
+
'credit_transactions',
|
|
56
|
+
['transfer_status'],
|
|
57
|
+
'idx_credit_transactions_transfer_status'
|
|
58
|
+
);
|
|
59
|
+
await createIndexIfNotExists(context, 'credit_grants', ['chain_status'], 'idx_credit_grants_chain_status');
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const down: Migration = async ({ context }) => {
|
|
63
|
+
// Remove indexes
|
|
64
|
+
await context.removeIndex('credit_transactions', 'idx_credit_transactions_transfer_status');
|
|
65
|
+
await context.removeIndex('credit_grants', 'idx_credit_grants_chain_status');
|
|
66
|
+
|
|
67
|
+
// Remove columns from credit_transactions
|
|
68
|
+
await context.removeColumn('credit_transactions', 'transfer_hash');
|
|
69
|
+
await context.removeColumn('credit_transactions', 'transfer_status');
|
|
70
|
+
|
|
71
|
+
// Remove columns from credit_grants
|
|
72
|
+
await context.removeColumn('credit_grants', 'chain_detail');
|
|
73
|
+
await context.removeColumn('credit_grants', 'chain_status');
|
|
74
|
+
};
|
|
@@ -6,9 +6,10 @@ import type { LiteralUnion } from 'type-fest';
|
|
|
6
6
|
import { createEvent } from '../../libs/audit';
|
|
7
7
|
import { createIdGenerator } from '../../libs/util';
|
|
8
8
|
import dayjs from '../../libs/dayjs';
|
|
9
|
-
import { CreditGrantApplicabilityConfig } from './types';
|
|
9
|
+
import { CreditGrantApplicabilityConfig, CreditGrantChainDetail, CreditGrantChainStatus } from './types';
|
|
10
10
|
import logger from '../../libs/logger';
|
|
11
11
|
import { PaymentCurrency, TPaymentCurrency } from './payment-currency';
|
|
12
|
+
import { Meter, TMeter } from './meter';
|
|
12
13
|
|
|
13
14
|
const CREDIT_GRANT_STATUS_EVENTS = {
|
|
14
15
|
depleted: 'depleted',
|
|
@@ -18,6 +19,7 @@ const CREDIT_GRANT_STATUS_EVENTS = {
|
|
|
18
19
|
|
|
19
20
|
type CreditGrantSummary = {
|
|
20
21
|
paymentCurrency: TPaymentCurrency;
|
|
22
|
+
meter?: TMeter;
|
|
21
23
|
totalAmount: string;
|
|
22
24
|
remainingAmount: string;
|
|
23
25
|
grantCount: number;
|
|
@@ -62,6 +64,10 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
62
64
|
declare status: LiteralUnion<'pending' | 'granted' | 'depleted' | 'expired' | 'voided', string>;
|
|
63
65
|
declare remaining_amount: string; // 剩余金额
|
|
64
66
|
|
|
67
|
+
// On-chain credit token operation fields
|
|
68
|
+
declare chain_status?: CreditGrantChainStatus;
|
|
69
|
+
declare chain_detail?: CreditGrantChainDetail;
|
|
70
|
+
|
|
65
71
|
// 审计字段
|
|
66
72
|
declare created_by?: string;
|
|
67
73
|
declare updated_by?: string;
|
|
@@ -148,6 +154,21 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
148
154
|
type: DataTypes.STRING(32),
|
|
149
155
|
allowNull: false,
|
|
150
156
|
},
|
|
157
|
+
chain_status: {
|
|
158
|
+
type: DataTypes.ENUM(
|
|
159
|
+
'mint_pending',
|
|
160
|
+
'mint_completed',
|
|
161
|
+
'mint_failed',
|
|
162
|
+
'burn_pending',
|
|
163
|
+
'burn_completed',
|
|
164
|
+
'burn_failed'
|
|
165
|
+
),
|
|
166
|
+
allowNull: true,
|
|
167
|
+
},
|
|
168
|
+
chain_detail: {
|
|
169
|
+
type: DataTypes.JSON,
|
|
170
|
+
allowNull: true,
|
|
171
|
+
},
|
|
151
172
|
created_by: {
|
|
152
173
|
type: DataTypes.STRING(40),
|
|
153
174
|
allowNull: true,
|
|
@@ -267,6 +288,16 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
267
288
|
return true;
|
|
268
289
|
}
|
|
269
290
|
|
|
291
|
+
// check if Credit Grant has on-chain token (mint completed)
|
|
292
|
+
public hasOnchainToken(): boolean {
|
|
293
|
+
return CreditGrant.hasOnchainToken(this);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Static version that works with plain objects
|
|
297
|
+
public static hasOnchainToken(grant: { chain_status?: string | null }): boolean {
|
|
298
|
+
return grant.chain_status === 'mint_completed';
|
|
299
|
+
}
|
|
300
|
+
|
|
270
301
|
public static initialize(sequelize: any) {
|
|
271
302
|
this.init(this.GENESIS_ATTRIBUTES, {
|
|
272
303
|
sequelize,
|
|
@@ -282,9 +313,6 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
282
313
|
hooks: {
|
|
283
314
|
afterCreate: (model: CreditGrant, options) => {
|
|
284
315
|
createEvent('CreditGrant', 'customer.credit_grant.created', model, options).catch(console.error);
|
|
285
|
-
if (!model.effective_at || model.effective_at <= Math.floor(Date.now() / 1000)) {
|
|
286
|
-
createEvent('CreditGrant', 'customer.credit_grant.granted', model, options).catch(console.error);
|
|
287
|
-
}
|
|
288
316
|
},
|
|
289
317
|
afterUpdate: (model: CreditGrant, options) => {
|
|
290
318
|
createEvent('CreditGrant', 'customer.credit_grant.updated', model, options).catch(console.error);
|
|
@@ -404,10 +432,7 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
404
432
|
currencyId?: string[] | string;
|
|
405
433
|
priceIds?: string[];
|
|
406
434
|
}): Promise<Record<string, CreditGrantSummary>> {
|
|
407
|
-
const summary: Record<
|
|
408
|
-
string,
|
|
409
|
-
{ paymentCurrency: TPaymentCurrency; totalAmount: string; remainingAmount: string; grantCount: number }
|
|
410
|
-
> = {};
|
|
435
|
+
const summary: Record<string, CreditGrantSummary> = {};
|
|
411
436
|
|
|
412
437
|
let targetCurrencyIds: string[] = [];
|
|
413
438
|
if (searchCurrencyId) {
|
|
@@ -427,9 +452,28 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
427
452
|
targetCurrencyIds = grantsWithCurrency.map((grant: any) => grant.currency_id);
|
|
428
453
|
}
|
|
429
454
|
|
|
455
|
+
const currencies = await PaymentCurrency.scope('withRechargeConfig').findAll({
|
|
456
|
+
where: {
|
|
457
|
+
id: {
|
|
458
|
+
[Op.in]: Array.from(new Set(targetCurrencyIds)),
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
const currencyMap = new Map(currencies.map((c) => [c.id, c]));
|
|
463
|
+
|
|
464
|
+
// Query meters for credit currencies
|
|
465
|
+
const meters = await Meter.findAll({
|
|
466
|
+
where: {
|
|
467
|
+
currency_id: {
|
|
468
|
+
[Op.in]: Array.from(new Set(targetCurrencyIds)),
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
const meterMap = new Map(meters.map((m) => [m.currency_id, m]));
|
|
473
|
+
|
|
430
474
|
await Promise.all(
|
|
431
475
|
targetCurrencyIds.map(async (currencyId: string) => {
|
|
432
|
-
const paymentCurrency =
|
|
476
|
+
const paymentCurrency = currencyMap.get(currencyId);
|
|
433
477
|
if (!paymentCurrency) {
|
|
434
478
|
return null;
|
|
435
479
|
}
|
|
@@ -437,6 +481,7 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
437
481
|
return null;
|
|
438
482
|
}
|
|
439
483
|
|
|
484
|
+
const meter = meterMap.get(currencyId);
|
|
440
485
|
const availableGrants = await this.getAvailableCreditsForCustomer(customerId, currencyId, priceIds);
|
|
441
486
|
|
|
442
487
|
if (availableGrants.length > 0) {
|
|
@@ -450,8 +495,9 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
450
495
|
grantCount += 1;
|
|
451
496
|
});
|
|
452
497
|
|
|
453
|
-
const result = {
|
|
498
|
+
const result: CreditGrantSummary = {
|
|
454
499
|
paymentCurrency,
|
|
500
|
+
meter,
|
|
455
501
|
totalAmount,
|
|
456
502
|
remainingAmount,
|
|
457
503
|
grantCount,
|
|
@@ -462,6 +508,7 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
462
508
|
}
|
|
463
509
|
summary[currencyId] = {
|
|
464
510
|
paymentCurrency,
|
|
511
|
+
meter,
|
|
465
512
|
totalAmount: '0',
|
|
466
513
|
remainingAmount: '0',
|
|
467
514
|
grantCount: 0,
|
|
@@ -29,6 +29,10 @@ export class CreditTransaction extends Model<
|
|
|
29
29
|
declare description?: string;
|
|
30
30
|
declare metadata?: Record<string, any>;
|
|
31
31
|
|
|
32
|
+
// Token transfer fields
|
|
33
|
+
declare transfer_status?: 'pending' | 'completed' | 'failed' | null;
|
|
34
|
+
declare transfer_hash?: string;
|
|
35
|
+
|
|
32
36
|
declare created_at: CreationOptional<Date>;
|
|
33
37
|
declare updated_at: CreationOptional<Date>;
|
|
34
38
|
|
|
@@ -88,6 +92,14 @@ export class CreditTransaction extends Model<
|
|
|
88
92
|
type: DataTypes.JSON,
|
|
89
93
|
allowNull: true,
|
|
90
94
|
},
|
|
95
|
+
transfer_status: {
|
|
96
|
+
type: DataTypes.ENUM('pending', 'completed', 'failed'),
|
|
97
|
+
allowNull: true,
|
|
98
|
+
},
|
|
99
|
+
transfer_hash: {
|
|
100
|
+
type: DataTypes.STRING(255),
|
|
101
|
+
allowNull: true,
|
|
102
|
+
},
|
|
91
103
|
created_at: {
|
|
92
104
|
type: DataTypes.DATE,
|
|
93
105
|
defaultValue: DataTypes.NOW,
|
|
@@ -107,7 +119,12 @@ export class CreditTransaction extends Model<
|
|
|
107
119
|
tableName: 'credit_transactions',
|
|
108
120
|
createdAt: 'created_at',
|
|
109
121
|
updatedAt: 'updated_at',
|
|
110
|
-
indexes: [
|
|
122
|
+
indexes: [
|
|
123
|
+
{ fields: ['customer_id'] },
|
|
124
|
+
{ fields: ['credit_grant_id'] },
|
|
125
|
+
{ fields: ['source'] },
|
|
126
|
+
{ fields: ['transfer_status'] },
|
|
127
|
+
],
|
|
111
128
|
hooks: {
|
|
112
129
|
afterCreate: (model: CreditTransaction, options) =>
|
|
113
130
|
createEvent('CreditTransaction', 'customer.credit_transaction.created', model, options).catch(console.error),
|
|
@@ -373,27 +373,52 @@ export class MeterEvent extends Model<InferAttributes<MeterEvent>, InferCreation
|
|
|
373
373
|
const summary: GroupedBN = {};
|
|
374
374
|
const detail: GroupedStrList = {};
|
|
375
375
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
376
|
+
if (events.length === 0) {
|
|
377
|
+
return [summary, detail, events];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const eventNames = [...new Set(events.map((e) => e.event_name))];
|
|
381
|
+
const meters = await Meter.findAll({
|
|
382
|
+
where: {
|
|
383
|
+
event_name: {
|
|
384
|
+
[Op.in]: eventNames,
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
});
|
|
388
|
+
const meterMap = new Map(meters.map((m) => [m.event_name, m]));
|
|
389
|
+
|
|
390
|
+
const currencyIds = [...new Set(meters.map((m) => m.currency_id).filter((id): id is string => Boolean(id)))];
|
|
391
|
+
const currencies =
|
|
392
|
+
currencyIds.length > 0
|
|
393
|
+
? await PaymentCurrency.findAll({
|
|
394
|
+
where: {
|
|
395
|
+
id: {
|
|
396
|
+
[Op.in]: currencyIds,
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
})
|
|
400
|
+
: [];
|
|
401
|
+
const currencyMap = new Map(currencies.map((c) => [c.id, c]));
|
|
402
|
+
|
|
403
|
+
events.forEach((event) => {
|
|
404
|
+
const meter = meterMap.get(event.event_name);
|
|
405
|
+
if (!meter || !meter.currency_id) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const paymentCurrency = currencyMap.get(meter.currency_id);
|
|
409
|
+
if (!paymentCurrency) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const currencyId = meter.currency_id as string;
|
|
413
|
+
if (searchCurrencyId && searchCurrencyId !== currencyId) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (!detail[currencyId]) {
|
|
417
|
+
detail[currencyId] = [];
|
|
418
|
+
}
|
|
419
|
+
summary[currencyId] = new BN(summary[currencyId] || '0').add(new BN(event.credit_pending)).toString();
|
|
420
|
+
detail[currencyId]!.push(event.id);
|
|
421
|
+
});
|
|
397
422
|
|
|
398
423
|
return [summary, detail, events];
|
|
399
424
|
}
|
|
@@ -424,14 +449,12 @@ export class MeterEvent extends Model<InferAttributes<MeterEvent>, InferCreation
|
|
|
424
449
|
if (subscriptionId) {
|
|
425
450
|
where[Op.and] = where[Op.and] || [];
|
|
426
451
|
where[Op.and].push(
|
|
427
|
-
Sequelize.where(Sequelize.
|
|
452
|
+
Sequelize.where(Sequelize.literal("json_extract(payload, '$.subscription_id')"), subscriptionId)
|
|
428
453
|
);
|
|
429
454
|
}
|
|
430
455
|
if (customerId) {
|
|
431
456
|
where[Op.and] = where[Op.and] || [];
|
|
432
|
-
where[Op.and].push(
|
|
433
|
-
Sequelize.where(Sequelize.fn('json_extract', Sequelize.col('payload'), '$.customer_id'), customerId)
|
|
434
|
-
);
|
|
457
|
+
where[Op.and].push(Sequelize.where(Sequelize.literal("json_extract(payload, '$.customer_id')"), customerId));
|
|
435
458
|
}
|
|
436
459
|
|
|
437
460
|
return this._getPendingAmounts(where, currencyId);
|
|
@@ -46,6 +46,7 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
|
|
|
46
46
|
declare vault_config?: VaultConfig;
|
|
47
47
|
declare type: LiteralUnion<'standard' | 'credit', string>;
|
|
48
48
|
declare recharge_config?: RechargeConfig;
|
|
49
|
+
declare token_config?: Record<string, any> | null;
|
|
49
50
|
|
|
50
51
|
public static readonly GENESIS_ATTRIBUTES = {
|
|
51
52
|
id: {
|
|
@@ -144,6 +145,10 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
|
|
|
144
145
|
type: DataTypes.JSON,
|
|
145
146
|
allowNull: true,
|
|
146
147
|
},
|
|
148
|
+
token_config: {
|
|
149
|
+
type: DataTypes.JSON,
|
|
150
|
+
allowNull: true,
|
|
151
|
+
},
|
|
147
152
|
},
|
|
148
153
|
{
|
|
149
154
|
sequelize,
|
|
@@ -210,7 +215,12 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
|
|
|
210
215
|
);
|
|
211
216
|
return count > 0;
|
|
212
217
|
}
|
|
213
|
-
public static async createForMeter(
|
|
218
|
+
public static async createForMeter(
|
|
219
|
+
meter: any,
|
|
220
|
+
paymentMethodId: string,
|
|
221
|
+
tokenConfig?: Record<string, any>,
|
|
222
|
+
options?: { decimal?: number }
|
|
223
|
+
) {
|
|
214
224
|
const existingCurrency = await this.findOne({
|
|
215
225
|
where: {
|
|
216
226
|
type: 'credit',
|
|
@@ -222,6 +232,8 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
|
|
|
222
232
|
if (existingCurrency) {
|
|
223
233
|
return existingCurrency;
|
|
224
234
|
}
|
|
235
|
+
|
|
236
|
+
const decimal = options?.decimal ?? 10;
|
|
225
237
|
const currency = await this.create({
|
|
226
238
|
type: 'credit',
|
|
227
239
|
payment_method_id: paymentMethodId,
|
|
@@ -229,8 +241,8 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
|
|
|
229
241
|
description: `Credit for ${meter.unit}`,
|
|
230
242
|
symbol: meter.unit,
|
|
231
243
|
logo: getUrl('/methods/arcblock.png'), // 默认credit图标
|
|
232
|
-
decimal
|
|
233
|
-
maximum_precision:
|
|
244
|
+
decimal,
|
|
245
|
+
maximum_precision: decimal,
|
|
234
246
|
minimum_payment_amount: '1',
|
|
235
247
|
maximum_payment_amount: '100000000000',
|
|
236
248
|
active: true,
|
|
@@ -242,13 +254,28 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
|
|
|
242
254
|
meter_event_name: meter.event_name,
|
|
243
255
|
created_by_meter: true,
|
|
244
256
|
},
|
|
257
|
+
token_config: tokenConfig,
|
|
245
258
|
});
|
|
246
259
|
|
|
247
260
|
return currency;
|
|
248
261
|
}
|
|
249
262
|
|
|
250
263
|
public isCredit(): boolean {
|
|
251
|
-
return this
|
|
264
|
+
return PaymentCurrency.isCredit(this);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
public isOnChainCredit(): boolean {
|
|
268
|
+
return PaymentCurrency.isOnChainCredit(this);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Static version that works with plain objects
|
|
272
|
+
public static isCredit(currency: { type?: string | null }): boolean {
|
|
273
|
+
return currency.type === 'credit';
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Static version that works with plain objects
|
|
277
|
+
public static isOnChainCredit(currency: { type?: string | null; token_config?: any }): boolean {
|
|
278
|
+
return PaymentCurrency.isCredit(currency) && !!currency.token_config;
|
|
252
279
|
}
|
|
253
280
|
}
|
|
254
281
|
|
|
@@ -32,7 +32,11 @@ export class Refund extends Model<InferAttributes<Refund>, InferCreationAttribut
|
|
|
32
32
|
|
|
33
33
|
declare metadata?: Record<string, any>;
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// processing: refund transfer succeeded, waiting for credit burn (for onchain credit only)
|
|
36
|
+
declare status: LiteralUnion<
|
|
37
|
+
'pending' | 'requires_action' | 'processing' | 'failed' | 'canceled' | 'succeeded',
|
|
38
|
+
string
|
|
39
|
+
>;
|
|
36
40
|
declare reason?: LiteralUnion<
|
|
37
41
|
'duplicate' | 'requested_by_customer' | 'requested_by_admin' | 'fraudulent' | 'expired_uncaptured_charge',
|
|
38
42
|
string
|
|
@@ -199,7 +203,13 @@ export class Refund extends Model<InferAttributes<Refund>, InferCreationAttribut
|
|
|
199
203
|
afterCreate: (model: Refund, options) =>
|
|
200
204
|
createEvent('Refund', 'refund.created', model, options).catch(console.error),
|
|
201
205
|
afterUpdate: (model: Refund, options) =>
|
|
202
|
-
createStatusEvent(
|
|
206
|
+
createStatusEvent(
|
|
207
|
+
'Refund',
|
|
208
|
+
'refund',
|
|
209
|
+
{ canceled: 'canceled', processing: 'processing', succeeded: 'succeeded' },
|
|
210
|
+
model,
|
|
211
|
+
options
|
|
212
|
+
),
|
|
203
213
|
afterDestroy: (model: Refund, options) =>
|
|
204
214
|
createEvent('Refund', 'refund.deleted', model, options).catch(console.error),
|
|
205
215
|
},
|
|
@@ -835,3 +835,51 @@ export type StructuredSourceDataField = {
|
|
|
835
835
|
};
|
|
836
836
|
|
|
837
837
|
export type SourceData = SimpleSourceData | StructuredSourceDataField[];
|
|
838
|
+
|
|
839
|
+
// Credit Grant on-chain operation status (combined mint, burn and transfer status)
|
|
840
|
+
export type CreditGrantChainStatus =
|
|
841
|
+
| 'mint_pending'
|
|
842
|
+
| 'mint_completed'
|
|
843
|
+
| 'mint_failed'
|
|
844
|
+
| 'burn_pending'
|
|
845
|
+
| 'burn_completed'
|
|
846
|
+
| 'burn_failed'
|
|
847
|
+
| 'transfer_completed' // for expired credits transferred to system wallet
|
|
848
|
+
| 'transfer_failed';
|
|
849
|
+
|
|
850
|
+
// Credit Grant chain operation details
|
|
851
|
+
export type CreditGrantChainDetail = {
|
|
852
|
+
// Mint operation details
|
|
853
|
+
mint?: {
|
|
854
|
+
hash?: string;
|
|
855
|
+
at?: number; // timestamp
|
|
856
|
+
error?: string;
|
|
857
|
+
failed_at?: number;
|
|
858
|
+
};
|
|
859
|
+
// Burn operation details (for expiration)
|
|
860
|
+
burn?: {
|
|
861
|
+
hash?: string;
|
|
862
|
+
at?: number; // timestamp
|
|
863
|
+
amount?: string;
|
|
864
|
+
error?: string;
|
|
865
|
+
failed_at?: number;
|
|
866
|
+
};
|
|
867
|
+
// Expired transfer operation details (for expiration - transfer to system wallet)
|
|
868
|
+
expired_transfer?: {
|
|
869
|
+
hash?: string;
|
|
870
|
+
at?: number; // timestamp
|
|
871
|
+
amount?: string;
|
|
872
|
+
error?: string;
|
|
873
|
+
failed_at?: number;
|
|
874
|
+
};
|
|
875
|
+
// Refund burn operation details
|
|
876
|
+
refund?: {
|
|
877
|
+
id?: string; // refund id
|
|
878
|
+
burn_hash?: string;
|
|
879
|
+
burned_amount?: string;
|
|
880
|
+
system_retained?: string; // amount retained by system (consumed portion)
|
|
881
|
+
burn_error?: string;
|
|
882
|
+
};
|
|
883
|
+
// Voided reason
|
|
884
|
+
voided_reason?: 'refund' | 'expired' | 'manual';
|
|
885
|
+
};
|
package/api/third.d.ts
CHANGED
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
@@ -53,13 +53,14 @@
|
|
|
53
53
|
"@arcblock/react-hooks": "^3.2.11",
|
|
54
54
|
"@arcblock/ux": "^3.2.11",
|
|
55
55
|
"@arcblock/validator": "^1.27.14",
|
|
56
|
+
"@arcblock/vc": "^1.27.14",
|
|
56
57
|
"@blocklet/did-space-js": "^1.2.9",
|
|
57
58
|
"@blocklet/error": "^0.3.4",
|
|
58
59
|
"@blocklet/js-sdk": "^1.17.4",
|
|
59
60
|
"@blocklet/logger": "^1.17.4",
|
|
60
|
-
"@blocklet/payment-broker-client": "1.
|
|
61
|
-
"@blocklet/payment-react": "1.
|
|
62
|
-
"@blocklet/payment-vendor": "1.
|
|
61
|
+
"@blocklet/payment-broker-client": "1.23.0",
|
|
62
|
+
"@blocklet/payment-react": "1.23.0",
|
|
63
|
+
"@blocklet/payment-vendor": "1.23.0",
|
|
63
64
|
"@blocklet/sdk": "^1.17.4",
|
|
64
65
|
"@blocklet/ui-react": "^3.2.11",
|
|
65
66
|
"@blocklet/uploader": "^0.3.14",
|
|
@@ -129,7 +130,7 @@
|
|
|
129
130
|
"devDependencies": {
|
|
130
131
|
"@abtnode/types": "^1.17.4",
|
|
131
132
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
132
|
-
"@blocklet/payment-types": "1.
|
|
133
|
+
"@blocklet/payment-types": "1.23.0",
|
|
133
134
|
"@types/cookie-parser": "^1.4.9",
|
|
134
135
|
"@types/cors": "^2.8.19",
|
|
135
136
|
"@types/debug": "^4.1.12",
|
|
@@ -176,5 +177,5 @@
|
|
|
176
177
|
"parser": "typescript"
|
|
177
178
|
}
|
|
178
179
|
},
|
|
179
|
-
"gitHead": "
|
|
180
|
+
"gitHead": "d791ace5b095ff78f4b7328dac8a9575e6b2e265"
|
|
180
181
|
}
|
|
@@ -173,7 +173,7 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
173
173
|
pb: 2,
|
|
174
174
|
}}>
|
|
175
175
|
<Typography variant="h6" component="div">
|
|
176
|
-
{currency.name}
|
|
176
|
+
{grantData.meter?.name || currency.name} ({currency.symbol})
|
|
177
177
|
</Typography>
|
|
178
178
|
{showRecharge && (
|
|
179
179
|
<SplitButton
|