payment-kit 1.22.30 → 1.22.32
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/queues/webhook.ts +35 -16
- package/api/src/routes/meter-events.ts +6 -3
- package/api/src/store/migrations/20251208-database-performance-indexes.ts +129 -0
- package/api/src/store/models/credit-grant.ts +10 -1
- package/api/src/store/models/meter-event.ts +54 -23
- package/blocklet.yml +1 -1
- package/package.json +23 -23
- package/src/components/webhook/attempts.tsx +42 -12
- package/src/locales/en.tsx +15 -0
- package/src/locales/zh.tsx +14 -0
|
@@ -14,6 +14,29 @@ import { WebhookAttempt } from '../store/models/webhook-attempt';
|
|
|
14
14
|
import { WebhookEndpoint } from '../store/models/webhook-endpoint';
|
|
15
15
|
import { addNotificationJob } from './notification';
|
|
16
16
|
|
|
17
|
+
async function decrementPendingWebhooks(event: Event, webhookId: string) {
|
|
18
|
+
const existingSuccessCount = await WebhookAttempt.count({
|
|
19
|
+
where: {
|
|
20
|
+
event_id: event.id,
|
|
21
|
+
webhook_endpoint_id: webhookId,
|
|
22
|
+
status: 'succeeded',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (existingSuccessCount === 0) {
|
|
27
|
+
if (event.pending_webhooks > 0) {
|
|
28
|
+
await event.decrement('pending_webhooks');
|
|
29
|
+
logger.info('pending_webhooks decremented', { eventId: event.id, newCount: event.pending_webhooks, webhookId });
|
|
30
|
+
} else {
|
|
31
|
+
logger.warn('Attempted to decrement pending_webhooks below 0', {
|
|
32
|
+
eventId: event.id,
|
|
33
|
+
currentCount: event.pending_webhooks,
|
|
34
|
+
webhookId,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
17
40
|
type WebhookJob = {
|
|
18
41
|
eventId: string;
|
|
19
42
|
webhookId: string;
|
|
@@ -80,8 +103,7 @@ export const handleWebhook = async (job: WebhookJob) => {
|
|
|
80
103
|
});
|
|
81
104
|
logger.info('WebhookAttempt created successfully', { eventId: event.id, webhookId: webhook.id });
|
|
82
105
|
|
|
83
|
-
await event.
|
|
84
|
-
logger.info('pending_webhooks decremented', { eventId: event.id, newCount: event.pending_webhooks });
|
|
106
|
+
await decrementPendingWebhooks(event, webhook.id);
|
|
85
107
|
|
|
86
108
|
logger.info('webhook attempt success', { ...job, retryCount });
|
|
87
109
|
} catch (err: any) {
|
|
@@ -113,15 +135,10 @@ export const handleWebhook = async (job: WebhookJob) => {
|
|
|
113
135
|
addWebhookJob(event.id, webhook.id, {
|
|
114
136
|
runAt: getNextRetry(retryCount, event.created_at),
|
|
115
137
|
persist: false,
|
|
116
|
-
skipExistCheck: true,
|
|
117
138
|
});
|
|
118
139
|
});
|
|
119
140
|
} else {
|
|
120
|
-
await event.
|
|
121
|
-
logger.info('Max retries reached, pending_webhooks decremented', {
|
|
122
|
-
eventId: event.id,
|
|
123
|
-
newCount: event.pending_webhooks,
|
|
124
|
-
});
|
|
141
|
+
await decrementPendingWebhooks(event, webhook.id);
|
|
125
142
|
}
|
|
126
143
|
}
|
|
127
144
|
};
|
|
@@ -188,18 +205,20 @@ export async function addWebhookJob(
|
|
|
188
205
|
options: {
|
|
189
206
|
runAt?: number;
|
|
190
207
|
persist?: boolean;
|
|
191
|
-
|
|
208
|
+
replace?: boolean;
|
|
192
209
|
} = {}
|
|
193
210
|
) {
|
|
194
|
-
const { runAt, persist = false,
|
|
211
|
+
const { runAt, persist = false, replace = true } = options;
|
|
195
212
|
const jobId = getWebhookJobId(eventId, webhookId);
|
|
213
|
+
const exist = await webhookQueue.get(jobId);
|
|
196
214
|
|
|
197
|
-
if (!
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
215
|
+
if (exist && !replace) {
|
|
216
|
+
logger.info('Webhook job already exists, skipping', { eventId, webhookId, jobId });
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (exist) {
|
|
221
|
+
await webhookQueue.delete(jobId);
|
|
203
222
|
}
|
|
204
223
|
|
|
205
224
|
webhookQueue.push({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import Joi from 'joi';
|
|
3
|
-
import { Op, QueryTypes } from 'sequelize';
|
|
3
|
+
import { Op, QueryTypes, Sequelize } from 'sequelize';
|
|
4
4
|
|
|
5
5
|
import { fromTokenToUnit } from '@ocap/util';
|
|
6
6
|
import { createListParamSchema, getOrder, getWhereFromKvQuery, MetadataSchema } from '../libs/api';
|
|
@@ -95,7 +95,10 @@ router.get('/', authMine, async (req, res) => {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
if (query.customer_id) {
|
|
98
|
-
where[
|
|
98
|
+
where[Op.and] = where[Op.and] || [];
|
|
99
|
+
where[Op.and].push(
|
|
100
|
+
Sequelize.where(Sequelize.literal("json_extract(payload, '$.customer_id')"), query.customer_id)
|
|
101
|
+
);
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
if (query.start || query.end) {
|
|
@@ -171,7 +174,7 @@ router.get('/stats', authMine, async (req, res) => {
|
|
|
171
174
|
};
|
|
172
175
|
|
|
173
176
|
if (customerId) {
|
|
174
|
-
whereClause += " AND payload
|
|
177
|
+
whereClause += " AND json_extract(payload, '$.customer_id') = :customerId";
|
|
175
178
|
replacements.customerId = customerId;
|
|
176
179
|
}
|
|
177
180
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { createIndexIfNotExists, type Migration } from '../migrate';
|
|
3
|
+
|
|
4
|
+
export const up: Migration = async ({ context }) => {
|
|
5
|
+
console.log('🚀 Starting database performance optimization...');
|
|
6
|
+
|
|
7
|
+
// meter_events
|
|
8
|
+
await createIndexIfNotExists(context, 'meter_events', ['status', 'created_at'], 'idx_meter_events_status_created');
|
|
9
|
+
await createIndexIfNotExists(
|
|
10
|
+
context,
|
|
11
|
+
'meter_events',
|
|
12
|
+
['event_name', 'livemode', 'created_at'],
|
|
13
|
+
'idx_meter_events_event_livemode_created'
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
await context.sequelize.query(
|
|
17
|
+
"CREATE INDEX IF NOT EXISTS idx_meter_events_customer_status ON meter_events(json_extract(payload, '$.customer_id'), status, livemode, created_at)"
|
|
18
|
+
);
|
|
19
|
+
await context.sequelize.query(
|
|
20
|
+
"CREATE INDEX IF NOT EXISTS idx_meter_events_subscription_status ON meter_events(json_extract(payload, '$.subscription_id'), status, livemode, created_at)"
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// credit_transactions
|
|
24
|
+
await createIndexIfNotExists(context, 'credit_transactions', ['created_at'], 'idx_credit_transactions_created');
|
|
25
|
+
await createIndexIfNotExists(
|
|
26
|
+
context,
|
|
27
|
+
'credit_transactions',
|
|
28
|
+
['customer_id', 'created_at'],
|
|
29
|
+
'idx_credit_transactions_customer_created'
|
|
30
|
+
);
|
|
31
|
+
await createIndexIfNotExists(
|
|
32
|
+
context,
|
|
33
|
+
'credit_transactions',
|
|
34
|
+
['meter_id', 'created_at'],
|
|
35
|
+
'idx_credit_transactions_meter_created'
|
|
36
|
+
);
|
|
37
|
+
await createIndexIfNotExists(
|
|
38
|
+
context,
|
|
39
|
+
'credit_transactions',
|
|
40
|
+
['meter_event_name', 'created_at'],
|
|
41
|
+
'idx_credit_transactions_event_created'
|
|
42
|
+
);
|
|
43
|
+
await createIndexIfNotExists(
|
|
44
|
+
context,
|
|
45
|
+
'credit_transactions',
|
|
46
|
+
['subscription_id', 'created_at'],
|
|
47
|
+
'idx_credit_transactions_subscription_created'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// credit_grants
|
|
51
|
+
await createIndexIfNotExists(
|
|
52
|
+
context,
|
|
53
|
+
'credit_grants',
|
|
54
|
+
['customer_id', 'currency_id', 'status'],
|
|
55
|
+
'idx_credit_grants_customer_currency_status'
|
|
56
|
+
);
|
|
57
|
+
await createIndexIfNotExists(context, 'credit_grants', ['customer_id'], 'idx_credit_grants_customer');
|
|
58
|
+
await createIndexIfNotExists(context, 'credit_grants', ['currency_id'], 'idx_credit_grants_currency');
|
|
59
|
+
await createIndexIfNotExists(context, 'credit_grants', ['status'], 'idx_credit_grants_status');
|
|
60
|
+
|
|
61
|
+
// customers
|
|
62
|
+
await createIndexIfNotExists(context, 'customers', ['did'], 'idx_customers_did');
|
|
63
|
+
|
|
64
|
+
// subscriptions
|
|
65
|
+
await createIndexIfNotExists(context, 'subscriptions', ['customer_id'], 'idx_subscriptions_customer_id');
|
|
66
|
+
|
|
67
|
+
// meters
|
|
68
|
+
await createIndexIfNotExists(context, 'meters', ['livemode', 'status'], 'idx_meters_livemode_status');
|
|
69
|
+
|
|
70
|
+
// events
|
|
71
|
+
await createIndexIfNotExists(context, 'events', ['type', 'created_at'], 'idx_events_type_created');
|
|
72
|
+
await createIndexIfNotExists(
|
|
73
|
+
context,
|
|
74
|
+
'events',
|
|
75
|
+
['object_type', 'object_id', 'created_at'],
|
|
76
|
+
'idx_events_object_type_id_created'
|
|
77
|
+
);
|
|
78
|
+
await createIndexIfNotExists(context, 'events', ['object_id', 'created_at'], 'idx_events_object_id_created');
|
|
79
|
+
|
|
80
|
+
// jobs
|
|
81
|
+
await createIndexIfNotExists(context, 'jobs', ['queue', 'will_run_at'], 'idx_jobs_queue_run_at');
|
|
82
|
+
await createIndexIfNotExists(context, 'jobs', ['cancelled', 'will_run_at'], 'idx_jobs_cancelled_run_at');
|
|
83
|
+
|
|
84
|
+
// invoices
|
|
85
|
+
await createIndexIfNotExists(context, 'invoices', ['status'], 'idx_invoices_status');
|
|
86
|
+
|
|
87
|
+
// checkout_sessions
|
|
88
|
+
await createIndexIfNotExists(
|
|
89
|
+
context,
|
|
90
|
+
'checkout_sessions',
|
|
91
|
+
['payment_link_id', 'status', 'livemode'],
|
|
92
|
+
'idx_checkout_sessions_payment_link_status_livemode'
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
console.log('✅ Performance optimization completed');
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const down: Migration = async ({ context }) => {
|
|
99
|
+
console.log('🔄 Rolling back performance optimization...');
|
|
100
|
+
|
|
101
|
+
await context.removeIndex('meter_events', 'idx_meter_events_status_created');
|
|
102
|
+
await context.removeIndex('meter_events', 'idx_meter_events_event_livemode_created');
|
|
103
|
+
await context.removeIndex('meter_events', 'idx_meter_events_customer_status');
|
|
104
|
+
await context.removeIndex('meter_events', 'idx_meter_events_subscription_status');
|
|
105
|
+
|
|
106
|
+
await context.removeIndex('credit_transactions', 'idx_credit_transactions_created');
|
|
107
|
+
await context.removeIndex('credit_transactions', 'idx_credit_transactions_customer_created');
|
|
108
|
+
await context.removeIndex('credit_transactions', 'idx_credit_transactions_meter_created');
|
|
109
|
+
await context.removeIndex('credit_transactions', 'idx_credit_transactions_event_created');
|
|
110
|
+
await context.removeIndex('credit_transactions', 'idx_credit_transactions_subscription_created');
|
|
111
|
+
|
|
112
|
+
await context.removeIndex('credit_grants', 'idx_credit_grants_customer_currency_status');
|
|
113
|
+
await context.removeIndex('credit_grants', 'idx_credit_grants_customer');
|
|
114
|
+
await context.removeIndex('credit_grants', 'idx_credit_grants_currency');
|
|
115
|
+
await context.removeIndex('credit_grants', 'idx_credit_grants_status');
|
|
116
|
+
|
|
117
|
+
await context.removeIndex('customers', 'idx_customers_did');
|
|
118
|
+
await context.removeIndex('subscriptions', 'idx_subscriptions_customer_id');
|
|
119
|
+
await context.removeIndex('meters', 'idx_meters_livemode_status');
|
|
120
|
+
await context.removeIndex('events', 'idx_events_type_created');
|
|
121
|
+
await context.removeIndex('events', 'idx_events_object_type_id_created');
|
|
122
|
+
await context.removeIndex('events', 'idx_events_object_id_created');
|
|
123
|
+
await context.removeIndex('jobs', 'idx_jobs_queue_run_at');
|
|
124
|
+
await context.removeIndex('jobs', 'idx_jobs_cancelled_run_at');
|
|
125
|
+
await context.removeIndex('invoices', 'idx_invoices_status');
|
|
126
|
+
await context.removeIndex('checkout_sessions', 'idx_checkout_sessions_payment_link_status_livemode');
|
|
127
|
+
|
|
128
|
+
console.log('✅ Rollback completed');
|
|
129
|
+
};
|
|
@@ -427,9 +427,18 @@ export class CreditGrant extends Model<InferAttributes<CreditGrant>, InferCreati
|
|
|
427
427
|
targetCurrencyIds = grantsWithCurrency.map((grant: any) => grant.currency_id);
|
|
428
428
|
}
|
|
429
429
|
|
|
430
|
+
const currencies = await PaymentCurrency.scope('withRechargeConfig').findAll({
|
|
431
|
+
where: {
|
|
432
|
+
id: {
|
|
433
|
+
[Op.in]: Array.from(new Set(targetCurrencyIds)),
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
const currencyMap = new Map(currencies.map((c) => [c.id, c]));
|
|
438
|
+
|
|
430
439
|
await Promise.all(
|
|
431
440
|
targetCurrencyIds.map(async (currencyId: string) => {
|
|
432
|
-
const paymentCurrency =
|
|
441
|
+
const paymentCurrency = currencyMap.get(currencyId);
|
|
433
442
|
if (!paymentCurrency) {
|
|
434
443
|
return null;
|
|
435
444
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
Model,
|
|
8
8
|
Op,
|
|
9
9
|
QueryTypes,
|
|
10
|
+
Sequelize,
|
|
10
11
|
WhereOptions,
|
|
11
12
|
} from 'sequelize';
|
|
12
13
|
import type { LiteralUnion } from 'type-fest';
|
|
@@ -372,27 +373,52 @@ export class MeterEvent extends Model<InferAttributes<MeterEvent>, InferCreation
|
|
|
372
373
|
const summary: GroupedBN = {};
|
|
373
374
|
const detail: GroupedStrList = {};
|
|
374
375
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
+
});
|
|
396
422
|
|
|
397
423
|
return [summary, detail, events];
|
|
398
424
|
}
|
|
@@ -419,11 +445,16 @@ export class MeterEvent extends Model<InferAttributes<MeterEvent>, InferCreation
|
|
|
419
445
|
if (status) {
|
|
420
446
|
where.status = Array.isArray(status) ? { [Op.in]: status } : status;
|
|
421
447
|
}
|
|
448
|
+
|
|
422
449
|
if (subscriptionId) {
|
|
423
|
-
where[
|
|
450
|
+
where[Op.and] = where[Op.and] || [];
|
|
451
|
+
where[Op.and].push(
|
|
452
|
+
Sequelize.where(Sequelize.literal("json_extract(payload, '$.subscription_id')"), subscriptionId)
|
|
453
|
+
);
|
|
424
454
|
}
|
|
425
455
|
if (customerId) {
|
|
426
|
-
where[
|
|
456
|
+
where[Op.and] = where[Op.and] || [];
|
|
457
|
+
where[Op.and].push(Sequelize.where(Sequelize.literal("json_extract(payload, '$.customer_id')"), customerId));
|
|
427
458
|
}
|
|
428
459
|
|
|
429
460
|
return this._getPendingAmounts(where, currencyId);
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.32",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
@@ -44,35 +44,35 @@
|
|
|
44
44
|
]
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@abtnode/cron": "^1.17.4
|
|
48
|
-
"@arcblock/did": "^1.27.
|
|
47
|
+
"@abtnode/cron": "^1.17.4",
|
|
48
|
+
"@arcblock/did": "^1.27.14",
|
|
49
49
|
"@arcblock/did-connect-react": "^3.2.11",
|
|
50
50
|
"@arcblock/did-connect-storage-nedb": "^1.8.0",
|
|
51
|
-
"@arcblock/did-util": "^1.27.
|
|
52
|
-
"@arcblock/jwt": "^1.27.
|
|
51
|
+
"@arcblock/did-util": "^1.27.14",
|
|
52
|
+
"@arcblock/jwt": "^1.27.14",
|
|
53
53
|
"@arcblock/react-hooks": "^3.2.11",
|
|
54
54
|
"@arcblock/ux": "^3.2.11",
|
|
55
|
-
"@arcblock/validator": "^1.27.
|
|
56
|
-
"@blocklet/did-space-js": "^1.2.
|
|
55
|
+
"@arcblock/validator": "^1.27.14",
|
|
56
|
+
"@blocklet/did-space-js": "^1.2.9",
|
|
57
57
|
"@blocklet/error": "^0.3.4",
|
|
58
|
-
"@blocklet/js-sdk": "^1.17.4
|
|
59
|
-
"@blocklet/logger": "^1.17.4
|
|
60
|
-
"@blocklet/payment-broker-client": "1.22.
|
|
61
|
-
"@blocklet/payment-react": "1.22.
|
|
62
|
-
"@blocklet/payment-vendor": "1.22.
|
|
63
|
-
"@blocklet/sdk": "^1.17.4
|
|
58
|
+
"@blocklet/js-sdk": "^1.17.4",
|
|
59
|
+
"@blocklet/logger": "^1.17.4",
|
|
60
|
+
"@blocklet/payment-broker-client": "1.22.32",
|
|
61
|
+
"@blocklet/payment-react": "1.22.32",
|
|
62
|
+
"@blocklet/payment-vendor": "1.22.32",
|
|
63
|
+
"@blocklet/sdk": "^1.17.4",
|
|
64
64
|
"@blocklet/ui-react": "^3.2.11",
|
|
65
|
-
"@blocklet/uploader": "^0.3.
|
|
66
|
-
"@blocklet/xss": "^0.3.
|
|
65
|
+
"@blocklet/uploader": "^0.3.14",
|
|
66
|
+
"@blocklet/xss": "^0.3.12",
|
|
67
67
|
"@mui/icons-material": "^7.1.2",
|
|
68
68
|
"@mui/lab": "7.0.0-beta.14",
|
|
69
69
|
"@mui/material": "^7.1.2",
|
|
70
70
|
"@mui/system": "^7.1.1",
|
|
71
|
-
"@ocap/asset": "^1.27.
|
|
72
|
-
"@ocap/client": "^1.27.
|
|
73
|
-
"@ocap/mcrypto": "^1.27.
|
|
74
|
-
"@ocap/util": "^1.27.
|
|
75
|
-
"@ocap/wallet": "^1.27.
|
|
71
|
+
"@ocap/asset": "^1.27.14",
|
|
72
|
+
"@ocap/client": "^1.27.14",
|
|
73
|
+
"@ocap/mcrypto": "^1.27.14",
|
|
74
|
+
"@ocap/util": "^1.27.14",
|
|
75
|
+
"@ocap/wallet": "^1.27.14",
|
|
76
76
|
"@stripe/react-stripe-js": "^2.9.0",
|
|
77
77
|
"@stripe/stripe-js": "^2.4.0",
|
|
78
78
|
"ahooks": "^3.8.5",
|
|
@@ -127,9 +127,9 @@
|
|
|
127
127
|
"web3": "^4.16.0"
|
|
128
128
|
},
|
|
129
129
|
"devDependencies": {
|
|
130
|
-
"@abtnode/types": "^1.17.4
|
|
130
|
+
"@abtnode/types": "^1.17.4",
|
|
131
131
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
132
|
-
"@blocklet/payment-types": "1.22.
|
|
132
|
+
"@blocklet/payment-types": "1.22.32",
|
|
133
133
|
"@types/cookie-parser": "^1.4.9",
|
|
134
134
|
"@types/cors": "^2.8.19",
|
|
135
135
|
"@types/debug": "^4.1.12",
|
|
@@ -176,5 +176,5 @@
|
|
|
176
176
|
"parser": "typescript"
|
|
177
177
|
}
|
|
178
178
|
},
|
|
179
|
-
"gitHead": "
|
|
179
|
+
"gitHead": "4b5eac2af9a6a10a4f971f7dcbe220e8049d0cc7"
|
|
180
180
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable react/no-unstable-nested-components */
|
|
2
2
|
import CodeBlock from '@arcblock/ux/lib/CodeBlock';
|
|
3
3
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
4
|
-
import { api, formatTime } from '@blocklet/payment-react';
|
|
4
|
+
import { api, formatTime, ConfirmDialog } from '@blocklet/payment-react';
|
|
5
5
|
import type { Paginated, TEvent, TWebhookAttemptExpanded } from '@blocklet/payment-types';
|
|
6
6
|
import { CheckCircleOutlined, ErrorOutlined, RefreshOutlined } from '@mui/icons-material';
|
|
7
7
|
import {
|
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
Stack,
|
|
19
19
|
Typography,
|
|
20
20
|
} from '@mui/material';
|
|
21
|
-
import {
|
|
21
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
22
|
+
import { useInfiniteScroll, useRequest, useSetState } from 'ahooks';
|
|
22
23
|
import React, { useEffect, useState } from 'react';
|
|
23
24
|
|
|
24
25
|
import { isEmpty } from 'lodash';
|
|
@@ -52,6 +53,7 @@ type Props = {
|
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
export default function WebhookAttempts({ event_id = '', webhook_endpoint_id = '', event = undefined }: Props) {
|
|
56
|
+
const { t } = useLocaleContext();
|
|
55
57
|
const { data, loadMore, loadingMore, loading, reload } = useInfiniteScroll<Paginated<TWebhookAttemptExpanded>>(
|
|
56
58
|
(d) => {
|
|
57
59
|
const size = 15;
|
|
@@ -69,6 +71,11 @@ export default function WebhookAttempts({ event_id = '', webhook_endpoint_id = '
|
|
|
69
71
|
const [selected, setSelected] = useState<
|
|
70
72
|
(TWebhookAttemptExpanded & { event: TEvent & { requestInfo?: RequestInfo } }) | null
|
|
71
73
|
>(null);
|
|
74
|
+
|
|
75
|
+
const [state, setState] = useSetState({
|
|
76
|
+
retryEventId: '',
|
|
77
|
+
});
|
|
78
|
+
|
|
72
79
|
const groupedAttempts = groupAttemptsByDate(attempts);
|
|
73
80
|
|
|
74
81
|
const { loading: retrying, run: retryWebhook } = useRequest(
|
|
@@ -76,11 +83,11 @@ export default function WebhookAttempts({ event_id = '', webhook_endpoint_id = '
|
|
|
76
83
|
{
|
|
77
84
|
manual: true,
|
|
78
85
|
onSuccess: (result) => {
|
|
79
|
-
Toast.success(result.message || '
|
|
86
|
+
Toast.success(result.message || t('admin.event.retryOptions.success'));
|
|
80
87
|
reload();
|
|
81
88
|
},
|
|
82
89
|
onError: (err: any) => {
|
|
83
|
-
Toast.error(err.response?.data?.error || '
|
|
90
|
+
Toast.error(err.response?.data?.error || t('admin.event.retryOptions.error'));
|
|
84
91
|
},
|
|
85
92
|
}
|
|
86
93
|
);
|
|
@@ -95,6 +102,17 @@ export default function WebhookAttempts({ event_id = '', webhook_endpoint_id = '
|
|
|
95
102
|
setSelected(attempt);
|
|
96
103
|
};
|
|
97
104
|
|
|
105
|
+
const handleRetryClick = (eventId: string) => {
|
|
106
|
+
setState({ retryEventId: eventId });
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleConfirmRetry = () => {
|
|
110
|
+
if (state.retryEventId) {
|
|
111
|
+
retryWebhook(state.retryEventId);
|
|
112
|
+
setState({ retryEventId: '' });
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
98
116
|
if (loading) {
|
|
99
117
|
return <CircularProgress />;
|
|
100
118
|
}
|
|
@@ -109,7 +127,7 @@ export default function WebhookAttempts({ event_id = '', webhook_endpoint_id = '
|
|
|
109
127
|
sx={{
|
|
110
128
|
color: 'text.secondary',
|
|
111
129
|
}}>
|
|
112
|
-
|
|
130
|
+
{t('admin.event.noAttempts')}
|
|
113
131
|
</Typography>
|
|
114
132
|
) : (
|
|
115
133
|
<>
|
|
@@ -155,7 +173,7 @@ export default function WebhookAttempts({ event_id = '', webhook_endpoint_id = '
|
|
|
155
173
|
</List>
|
|
156
174
|
{hasMore && (
|
|
157
175
|
<Button variant="text" type="button" color="inherit" onClick={loadMore} disabled={loadingMore}>
|
|
158
|
-
{loadingMore ? '
|
|
176
|
+
{loadingMore ? t('common.loading') : t('common.loadMore')}
|
|
159
177
|
</Button>
|
|
160
178
|
)}
|
|
161
179
|
{!hasMore && (
|
|
@@ -163,7 +181,7 @@ export default function WebhookAttempts({ event_id = '', webhook_endpoint_id = '
|
|
|
163
181
|
sx={{
|
|
164
182
|
color: 'text.secondary',
|
|
165
183
|
}}>
|
|
166
|
-
|
|
184
|
+
{t('common.noMoreData')}
|
|
167
185
|
</Typography>
|
|
168
186
|
)}
|
|
169
187
|
</>
|
|
@@ -182,13 +200,25 @@ export default function WebhookAttempts({ event_id = '', webhook_endpoint_id = '
|
|
|
182
200
|
variant="outlined"
|
|
183
201
|
size="small"
|
|
184
202
|
startIcon={<RefreshOutlined />}
|
|
185
|
-
onClick={() =>
|
|
203
|
+
onClick={() => handleRetryClick(selected.event_id)}
|
|
186
204
|
disabled={retrying}>
|
|
187
|
-
{retrying ? '
|
|
205
|
+
{retrying ? t('admin.event.retrying') : t('admin.event.retry')}
|
|
188
206
|
</Button>
|
|
189
207
|
</Stack>
|
|
208
|
+
{state.retryEventId && (
|
|
209
|
+
<ConfirmDialog
|
|
210
|
+
onConfirm={handleConfirmRetry}
|
|
211
|
+
onCancel={() => setState({ retryEventId: '' })}
|
|
212
|
+
title={t('admin.event.retryOptions.title')}
|
|
213
|
+
message={t('admin.event.retryOptions.confirmMessage')}
|
|
214
|
+
loading={retrying}
|
|
215
|
+
color="primary"
|
|
216
|
+
/>
|
|
217
|
+
)}
|
|
190
218
|
<Box>
|
|
191
|
-
<Typography variant="h6">
|
|
219
|
+
<Typography variant="h6">
|
|
220
|
+
{t('admin.event.response')} ({selected.response_status})
|
|
221
|
+
</Typography>
|
|
192
222
|
{/* @ts-ignore */}
|
|
193
223
|
<CodeBlock language="json">{JSON.stringify(selected.response_body, null, 2)}</CodeBlock>
|
|
194
224
|
</Box>
|
|
@@ -199,7 +229,7 @@ export default function WebhookAttempts({ event_id = '', webhook_endpoint_id = '
|
|
|
199
229
|
sx={{
|
|
200
230
|
alignItems: 'center',
|
|
201
231
|
}}>
|
|
202
|
-
<Typography variant="h6">
|
|
232
|
+
<Typography variant="h6">{t('admin.event.request')}</Typography>
|
|
203
233
|
<RequestInfoPopper
|
|
204
234
|
// @ts-ignore
|
|
205
235
|
requestInfo={selected?.event?.requestInfo}
|
|
@@ -219,7 +249,7 @@ export default function WebhookAttempts({ event_id = '', webhook_endpoint_id = '
|
|
|
219
249
|
sx={{
|
|
220
250
|
alignItems: 'center',
|
|
221
251
|
}}>
|
|
222
|
-
<Typography variant="h6">
|
|
252
|
+
<Typography variant="h6">{t('admin.event.eventData')}</Typography>
|
|
223
253
|
<RequestInfoPopper
|
|
224
254
|
// @ts-ignore
|
|
225
255
|
requestInfo={event.requestInfo}
|
package/src/locales/en.tsx
CHANGED
|
@@ -31,6 +31,8 @@ export default flat({
|
|
|
31
31
|
latinOnly:
|
|
32
32
|
'Must contain at least one letter and cannot include Chinese characters or special characters such as <, >, ", \' or \\',
|
|
33
33
|
loading: 'Loading...',
|
|
34
|
+
loadMore: 'Load more',
|
|
35
|
+
noMoreData: 'No more data',
|
|
34
36
|
rechargeTime: 'Recharge Time',
|
|
35
37
|
submit: 'Submit',
|
|
36
38
|
custom: 'Custom',
|
|
@@ -1217,6 +1219,19 @@ export default flat({
|
|
|
1217
1219
|
webhooks: 'Webhook Attempts',
|
|
1218
1220
|
type: 'Type',
|
|
1219
1221
|
pendingWebhooks: 'Pending Webhooks',
|
|
1222
|
+
noAttempts: 'No Attempt',
|
|
1223
|
+
retry: 'Retry',
|
|
1224
|
+
retrying: 'Retrying...',
|
|
1225
|
+
retryOptions: {
|
|
1226
|
+
title: 'Retry Webhook',
|
|
1227
|
+
confirmMessage:
|
|
1228
|
+
'Are you sure you want to retry this webhook? This will send another request to the webhook endpoint.',
|
|
1229
|
+
success: 'Webhook scheduled for retry',
|
|
1230
|
+
error: 'Failed to retry webhook',
|
|
1231
|
+
},
|
|
1232
|
+
response: 'Response',
|
|
1233
|
+
request: 'Request',
|
|
1234
|
+
eventData: 'Event Data',
|
|
1220
1235
|
},
|
|
1221
1236
|
invoice: {
|
|
1222
1237
|
view: 'View invoice',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -29,6 +29,8 @@ export default flat({
|
|
|
29
29
|
invalidCharacters: '无效字符',
|
|
30
30
|
latinOnly: '至少包含一个字母,并且不能包含中文字符和特殊字符如 <, >、"、’ 或 \\',
|
|
31
31
|
loading: '加载中...',
|
|
32
|
+
loadMore: '加载更多',
|
|
33
|
+
noMoreData: '没有更多数据',
|
|
32
34
|
rechargeTime: '充值时间',
|
|
33
35
|
submit: '提交',
|
|
34
36
|
custom: '自定义',
|
|
@@ -1171,6 +1173,18 @@ export default flat({
|
|
|
1171
1173
|
webhooks: '钩子调用',
|
|
1172
1174
|
type: '类型',
|
|
1173
1175
|
pendingWebhooks: '待处理的钩子',
|
|
1176
|
+
noAttempts: '无调用记录',
|
|
1177
|
+
retry: '重试',
|
|
1178
|
+
retrying: '重试中...',
|
|
1179
|
+
retryOptions: {
|
|
1180
|
+
title: '重试 Webhook',
|
|
1181
|
+
confirmMessage: '确定要重试此 webhook 吗?这将向 webhook 端点发送另一个请求。',
|
|
1182
|
+
success: 'Webhook 已安排重试',
|
|
1183
|
+
error: '重试 webhook 失败',
|
|
1184
|
+
},
|
|
1185
|
+
response: '响应',
|
|
1186
|
+
request: '请求',
|
|
1187
|
+
eventData: '事件数据',
|
|
1174
1188
|
events: {
|
|
1175
1189
|
dailyEventCount: '每日事件数量',
|
|
1176
1190
|
dailyTotalValue: '每日总使用量',
|