payment-kit 1.22.28 → 1.22.29
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/README.md +9 -0
- package/api/src/libs/env.ts +6 -0
- package/api/src/libs/notification/template/webhook-endpoint-failed.ts +140 -0
- package/api/src/locales/en.ts +10 -0
- package/api/src/locales/zh.ts +10 -0
- package/api/src/queues/notification.ts +10 -1
- package/api/src/queues/webhook.ts +60 -7
- package/blocklet.yml +7 -1
- package/package.json +23 -23
- package/src/locales/en.tsx +3 -0
- package/src/locales/zh.tsx +2 -0
- package/src/pages/admin/developers/index.tsx +2 -6
- package/src/pages/admin/settings/payment-methods/index.tsx +45 -1
package/README.md
CHANGED
|
@@ -25,11 +25,20 @@ The decentralized Stripe for the Blocklet platform.
|
|
|
25
25
|
- Copy the `BLOCKLET_DEV_APP_DID`
|
|
26
26
|
- Transfer 2 TBA from your DID Wallet to the copied address
|
|
27
27
|
|
|
28
|
+
### Configure Stripe
|
|
29
|
+
|
|
30
|
+
Before using Stripe as a payment method, you need to configure the webhook secret:
|
|
31
|
+
|
|
32
|
+
1. Set the `STRIPE_WEBHOOK_SECRET` environment variable in your Blocklet settings
|
|
33
|
+
- Add the environment variable `STRIPE_WEBHOOK_SECRET` with your Stripe webhook signing secret
|
|
34
|
+
- You can find this in your Stripe Dashboard > Developers > Webhooks > Signing Secret
|
|
35
|
+
|
|
28
36
|
### Debug Stripe
|
|
29
37
|
|
|
30
38
|
1. Install and log in following the instructions at: https://stripe.com/docs/stripe-cli
|
|
31
39
|
2. Start your local Payment Kit server and note its port
|
|
32
40
|
3. Run `stripe listen --forward-to http://127.0.0.1:8188/api/integrations/stripe/webhook --log-level=debug --latest`
|
|
41
|
+
4. Copy the webhook signing secret from the CLI output and set it as `STRIPE_WEBHOOK_SECRET` environment variable
|
|
33
42
|
|
|
34
43
|
### Test Stripe
|
|
35
44
|
|
package/api/src/libs/env.ts
CHANGED
|
@@ -19,6 +19,12 @@ export const vendorReturnScanCronTime: string = process.env.VENDOR_RETURN_SCAN_C
|
|
|
19
19
|
export const vendorTimeoutMinutes: number = process.env.VENDOR_TIMEOUT_MINUTES
|
|
20
20
|
? +process.env.VENDOR_TIMEOUT_MINUTES
|
|
21
21
|
: 10; // 默认 10 分钟超时
|
|
22
|
+
export const webhookAlertWindowMinutes: number = process.env.WEBHOOK_ALERT_WINDOW_MINUTES
|
|
23
|
+
? +process.env.WEBHOOK_ALERT_WINDOW_MINUTES
|
|
24
|
+
: 10; // webhook 连续失败告警时间窗口,默认 10 分钟
|
|
25
|
+
export const webhookAlertMinFailures: number = process.env.WEBHOOK_ALERT_MIN_FAILURES
|
|
26
|
+
? +process.env.WEBHOOK_ALERT_MIN_FAILURES
|
|
27
|
+
: 3; // webhook 触发告警的最小失败次数,默认 3 次
|
|
22
28
|
|
|
23
29
|
export const shortUrlApiKey: string = process.env.SHORT_URL_API_KEY || '';
|
|
24
30
|
export const shortUrlDomain: string = process.env.SHORT_URL_DOMAIN || 's.abtnet.io';
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { getUrl } from '@blocklet/sdk/lib/component';
|
|
2
|
+
import { withQuery } from 'ufo';
|
|
3
|
+
import { getConnectQueryParam, getOwnerDid } from '../../util';
|
|
4
|
+
import { translate } from '../../../locales';
|
|
5
|
+
import { WebhookEndpoint } from '../../../store/models';
|
|
6
|
+
import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
|
|
7
|
+
import { getUserLocale } from '../../../integrations/blocklet/notification';
|
|
8
|
+
|
|
9
|
+
export interface WebhookEndpointFailedEmailTemplateOptions {
|
|
10
|
+
webhookEndpointId: string;
|
|
11
|
+
failedCount: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface WebhookEndpointFailedEmailTemplateContext {
|
|
15
|
+
locale: string;
|
|
16
|
+
userDid: string;
|
|
17
|
+
webhookDescription: string;
|
|
18
|
+
failedCount: number;
|
|
19
|
+
webhookEndpointId: string;
|
|
20
|
+
viewWebhookLink: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// eslint-disable-next-line prettier/prettier
|
|
24
|
+
export class WebhookEndpointFailedEmailTemplate implements BaseEmailTemplate<WebhookEndpointFailedEmailTemplateContext> {
|
|
25
|
+
options: WebhookEndpointFailedEmailTemplateOptions;
|
|
26
|
+
|
|
27
|
+
constructor(options: WebhookEndpointFailedEmailTemplateOptions) {
|
|
28
|
+
this.options = options;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getContext(): Promise<WebhookEndpointFailedEmailTemplateContext> {
|
|
32
|
+
const { webhookEndpointId, failedCount } = this.options;
|
|
33
|
+
|
|
34
|
+
const webhookEndpoint = await WebhookEndpoint.findByPk(webhookEndpointId);
|
|
35
|
+
if (!webhookEndpoint) {
|
|
36
|
+
throw new Error(`WebhookEndpoint not found: ${webhookEndpointId}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const userDid = await getOwnerDid();
|
|
40
|
+
if (!userDid) {
|
|
41
|
+
throw new Error('get owner did failed');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const locale = await getUserLocale(userDid);
|
|
45
|
+
|
|
46
|
+
const viewWebhookLink = getUrl(
|
|
47
|
+
withQuery(`admin/developers/${webhookEndpointId}`, {
|
|
48
|
+
locale,
|
|
49
|
+
...getConnectQueryParam({ userDid }),
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
userDid,
|
|
55
|
+
locale,
|
|
56
|
+
webhookDescription: webhookEndpoint.description || '',
|
|
57
|
+
failedCount,
|
|
58
|
+
webhookEndpointId,
|
|
59
|
+
viewWebhookLink,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async getTemplate(): Promise<BaseEmailTemplateType> {
|
|
64
|
+
const { locale, webhookDescription, failedCount, webhookEndpointId, viewWebhookLink } = await this.getContext();
|
|
65
|
+
|
|
66
|
+
const template: BaseEmailTemplateType = {
|
|
67
|
+
title: translate('notification.webhookEndpointFailed.title', locale, {
|
|
68
|
+
failedCount,
|
|
69
|
+
}),
|
|
70
|
+
body: translate('notification.webhookEndpointFailed.body', locale, {
|
|
71
|
+
failedCount,
|
|
72
|
+
}),
|
|
73
|
+
attachments: [
|
|
74
|
+
{
|
|
75
|
+
type: 'section',
|
|
76
|
+
fields: [
|
|
77
|
+
{
|
|
78
|
+
type: 'text',
|
|
79
|
+
data: {
|
|
80
|
+
type: 'plain',
|
|
81
|
+
color: '#9397A1',
|
|
82
|
+
text: translate('notification.common.webhookEndpointId', locale),
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: 'text',
|
|
87
|
+
data: {
|
|
88
|
+
type: 'plain',
|
|
89
|
+
text: webhookEndpointId,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
webhookDescription
|
|
93
|
+
? {
|
|
94
|
+
type: 'text',
|
|
95
|
+
data: {
|
|
96
|
+
type: 'plain',
|
|
97
|
+
color: '#9397A1',
|
|
98
|
+
text: translate('notification.common.description', locale),
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
: null,
|
|
102
|
+
webhookDescription
|
|
103
|
+
? {
|
|
104
|
+
type: 'text',
|
|
105
|
+
data: {
|
|
106
|
+
type: 'plain',
|
|
107
|
+
text: webhookDescription,
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
: null,
|
|
111
|
+
{
|
|
112
|
+
type: 'text',
|
|
113
|
+
data: {
|
|
114
|
+
type: 'plain',
|
|
115
|
+
color: '#9397A1',
|
|
116
|
+
text: translate('notification.common.failedCount', locale),
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: 'text',
|
|
121
|
+
data: {
|
|
122
|
+
type: 'plain',
|
|
123
|
+
text: failedCount.toString(),
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
].filter(Boolean),
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
actions: [
|
|
130
|
+
{
|
|
131
|
+
name: translate('notification.common.viewWebhook', locale),
|
|
132
|
+
title: translate('notification.common.viewWebhook', locale),
|
|
133
|
+
link: viewWebhookLink,
|
|
134
|
+
},
|
|
135
|
+
].filter(Boolean),
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return template;
|
|
139
|
+
}
|
|
140
|
+
}
|
package/api/src/locales/en.ts
CHANGED
|
@@ -53,6 +53,11 @@ export default flat({
|
|
|
53
53
|
reloadCredits: 'Reload Credits',
|
|
54
54
|
invoiceNumber: 'Invoice Number',
|
|
55
55
|
payer: 'Payer',
|
|
56
|
+
webhookEndpointId: 'Webhook Endpoint ID',
|
|
57
|
+
webhookUrl: 'Webhook URL',
|
|
58
|
+
description: 'Description',
|
|
59
|
+
failedCount: 'Failed Count',
|
|
60
|
+
viewWebhook: 'View Webhook',
|
|
56
61
|
},
|
|
57
62
|
|
|
58
63
|
billingDiscrepancy: {
|
|
@@ -60,6 +65,11 @@ export default flat({
|
|
|
60
65
|
body: 'A billing discrepancy has been detected for {productName}. Please review your billing details.',
|
|
61
66
|
},
|
|
62
67
|
|
|
68
|
+
webhookEndpointFailed: {
|
|
69
|
+
title: 'Webhook endpoint alert: {webhookUrl}',
|
|
70
|
+
body: 'Your webhook endpoint has failed {failedCount} times consecutively. Please check the webhook details and ensure the endpoint is accessible and functioning correctly.',
|
|
71
|
+
},
|
|
72
|
+
|
|
63
73
|
sendTo: 'Sent to',
|
|
64
74
|
mintNFT: {
|
|
65
75
|
title: '{collection} NFT minted',
|
package/api/src/locales/zh.ts
CHANGED
|
@@ -53,6 +53,11 @@ export default flat({
|
|
|
53
53
|
manageCredit: '管理额度',
|
|
54
54
|
invoiceNumber: '账单编号',
|
|
55
55
|
payer: '付款方',
|
|
56
|
+
webhookEndpointId: 'Webhook 端点 ID',
|
|
57
|
+
webhookUrl: 'Webhook URL',
|
|
58
|
+
description: '描述',
|
|
59
|
+
failedCount: '失败次数',
|
|
60
|
+
viewWebhook: '查看 Webhook',
|
|
56
61
|
},
|
|
57
62
|
|
|
58
63
|
sendTo: '发送给',
|
|
@@ -71,6 +76,11 @@ export default flat({
|
|
|
71
76
|
body: '检测到 {productName} 账单金额核算不一致,请留意。',
|
|
72
77
|
},
|
|
73
78
|
|
|
79
|
+
webhookEndpointFailed: {
|
|
80
|
+
title: 'Webhook 端点警告: {webhookUrl}',
|
|
81
|
+
body: '您的 Webhook 端点已连续失败 {failedCount} 次,请查看详情并确保端点可正常访问和工作。',
|
|
82
|
+
},
|
|
83
|
+
|
|
74
84
|
meteringSubscriptionDetection: {
|
|
75
85
|
title: '[{appName}] 按量计费订阅检测',
|
|
76
86
|
body: '在 {startTimeStr} - {endTimeStr} 期间,共扫描了 {totalCount} 份按量计费的订阅,其中 {normalCount} 份为正常订阅,{abnormalCount} 份存在异常,包括 {unreportedCount} 份未上报的订阅和 {discrepantCount} 份账单核算有问题的订阅。\n\n 异常订阅:',
|
|
@@ -110,6 +110,10 @@ import {
|
|
|
110
110
|
AggregatedSubscriptionRenewedEmailTemplate,
|
|
111
111
|
AggregatedSubscriptionRenewedEmailTemplateOptions,
|
|
112
112
|
} from '../libs/notification/template/aggregated-subscription-renewed';
|
|
113
|
+
import {
|
|
114
|
+
WebhookEndpointFailedEmailTemplate,
|
|
115
|
+
WebhookEndpointFailedEmailTemplateOptions,
|
|
116
|
+
} from '../libs/notification/template/webhook-endpoint-failed';
|
|
113
117
|
import type { TJob } from '../store/models/job';
|
|
114
118
|
|
|
115
119
|
export type NotificationQueueJobOptions = any;
|
|
@@ -134,7 +138,8 @@ export type NotificationQueueJobType =
|
|
|
134
138
|
| 'customer.credit.insufficient'
|
|
135
139
|
| 'customer.credit_grant.granted'
|
|
136
140
|
| 'customer.credit.low_balance'
|
|
137
|
-
| 'customer.auto_recharge.failed'
|
|
141
|
+
| 'customer.auto_recharge.failed'
|
|
142
|
+
| 'webhook.endpoint.failed';
|
|
138
143
|
|
|
139
144
|
export type NotificationQueueJob = {
|
|
140
145
|
type: NotificationQueueJobType;
|
|
@@ -280,6 +285,10 @@ async function getNotificationTemplate(job: NotificationQueueJob): Promise<BaseE
|
|
|
280
285
|
return new CustomerAutoRechargeFailedEmailTemplate(job.options as CustomerAutoRechargeFailedEmailTemplateOptions);
|
|
281
286
|
}
|
|
282
287
|
|
|
288
|
+
if (job.type === 'webhook.endpoint.failed') {
|
|
289
|
+
return new WebhookEndpointFailedEmailTemplate(job.options as WebhookEndpointFailedEmailTemplateOptions);
|
|
290
|
+
}
|
|
291
|
+
|
|
283
292
|
throw new Error(`Unknown job type: ${job.type}`);
|
|
284
293
|
}
|
|
285
294
|
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import componentApi from '@blocklet/sdk/lib/util/component-api';
|
|
2
|
+
import { AxiosError } from 'axios';
|
|
3
|
+
import { Op } from 'sequelize';
|
|
3
4
|
|
|
4
5
|
import { wallet } from '../libs/auth';
|
|
5
6
|
import logger from '../libs/logger';
|
|
6
7
|
import createQueue from '../libs/queue';
|
|
7
8
|
import { MAX_RETRY_COUNT, getNextRetry, getWebhookJobId } from '../libs/util';
|
|
9
|
+
import { webhookAlertWindowMinutes, webhookAlertMinFailures } from '../libs/env';
|
|
8
10
|
import { Customer } from '../store/models/customer';
|
|
9
11
|
import { Event } from '../store/models/event';
|
|
10
12
|
import { PaymentCurrency } from '../store/models/payment-currency';
|
|
11
13
|
import { WebhookAttempt } from '../store/models/webhook-attempt';
|
|
12
14
|
import { WebhookEndpoint } from '../store/models/webhook-endpoint';
|
|
15
|
+
import { addNotificationJob } from './notification';
|
|
13
16
|
|
|
14
17
|
type WebhookJob = {
|
|
15
18
|
eventId: string;
|
|
@@ -55,7 +58,7 @@ export const handleWebhook = async (job: WebhookJob) => {
|
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
// verify similar to component call, but supports external urls
|
|
58
|
-
const result = await
|
|
61
|
+
const result = await componentApi.request({
|
|
59
62
|
url: webhook.url,
|
|
60
63
|
method: 'POST',
|
|
61
64
|
timeout: 60 * 1000,
|
|
@@ -63,8 +66,6 @@ export const handleWebhook = async (job: WebhookJob) => {
|
|
|
63
66
|
headers: {
|
|
64
67
|
'x-app-id': wallet.address,
|
|
65
68
|
'x-app-pk': wallet.publicKey,
|
|
66
|
-
'x-component-sig': await sign(json),
|
|
67
|
-
'x-component-did': process.env.BLOCKLET_COMPONENT_DID as string,
|
|
68
69
|
},
|
|
69
70
|
});
|
|
70
71
|
|
|
@@ -85,18 +86,28 @@ export const handleWebhook = async (job: WebhookJob) => {
|
|
|
85
86
|
logger.info('webhook attempt success', { ...job, retryCount });
|
|
86
87
|
} catch (err: any) {
|
|
87
88
|
logger.warn('webhook attempt error', { ...job, retryCount, message: err.message });
|
|
89
|
+
const errorStatus = (err as AxiosError).response?.status || 500;
|
|
90
|
+
|
|
88
91
|
await WebhookAttempt.create({
|
|
89
92
|
livemode: event.livemode,
|
|
90
93
|
event_id: event.id,
|
|
91
94
|
webhook_endpoint_id: webhook.id,
|
|
92
95
|
status: 'failed',
|
|
93
|
-
response_status:
|
|
96
|
+
response_status: errorStatus,
|
|
94
97
|
response_body: (err as AxiosError).response?.data || {},
|
|
95
98
|
retry_count: retryCount,
|
|
96
99
|
});
|
|
97
100
|
logger.info('Failed WebhookAttempt created', { eventId: event.id, webhookId: webhook.id });
|
|
98
101
|
|
|
99
|
-
|
|
102
|
+
try {
|
|
103
|
+
await checkAndNotifyWebhookFailures(webhook.id);
|
|
104
|
+
} catch (notifyError: any) {
|
|
105
|
+
logger.error('Failed to check and notify webhook failures', {
|
|
106
|
+
webhookId: webhook.id,
|
|
107
|
+
error: notifyError.message,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
100
111
|
if (retryCount < MAX_RETRY_COUNT) {
|
|
101
112
|
process.nextTick(() => {
|
|
102
113
|
webhookQueue.push({
|
|
@@ -117,6 +128,48 @@ export const handleWebhook = async (job: WebhookJob) => {
|
|
|
117
128
|
}
|
|
118
129
|
};
|
|
119
130
|
|
|
131
|
+
// Alert if webhook continuously fails within configured time window
|
|
132
|
+
async function checkAndNotifyWebhookFailures(webhookId: string) {
|
|
133
|
+
const alertWindowStart = new Date(Date.now() - webhookAlertWindowMinutes * 60 * 1000);
|
|
134
|
+
|
|
135
|
+
const recentAttempts = await WebhookAttempt.findAll({
|
|
136
|
+
where: {
|
|
137
|
+
webhook_endpoint_id: webhookId,
|
|
138
|
+
created_at: { [Op.gte]: alertWindowStart },
|
|
139
|
+
},
|
|
140
|
+
order: [['created_at', 'DESC']],
|
|
141
|
+
attributes: ['status'],
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const hasSuccessInWindow = recentAttempts.some((attempt) => attempt.status === 'succeeded');
|
|
145
|
+
if (hasSuccessInWindow) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const failedCountInWindow = recentAttempts.filter((attempt) => attempt.status === 'failed').length;
|
|
150
|
+
if (failedCountInWindow < webhookAlertMinFailures) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
addNotificationJob(
|
|
155
|
+
'webhook.endpoint.failed',
|
|
156
|
+
{
|
|
157
|
+
webhookEndpointId: webhookId,
|
|
158
|
+
failedCount: failedCountInWindow,
|
|
159
|
+
},
|
|
160
|
+
[webhookId],
|
|
161
|
+
true,
|
|
162
|
+
24 * 3600
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
logger.info('Notification job added for webhook consecutive failures', {
|
|
166
|
+
webhookId,
|
|
167
|
+
failedCountInWindow,
|
|
168
|
+
alertWindowMinutes: webhookAlertWindowMinutes,
|
|
169
|
+
minFailures: webhookAlertMinFailures,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
120
173
|
export const webhookQueue = createQueue<WebhookJob>({
|
|
121
174
|
name: 'webhook',
|
|
122
175
|
onJob: handleWebhook,
|
package/blocklet.yml
CHANGED
|
@@ -14,7 +14,7 @@ repository:
|
|
|
14
14
|
type: git
|
|
15
15
|
url: git+https://github.com/blocklet/payment-kit.git
|
|
16
16
|
specVersion: 1.2.8
|
|
17
|
-
version: 1.22.
|
|
17
|
+
version: 1.22.29
|
|
18
18
|
logo: logo.png
|
|
19
19
|
files:
|
|
20
20
|
- dist
|
|
@@ -79,6 +79,12 @@ environments:
|
|
|
79
79
|
default: s.abtnet.io
|
|
80
80
|
secure: false
|
|
81
81
|
shared: false
|
|
82
|
+
- name: STRIPE_WEBHOOK_SECRET
|
|
83
|
+
description: Stripe webhook secret
|
|
84
|
+
required: false
|
|
85
|
+
default: ''
|
|
86
|
+
secure: true
|
|
87
|
+
shared: false
|
|
82
88
|
capabilities:
|
|
83
89
|
navigation: true
|
|
84
90
|
clusterMode: false
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.29",
|
|
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,23 +44,23 @@
|
|
|
44
44
|
]
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@abtnode/cron": "^1.17.
|
|
48
|
-
"@arcblock/did": "^1.27.
|
|
47
|
+
"@abtnode/cron": "^1.17.4-beta-20251204-152224-243ff54f",
|
|
48
|
+
"@arcblock/did": "^1.27.13",
|
|
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.13",
|
|
52
|
+
"@arcblock/jwt": "^1.27.13",
|
|
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.
|
|
57
|
-
"@blocklet/error": "^0.3.
|
|
58
|
-
"@blocklet/js-sdk": "^1.17.
|
|
59
|
-
"@blocklet/logger": "^1.17.
|
|
60
|
-
"@blocklet/payment-broker-client": "1.22.
|
|
61
|
-
"@blocklet/payment-react": "1.22.
|
|
62
|
-
"@blocklet/payment-vendor": "1.22.
|
|
63
|
-
"@blocklet/sdk": "^1.17.
|
|
55
|
+
"@arcblock/validator": "^1.27.13",
|
|
56
|
+
"@blocklet/did-space-js": "^1.2.8",
|
|
57
|
+
"@blocklet/error": "^0.3.4",
|
|
58
|
+
"@blocklet/js-sdk": "^1.17.4-beta-20251204-152224-243ff54f",
|
|
59
|
+
"@blocklet/logger": "^1.17.4-beta-20251204-152224-243ff54f",
|
|
60
|
+
"@blocklet/payment-broker-client": "1.22.29",
|
|
61
|
+
"@blocklet/payment-react": "1.22.29",
|
|
62
|
+
"@blocklet/payment-vendor": "1.22.29",
|
|
63
|
+
"@blocklet/sdk": "^1.17.4-beta-20251204-152224-243ff54f",
|
|
64
64
|
"@blocklet/ui-react": "^3.2.11",
|
|
65
65
|
"@blocklet/uploader": "^0.3.13",
|
|
66
66
|
"@blocklet/xss": "^0.3.11",
|
|
@@ -68,11 +68,11 @@
|
|
|
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.13",
|
|
72
|
+
"@ocap/client": "^1.27.13",
|
|
73
|
+
"@ocap/mcrypto": "^1.27.13",
|
|
74
|
+
"@ocap/util": "^1.27.13",
|
|
75
|
+
"@ocap/wallet": "^1.27.13",
|
|
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.
|
|
130
|
+
"@abtnode/types": "^1.17.4-beta-20251204-152224-243ff54f",
|
|
131
131
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
132
|
-
"@blocklet/payment-types": "1.22.
|
|
132
|
+
"@blocklet/payment-types": "1.22.29",
|
|
133
133
|
"@types/cookie-parser": "^1.4.9",
|
|
134
134
|
"@types/cors": "^2.8.19",
|
|
135
135
|
"@types/debug": "^4.1.12",
|
|
@@ -160,7 +160,7 @@
|
|
|
160
160
|
"vite": "^7.0.0",
|
|
161
161
|
"vite-node": "^3.2.4",
|
|
162
162
|
"vite-plugin-babel-import": "^2.0.5",
|
|
163
|
-
"vite-plugin-blocklet": "^0.12.
|
|
163
|
+
"vite-plugin-blocklet": "^0.12.4",
|
|
164
164
|
"vite-plugin-node-polyfills": "^0.23.0",
|
|
165
165
|
"vite-plugin-svgr": "^4.3.0",
|
|
166
166
|
"vite-tsconfig-paths": "^5.1.4",
|
|
@@ -176,5 +176,5 @@
|
|
|
176
176
|
"parser": "typescript"
|
|
177
177
|
}
|
|
178
178
|
},
|
|
179
|
-
"gitHead": "
|
|
179
|
+
"gitHead": "8438b592c3709dc2ae5aeceb0cd883277de3d646"
|
|
180
180
|
}
|
package/src/locales/en.tsx
CHANGED
|
@@ -1119,6 +1119,9 @@ export default flat({
|
|
|
1119
1119
|
label: 'Webhook Signing Secret',
|
|
1120
1120
|
tip: 'Webhook Signing Secret, See Dashboard > Developers > Webhooks > Signing Secret',
|
|
1121
1121
|
},
|
|
1122
|
+
webhookHint:
|
|
1123
|
+
'If webhook events are not received, manually configure STRIPE_WEBHOOK_SECRET in environment variables',
|
|
1124
|
+
configureEnv: 'Go to Settings',
|
|
1122
1125
|
},
|
|
1123
1126
|
arcblock: {
|
|
1124
1127
|
chain_id: {
|
package/src/locales/zh.tsx
CHANGED
|
@@ -10,16 +10,14 @@ const EventDetail = React.lazy(() => import('./events/detail'));
|
|
|
10
10
|
const WebhookDetail = React.lazy(() => import('./webhooks/detail'));
|
|
11
11
|
|
|
12
12
|
const pages = {
|
|
13
|
-
overview: React.lazy(() => import('./overview')),
|
|
14
13
|
webhooks: React.lazy(() => import('./webhooks')),
|
|
15
14
|
events: React.lazy(() => import('./events')),
|
|
16
|
-
logs: React.lazy(() => import('./logs')),
|
|
17
15
|
};
|
|
18
16
|
|
|
19
17
|
export default function DevelopersIndex() {
|
|
20
18
|
const navigate = useNavigate();
|
|
21
19
|
const { t } = useLocaleContext();
|
|
22
|
-
const { page = '
|
|
20
|
+
const { page = 'webhooks' } = useParams();
|
|
23
21
|
const { startTransition } = useTransitionContext();
|
|
24
22
|
|
|
25
23
|
if (page.startsWith('evt_')) {
|
|
@@ -37,12 +35,10 @@ export default function DevelopersIndex() {
|
|
|
37
35
|
};
|
|
38
36
|
|
|
39
37
|
// @ts-ignore
|
|
40
|
-
const TabComponent = pages[page] || pages.
|
|
38
|
+
const TabComponent = pages[page] || pages.webhooks;
|
|
41
39
|
const tabs = [
|
|
42
|
-
{ label: t('admin.overview'), value: 'overview' },
|
|
43
40
|
{ label: t('admin.webhooks'), value: 'webhooks' },
|
|
44
41
|
{ label: t('admin.events.title'), value: 'events' },
|
|
45
|
-
{ label: t('admin.logs'), value: 'logs' },
|
|
46
42
|
];
|
|
47
43
|
|
|
48
44
|
return (
|
|
@@ -498,7 +498,51 @@ export default function PaymentMethods() {
|
|
|
498
498
|
/>
|
|
499
499
|
)}
|
|
500
500
|
|
|
501
|
-
<InfoRow
|
|
501
|
+
<InfoRow
|
|
502
|
+
label={t('admin.paymentMethod.props.confirmation')}
|
|
503
|
+
value={
|
|
504
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
505
|
+
<Typography variant="body2">{method.confirmation.type}</Typography>
|
|
506
|
+
{method.type === 'stripe' && method.confirmation.type === 'callback' && (
|
|
507
|
+
<Tooltip
|
|
508
|
+
title={
|
|
509
|
+
<Typography variant="body2" component="span">
|
|
510
|
+
{t('admin.paymentMethod.stripe.webhookHint')}{' '}
|
|
511
|
+
<Typography
|
|
512
|
+
variant="body2"
|
|
513
|
+
component="a"
|
|
514
|
+
href="/.well-known/service/admin/overview/components"
|
|
515
|
+
target="_blank"
|
|
516
|
+
rel="noopener noreferrer"
|
|
517
|
+
sx={{
|
|
518
|
+
color: 'primary.light',
|
|
519
|
+
textDecoration: 'underline',
|
|
520
|
+
'&:hover': {
|
|
521
|
+
color: 'primary.main',
|
|
522
|
+
},
|
|
523
|
+
}}>
|
|
524
|
+
{t('admin.paymentMethod.stripe.configureEnv')}
|
|
525
|
+
</Typography>
|
|
526
|
+
</Typography>
|
|
527
|
+
}
|
|
528
|
+
placement="right"
|
|
529
|
+
arrow>
|
|
530
|
+
<InfoOutlined
|
|
531
|
+
sx={{
|
|
532
|
+
fontSize: 16,
|
|
533
|
+
color: 'info.main',
|
|
534
|
+
cursor: 'help',
|
|
535
|
+
transition: 'color 0.2s',
|
|
536
|
+
'&:hover': {
|
|
537
|
+
color: 'info.dark',
|
|
538
|
+
},
|
|
539
|
+
}}
|
|
540
|
+
/>
|
|
541
|
+
</Tooltip>
|
|
542
|
+
)}
|
|
543
|
+
</Box>
|
|
544
|
+
}
|
|
545
|
+
/>
|
|
502
546
|
<InfoRow
|
|
503
547
|
label={t('admin.paymentMethod.props.recurring')}
|
|
504
548
|
value={method.features.recurring ? t('common.yes') : t('common.no')}
|