payment-kit 1.20.16 → 1.20.18

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/.env CHANGED
@@ -1 +1,2 @@
1
1
  COMPONENT_STORE_URL="https://test.store.blocklet.dev"
2
+ LOG_LEVEL=debug
package/api/src/index.ts CHANGED
@@ -37,6 +37,7 @@ import { startUploadBillingInfoListener } from './queues/space';
37
37
  import { startSubscriptionQueue } from './queues/subscription';
38
38
  import { startVendorCommissionQueue } from './queues/vendors/commission';
39
39
  import { startVendorFulfillmentQueue } from './queues/vendors/fulfillment';
40
+ import { startCoordinatedFulfillmentQueue } from './queues/vendors/fulfillment-coordinator';
40
41
  import routes from './routes';
41
42
  import autoRechargeAuthorizationHandlers from './routes/connect/auto-recharge-auth';
42
43
  import changePaymentHandlers from './routes/connect/change-payment';
@@ -131,6 +132,7 @@ export const server = app.listen(port, (err?: any) => {
131
132
  startPayoutQueue().then(() => logger.info('payout queue started'));
132
133
  startVendorCommissionQueue().then(() => logger.info('vendor commission queue started'));
133
134
  startVendorFulfillmentQueue().then(() => logger.info('vendor fulfillment queue started'));
135
+ startCoordinatedFulfillmentQueue().then(() => logger.info('coordinated fulfillment queue started'));
134
136
  startCheckoutSessionQueue().then(() => logger.info('checkoutSession queue started'));
135
137
  startNotificationQueue().then(() => logger.info('notification queue started'));
136
138
  startRefundQueue().then(() => logger.info('refund queue started'));
@@ -0,0 +1,412 @@
1
+ import { VendorAuth } from '@blocklet/payment-vendor';
2
+ import { toBase58 } from '@ocap/util';
3
+ import stableStringify from 'json-stable-stringify';
4
+ import { v4 as uuidV4 } from 'uuid';
5
+
6
+ import { ProductVendor } from '../../../store/models';
7
+ import { wallet } from '../../auth';
8
+ import logger from '../../logger';
9
+ import {
10
+ CheckOrderStatusParams,
11
+ CheckOrderStatusResult,
12
+ FulfillOrderParams,
13
+ FulfillOrderResult,
14
+ ReturnRequestParams,
15
+ ReturnRequestResult,
16
+ VendorAdapter,
17
+ VendorConfig,
18
+ } from './types';
19
+ import { formatVendorUrl } from './util';
20
+
21
+ export class DidnamesAdapter implements VendorAdapter {
22
+ private vendorConfig: VendorConfig | null = null;
23
+ private vendorKey: string;
24
+
25
+ constructor(vendorInfo: VendorConfig | string) {
26
+ if (typeof vendorInfo === 'string') {
27
+ this.vendorKey = vendorInfo;
28
+ this.vendorConfig = null;
29
+ } else {
30
+ this.vendorKey = vendorInfo.id;
31
+ this.vendorConfig = vendorInfo;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Generate random subdomain for documentation site
37
+ * Format: [prefix][separator][timestamp-suffix] or pure timestamp
38
+ * Configurable length, prefix, separator with timestamp-based uniqueness
39
+ */
40
+ private generateRandomSubdomain(
41
+ options: {
42
+ totalLength?: number; // Total subdomain length (default: 9)
43
+ prefix?: string; // Prefix (default: 'doc')
44
+ usePrefix?: boolean; // Whether to use prefix (default: true)
45
+ separator?: string; // Separator between prefix and suffix (default: '-')
46
+ } = {}
47
+ ): string {
48
+ const { totalLength = 8, prefix = 'doc', usePrefix = true, separator = '-' } = options;
49
+
50
+ if (usePrefix) {
51
+ // With prefix: 'doc' + separator + timestamp suffix
52
+ const prefixWithSeparatorLength = prefix.length + separator.length;
53
+ const suffixLength = totalLength - prefixWithSeparatorLength;
54
+ if (suffixLength <= 0) {
55
+ throw new Error(
56
+ `Total length (${totalLength}) must be greater than prefix + separator length (${prefixWithSeparatorLength})`
57
+ );
58
+ }
59
+
60
+ const timestamp = Date.now();
61
+ const timeStr = timestamp.toString(36);
62
+ let suffix = timeStr.slice(-suffixLength);
63
+
64
+ // Pad with random chars if timestamp is shorter than needed
65
+ while (suffix.length < suffixLength) {
66
+ suffix = Math.floor(Math.random() * 36).toString(36) + suffix;
67
+ }
68
+
69
+ return `${prefix}${separator}${suffix}`;
70
+ }
71
+
72
+ // Pure timestamp-based without prefix
73
+ const timestamp = Date.now();
74
+ const timeStr = timestamp.toString(36);
75
+ let result = timeStr.slice(-totalLength);
76
+
77
+ // Pad with random chars if needed
78
+ while (result.length < totalLength) {
79
+ result = Math.floor(Math.random() * 36).toString(36) + result;
80
+ }
81
+
82
+ return result;
83
+ }
84
+
85
+ /**
86
+ * Generate bindDomainCap (binding capability) for domain authorization
87
+ */
88
+ private generateBindCap({ domain, checkoutSessionId }: { domain: string; checkoutSessionId: string }): any {
89
+ const now = Math.floor(Date.now() / 1000);
90
+ const expireInMinutes = 30;
91
+
92
+ const cap = {
93
+ domain,
94
+ checkoutSessionId,
95
+ nbf: now, // not before
96
+ exp: now + expireInMinutes * 60,
97
+ nonce: uuidV4(),
98
+ };
99
+
100
+ const signature = toBase58(wallet.sign(stableStringify(cap) || ''));
101
+
102
+ return {
103
+ cap,
104
+ sig: signature,
105
+ };
106
+ }
107
+
108
+ async getVendorConfig(): Promise<VendorConfig> {
109
+ if (this.vendorConfig === null) {
110
+ this.vendorConfig = await ProductVendor.findOne({ where: { vendor_key: this.vendorKey } });
111
+ if (!this.vendorConfig) {
112
+ throw new Error(`Vendor not found: ${this.vendorKey}`);
113
+ }
114
+ }
115
+ return this.vendorConfig;
116
+ }
117
+
118
+ async fulfillOrder(params: FulfillOrderParams): Promise<FulfillOrderResult> {
119
+ logger.info('Creating didnames order with domain binding', {
120
+ checkoutSessionId: params.checkoutSessionId,
121
+ productCode: params.productCode,
122
+ customerId: params.customerId,
123
+ });
124
+
125
+ const vendorConfig = await this.getVendorConfig();
126
+
127
+ try {
128
+ const rootDomain = vendorConfig.metadata?.rootDomain;
129
+ if (!rootDomain) {
130
+ throw new Error('missing required metadata in didnames vendor: rootDomain');
131
+ }
132
+
133
+ logger.info('didnames vendor rootDomain', { rootDomain });
134
+
135
+ const subdomain = this.generateRandomSubdomain({ totalLength: 8 });
136
+ const domain = `${subdomain}.${rootDomain}`;
137
+
138
+ const { checkoutSessionId } = params;
139
+ const bindDomainCap = this.generateBindCap({
140
+ domain,
141
+ checkoutSessionId,
142
+ });
143
+
144
+ params.deliveryParams.customParams = {
145
+ ...params.deliveryParams.customParams,
146
+ years: 1,
147
+ whoisPrivacy: true,
148
+ subdomain,
149
+ rootDomain,
150
+ domain,
151
+ checkoutSessionId,
152
+ bindDomainCap,
153
+ };
154
+
155
+ const orderData = {
156
+ checkoutSessionId: params.checkoutSessionId,
157
+ description: params.description,
158
+ userInfo: params.userInfo,
159
+ deliveryParams: params.deliveryParams,
160
+ };
161
+
162
+ const url = formatVendorUrl(vendorConfig, '/api/vendor/deliveries');
163
+ logger.info('submitting domain delivery to DID Names', {
164
+ subdomain,
165
+ url,
166
+ });
167
+
168
+ const { headers, body } = VendorAuth.signRequestWithHeaders(orderData);
169
+
170
+ const response = await fetch(url, {
171
+ method: 'POST',
172
+ headers,
173
+ body,
174
+ });
175
+
176
+ if (!response.ok) {
177
+ const errorBody = await response.text();
178
+ logger.error('DID Names API error', {
179
+ url,
180
+ status: response.status,
181
+ statusText: response.statusText,
182
+ body: errorBody,
183
+ });
184
+ throw new Error(`DID Names API error: ${response.status} ${response.statusText}`);
185
+ }
186
+
187
+ const didNamesResult = await response.json();
188
+
189
+ const result: FulfillOrderResult = {
190
+ orderId: didNamesResult.orderId,
191
+ status: didNamesResult.status || 'pending',
192
+ serviceUrl: `https://${subdomain}.${rootDomain}`, // User expects to access via custom domain
193
+ estimatedTime: didNamesResult.estimatedTime || 300,
194
+ message: didNamesResult.message || 'Domain binding order created successfully',
195
+ vendorOrderId: didNamesResult.orderId,
196
+ progress: didNamesResult.progress || 0,
197
+ orderDetails: {
198
+ productCode: params.productCode,
199
+ customerId: params.customerId,
200
+ amount: params.amount,
201
+ currency: params.currency,
202
+ quantity: params.quantity,
203
+ invoiceId: params.invoiceId,
204
+ customParams: {
205
+ ...params.customParams,
206
+ subdomain,
207
+ rootDomain,
208
+ domain: didNamesResult.domain || domain,
209
+ nftDid: didNamesResult.nftDid,
210
+ sessionId: params.customParams?.checkoutSessionId,
211
+ bindDomainCap,
212
+ },
213
+ },
214
+ installationInfo: {
215
+ appId: didNamesResult.appId || `app_${subdomain.replace(/\./g, '_')}`,
216
+ appUrl: `https://${subdomain}.${rootDomain}`, // Custom domain URL
217
+ adminUrl: didNamesResult.adminUrl || `https://${subdomain}.${rootDomain}/admin`,
218
+ status: didNamesResult.installationStatus || didNamesResult.status || 'installing',
219
+ estimatedCompletionTime:
220
+ didNamesResult.estimatedCompletionTime || new Date(Date.now() + 300000).toISOString(),
221
+ },
222
+ };
223
+
224
+ logger.info('Didnames order created successfully', {
225
+ orderId: result.orderId,
226
+ status: result.status,
227
+ subdomain,
228
+ serviceUrl: result.serviceUrl,
229
+ });
230
+
231
+ return result;
232
+ } catch (error: any) {
233
+ logger.error('Failed to create didnames order', {
234
+ error,
235
+ productCode: params.productCode,
236
+ customerId: params.customerId,
237
+ });
238
+
239
+ throw error;
240
+ }
241
+ }
242
+
243
+ async requestReturn(params: ReturnRequestParams): Promise<ReturnRequestResult> {
244
+ logger.info('Requesting return for Didnames order', {
245
+ orderId: params.orderId,
246
+ reason: params.reason,
247
+ });
248
+
249
+ const vendorConfig = await this.getVendorConfig();
250
+
251
+ try {
252
+ const returnRequest = {
253
+ orderId: params.orderId,
254
+ vendorOrderId: params.vendorOrderId,
255
+ reason: params.reason,
256
+ customParams: params.customParams,
257
+ };
258
+
259
+ const { headers, body } = VendorAuth.signRequestWithHeaders(returnRequest);
260
+ const url = formatVendorUrl(vendorConfig, '/api/vendor/return');
261
+ logger.info('submitting domain return to DID Names', {
262
+ url,
263
+ });
264
+ const response = await fetch(url, {
265
+ method: 'POST',
266
+ headers,
267
+ body,
268
+ });
269
+
270
+ if (!response.ok) {
271
+ const errorBody = await response.text();
272
+ logger.error('domain return to DID Names API error', {
273
+ url,
274
+ status: response.status,
275
+ statusText: response.statusText,
276
+ body: errorBody,
277
+ });
278
+ throw new Error(`DID Names API error: ${response.status} ${response.statusText}`);
279
+ }
280
+
281
+ const didNamesResult = await response.json();
282
+
283
+ logger.info('domain return to DID Names processed', {
284
+ url,
285
+ orderId: params.orderId,
286
+ status: didNamesResult.status,
287
+ });
288
+
289
+ return {
290
+ status: didNamesResult.status || 'requested',
291
+ message: didNamesResult.message || 'Domain unbinding requested',
292
+ };
293
+ } catch (error: any) {
294
+ logger.error('Failed to process return request', {
295
+ error: error.message,
296
+ orderId: params.orderId,
297
+ });
298
+
299
+ throw error;
300
+ }
301
+ }
302
+
303
+ async checkOrderStatus(params: CheckOrderStatusParams): Promise<CheckOrderStatusResult> {
304
+ logger.info('Checking Didnames order status', {
305
+ orderId: params.orderId,
306
+ });
307
+
308
+ try {
309
+ const vendorConfig = await this.getVendorConfig();
310
+ const url = formatVendorUrl(vendorConfig, `/api/vendor/orders/${params.orderId}/status`);
311
+
312
+ const response = await fetch(url, {
313
+ method: 'GET',
314
+ headers: {
315
+ 'Content-Type': 'application/json',
316
+ },
317
+ });
318
+
319
+ if (!response.ok) {
320
+ logger.warn('DID Names order status check failed', {
321
+ orderId: params.orderId,
322
+ status: response.status,
323
+ });
324
+ return { status: 'processing' };
325
+ }
326
+
327
+ const orderStatus = await response.json();
328
+ let status: 'processing' | 'completed' | 'failed' = 'processing';
329
+
330
+ switch (orderStatus.status) {
331
+ case 'completed':
332
+ case 'active':
333
+ case 'failed':
334
+ case 'rejected':
335
+ case 'error':
336
+ status = 'failed';
337
+ break;
338
+ default:
339
+ status = 'processing';
340
+ }
341
+
342
+ logger.info('Didnames order status checked', {
343
+ orderId: params.orderId,
344
+ status,
345
+ didNamesStatus: orderStatus.status,
346
+ domain: params.domain,
347
+ });
348
+
349
+ return { status };
350
+ } catch (error: any) {
351
+ logger.error('Failed to check didnames order status', {
352
+ error: error.message,
353
+ orderId: params.orderId,
354
+ });
355
+
356
+ throw error;
357
+ }
358
+ }
359
+
360
+ async getOrder(vendor: ProductVendor, orderId: string): Promise<any> {
361
+ const url = formatVendorUrl(vendor, `/api/vendor/orders/${orderId}`);
362
+
363
+ const { headers } = VendorAuth.signRequestWithHeaders({});
364
+ const response = await fetch(url, { method: 'GET', headers });
365
+
366
+ if (!response.ok) {
367
+ logger.error('Failed to get didnames order', {
368
+ url,
369
+ orderId,
370
+ status: response.status,
371
+ statusText: response.statusText,
372
+ });
373
+ throw new Error(`Failed to get order: ${response.status} ${response.statusText}`);
374
+ }
375
+
376
+ const data = await response.json();
377
+ return data;
378
+ }
379
+
380
+ async getOrderStatus(vendor: ProductVendor, orderId: string): Promise<any> {
381
+ const url = formatVendorUrl(vendor, `/api/vendor/status/${orderId}`);
382
+
383
+ logger.info('getting didnames order status', {
384
+ url,
385
+ orderId,
386
+ });
387
+
388
+ const { headers } = VendorAuth.signRequestWithHeaders({});
389
+ const response = await fetch(url, { method: 'GET', headers });
390
+
391
+ logger.info('didnames order status response', {
392
+ url,
393
+ orderId,
394
+ status: response.status,
395
+ statusText: response.statusText,
396
+ });
397
+
398
+ if (!response.ok) {
399
+ logger.error('Failed to get didnames order status', {
400
+ url,
401
+ orderId,
402
+ status: response.status,
403
+ statusText: response.statusText,
404
+ });
405
+ throw new Error(`Failed to get order status: ${response.status} ${response.statusText}`);
406
+ }
407
+
408
+ const data = await response.json();
409
+
410
+ return data;
411
+ }
412
+ }
@@ -1,4 +1,6 @@
1
1
  import { BN } from '@ocap/util';
2
+
3
+ import { DidnamesAdapter } from './didnames-adapter';
2
4
  import { LauncherAdapter } from './launcher-adapter';
3
5
  import { VendorAdapter } from './types';
4
6
  import { ProductVendor } from '../../../store/models';
@@ -26,16 +28,39 @@ export class VendorAdapterFactory {
26
28
  if (!vendorConfig) {
27
29
  throw new Error(`Vendor not found: ${vendorKey}`);
28
30
  }
31
+
29
32
  switch (vendorConfig.vendor_type) {
30
33
  case 'launcher':
31
34
  return new LauncherAdapter(vendorConfig);
35
+ case 'didnames':
36
+ return new DidnamesAdapter(vendorConfig);
32
37
  default:
33
38
  throw new Error(`Unsupported vendor: ${vendorConfig.vendor_type}`);
34
39
  }
35
40
  }
36
41
 
42
+ /**
43
+ * Create coordinated adapters for domain binding workflow
44
+ * @param vendorKeys Array of vendor keys in execution order
45
+ */
46
+ static async createCoordinated(vendorKeys: string[]): Promise<{
47
+ didnamesAdapter?: DidnamesAdapter;
48
+ launcherAdapter?: LauncherAdapter;
49
+ }> {
50
+ const adapters: { [key: string]: VendorAdapter } = {};
51
+
52
+ for (const vendorKey of vendorKeys) {
53
+ adapters[vendorKey] = await this.create(vendorKey); // eslint-disable-line no-await-in-loop
54
+ }
55
+
56
+ return {
57
+ didnamesAdapter: adapters.didnames as DidnamesAdapter,
58
+ launcherAdapter: adapters.launcher as LauncherAdapter,
59
+ };
60
+ }
61
+
37
62
  static getSupportedVendors(): string[] {
38
- return ['launcher'];
63
+ return ['launcher', 'didnames'];
39
64
  }
40
65
 
41
66
  static isSupported(vendorKey: string): boolean {
@@ -1,8 +1,9 @@
1
1
  import { Auth as VendorAuth } from '@blocklet/payment-vendor';
2
2
 
3
- import { joinURL } from 'ufo';
4
3
  import { ProductVendor } from '../../../store/models';
4
+ import dayjs from '../../dayjs';
5
5
  import logger from '../../logger';
6
+ import { formatToShortUrl } from '../../url';
6
7
  import { api } from '../../util';
7
8
  import {
8
9
  CheckOrderStatusParams,
@@ -14,6 +15,51 @@ import {
14
15
  VendorAdapter,
15
16
  VendorConfig,
16
17
  } from './types';
18
+ import { formatVendorUrl } from './util';
19
+
20
+ const doRequestVendorData = (vendor: ProductVendor, orderId: string, url: string) => {
21
+ const { headers } = VendorAuth.signRequestWithHeaders({});
22
+ const name = vendor?.name;
23
+ const key = vendor?.vendor_key;
24
+
25
+ return fetch(url, { headers })
26
+ .then(async (r) => {
27
+ const data = await r.json();
28
+ if (r.status !== 200) {
29
+ logger.error('vendor status fetch failed', {
30
+ vendorId: vendor.id,
31
+ vendorKey: vendor.vendor_key,
32
+ orderId,
33
+ status: r.status,
34
+ url,
35
+ data,
36
+ });
37
+ throw new Error(
38
+ `vendor status fetch failed, vendor: ${vendor.vendor_key}, orderId: ${orderId}, status: ${r.status}, url: ${url}`
39
+ );
40
+ }
41
+ if (!data.dashboardUrl) {
42
+ return {
43
+ ...data,
44
+ name,
45
+ key,
46
+ };
47
+ }
48
+
49
+ const validUntil = dayjs().add(20, 'minutes').format('YYYY-MM-DDTHH:mm:ss+00:00');
50
+ const maxVisits = 5;
51
+ const homeUrl = await formatToShortUrl({ url: data.homeUrl, maxVisits, validUntil });
52
+ const dashboardUrl = await formatToShortUrl({ url: data.dashboardUrl, maxVisits, validUntil });
53
+ return {
54
+ ...data,
55
+ name,
56
+ key,
57
+ homeUrl,
58
+ dashboardUrl,
59
+ };
60
+ })
61
+ .catch((e) => ({ error: e.message }));
62
+ };
17
63
 
18
64
  export class LauncherAdapter implements VendorAdapter {
19
65
  private vendorConfig: VendorConfig | null = null;
@@ -40,12 +86,12 @@ export class LauncherAdapter implements VendorAdapter {
40
86
  productCode: params.productCode,
41
87
  customerId: params.customerId,
42
88
  description: params.description,
89
+ bindCapInfo: params.deliveryParams?.customParams?.bindDomainCap ? 'present' : 'not present',
43
90
  });
44
91
 
45
92
  const vendorConfig = await this.getVendorConfig();
46
93
 
47
94
  try {
48
- const launcherApiUrl = vendorConfig.app_url;
49
95
  const orderData = {
50
96
  checkoutSessionId: params.checkoutSessionId,
51
97
  description: params.description,
@@ -53,19 +99,42 @@ export class LauncherAdapter implements VendorAdapter {
53
99
  deliveryParams: params.deliveryParams,
54
100
  };
55
101
 
102
+ // Extract bindDomainCap related params if present
103
+ const customParams = params.deliveryParams?.customParams || {};
104
+ const { domain, sessionId, bindDomainCap, domainNftDid } = customParams;
105
+
106
+ if (bindDomainCap && domain && sessionId) {
107
+ logger.info('passing domain binding authorization to Blocklet Server', {
108
+ domain,
109
+ sessionId: `${sessionId.substring(0, 8)}...`, // Log partial sessionId for security
110
+ hasBindCap: true,
111
+ });
112
+
113
+ // Ensure bindDomainCap data is passed through to Blocklet Server
114
+ orderData.deliveryParams = {
115
+ ...orderData.deliveryParams,
116
+ customParams: {
117
+ ...orderData.deliveryParams.customParams,
118
+ domain,
119
+ sessionId,
120
+ bindDomainCap,
121
+ domainNftDid,
122
+ },
123
+ };
124
+ }
125
+
56
126
  const { headers, body } = VendorAuth.signRequestWithHeaders(orderData);
57
- const response = await fetch(
58
- joinURL(launcherApiUrl, vendorConfig.metadata?.mountPoint || '', '/api/vendor/deliveries'),
59
- {
60
- method: 'POST',
61
- headers,
62
- body,
63
- }
64
- );
127
+ const url = formatVendorUrl(vendorConfig, '/api/vendor/deliveries');
128
+ const response = await fetch(url, {
129
+ method: 'POST',
130
+ headers,
131
+ body,
132
+ });
65
133
 
66
134
  if (!response.ok) {
67
135
  const errorBody = await response.text();
68
- logger.error('Launcher API error', {
136
+ logger.error('call launcher API error', {
137
+ url,
69
138
  status: response.status,
70
139
  statusText: response.statusText,
71
140
  body: errorBody,
@@ -90,7 +159,18 @@ export class LauncherAdapter implements VendorAdapter {
90
159
  currency: params.currency,
91
160
  quantity: params.quantity,
92
161
  invoiceId: params.invoiceId,
93
- customParams: params.customParams,
162
+ customParams: {
163
+ ...params.customParams,
164
+ // Include domain binding info if available
165
+ ...(customParams.bindDomainCap
166
+ ? {
167
+ domain: customParams.domain,
168
+ sessionId: customParams.sessionId,
169
+ bindDomainCap: customParams.bindDomainCap,
170
+ domainNftDid: customParams.domainNftDid,
171
+ }
172
+ : {}),
173
+ },
94
174
  },
95
175
  installationInfo: {
96
176
  appId: launcherResult.appId,
@@ -110,7 +190,7 @@ export class LauncherAdapter implements VendorAdapter {
110
190
  return result;
111
191
  } catch (error: any) {
112
192
  logger.error('Failed to create launcher order', {
113
- error: error.message,
193
+ error,
114
194
  productCode: params.productCode,
115
195
  customerId: params.customerId,
116
196
  });
@@ -126,17 +206,13 @@ export class LauncherAdapter implements VendorAdapter {
126
206
  });
127
207
 
128
208
  const vendorConfig = await this.getVendorConfig();
129
- const launcherApiUrl = vendorConfig.app_url;
130
209
  const { headers, body } = VendorAuth.signRequestWithHeaders(params);
131
210
 
132
- const response = await fetch(
133
- joinURL(launcherApiUrl, vendorConfig.metadata?.mountPoint || '', '/api/vendor/return'),
134
- {
135
- method: 'POST',
136
- headers,
137
- body,
138
- }
139
- );
211
+ const response = await fetch(formatVendorUrl(vendorConfig, '/api/vendor/return'), {
212
+ method: 'POST',
213
+ headers,
214
+ body,
215
+ });
140
216
 
141
217
  if (!response.ok) {
142
218
  const errorBody = await response.text();
@@ -178,4 +254,16 @@ export class LauncherAdapter implements VendorAdapter {
178
254
  throw error;
179
255
  }
180
256
  }
257
+
258
+ getOrderStatus(vendor: ProductVendor, orderId: string): Promise<any> {
259
+ const url = formatVendorUrl(vendor, `/api/vendor/status/${orderId}`);
260
+
261
+ return doRequestVendorData(vendor, orderId, url);
262
+ }
263
+
264
+ getOrder(vendor: ProductVendor, orderId: string): Promise<any> {
265
+ const url = formatVendorUrl(vendor, `/api/vendor/orders/${orderId}`);
266
+
267
+ return doRequestVendorData(vendor, orderId, url);
268
+ }
181
269
  }
@@ -1,3 +1,5 @@
1
+ import type { ProductVendor } from '../../../store/models';
2
+
1
3
  export interface VendorConfig {
2
4
  id: string;
3
5
  vendor_key: string;
@@ -85,4 +87,6 @@ export interface VendorAdapter {
85
87
  fulfillOrder(params: FulfillOrderParams): Promise<FulfillOrderResult>;
86
88
  requestReturn(params: ReturnRequestParams): Promise<ReturnRequestResult>;
87
89
  checkOrderStatus(params: CheckOrderStatusParams): Promise<CheckOrderStatusResult>;
90
+ getOrder(vendor: ProductVendor, orderId: string): Promise<any>;
91
+ getOrderStatus(vendor: ProductVendor, orderId: string): Promise<any>;
88
92
  }
@@ -0,0 +1,7 @@
1
+ import { joinURL } from 'ufo';
2
+
3
+ import type { VendorConfig } from './types';
4
+
5
+ export const formatVendorUrl = (vendorConfig: VendorConfig, p: string) => {
6
+ return joinURL(vendorConfig.app_url, vendorConfig.metadata?.mountPoint || '', p);
7
+ };
@@ -47,7 +47,8 @@ export class VendorFulfillmentService {
47
47
  currency_id: string;
48
48
  customer_did: string;
49
49
  },
50
- vendorConfig: any
50
+ vendorConfig: any,
51
+ sharedContext?: any // Pass bindDomainCap and other shared data between vendors
51
52
  ): Promise<VendorFulfillmentResult> {
52
53
  try {
53
54
  const vendor = await ProductVendor.findByPk(vendorConfig.vendor_id);
@@ -87,7 +88,11 @@ export class VendorFulfillmentService {
87
88
  },
88
89
  deliveryParams: {
89
90
  blockletMetaUrl: vendor.metadata?.blockletMetaUrl,
90
- customParams: vendorConfig.custom_params,
91
+ customParams: {
92
+ ...vendorConfig.custom_params,
93
+ // Merge shared context (bindDomainCap from didnames-adapter)
94
+ ...(sharedContext || {}),
95
+ },
91
96
  },
92
97
  });
93
98
 
@@ -24,11 +24,172 @@ interface CoordinatorJob {
24
24
 
25
25
  const MAX_FULFILLMENT_TIMEOUT = 300000;
26
26
 
27
+ // Helper function to get order info for coordinated fulfillment
28
+ async function getCoordinatedOrderInfo(checkoutSessionId: string) {
29
+ const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId);
30
+
31
+ if (!checkoutSession) {
32
+ throw new Error('CheckoutSession or Invoice not found');
33
+ }
34
+
35
+ return {
36
+ checkoutSessionId,
37
+ amount_total: checkoutSession.amount_total,
38
+ customer_id: checkoutSession.customer_id || '',
39
+ invoiceId: checkoutSession.invoice_id || '',
40
+ currency_id: checkoutSession.currency_id,
41
+ customer_did: checkoutSession.customer_did || '',
42
+ };
43
+ }
44
+
45
+ async function handleCoordinatedFulfillment(job: any): Promise<void> {
46
+ const { checkoutSessionId, invoiceId, didnamesVendorConfig, launcherVendorConfig, context } = job;
47
+
48
+ try {
49
+ logger.info('Starting coordinated fulfillment', {
50
+ checkoutSessionId,
51
+ didnamesVendorId: didnamesVendorConfig.vendor_id,
52
+ launcherVendorId: launcherVendorConfig.vendor_id,
53
+ });
54
+
55
+ const orderInfo = await getCoordinatedOrderInfo(checkoutSessionId);
56
+ if (!orderInfo) {
57
+ throw new Error('Order info not found');
58
+ }
59
+
60
+ // Step 1: Execute didnames fulfillment to generate bindDomainCap
61
+ logger.info('Step 1: Executing didnames fulfillment', { checkoutSessionId });
62
+ const didnamesResult = await VendorFulfillmentService.fulfillSingleVendorOrder(
63
+ orderInfo,
64
+ didnamesVendorConfig,
65
+ context
66
+ );
67
+
68
+ if (didnamesResult.status === 'failed') {
69
+ throw new Error(`Didnames fulfillment failed: ${didnamesResult.errorMessage}`);
70
+ }
71
+
72
+ // Extract bindDomainCap from didnames result
73
+ const bindCapData = didnamesResult.orderDetails?.customParams || {};
74
+ const { domain, sessionId, bindDomainCap, nftDid } = bindCapData;
75
+
76
+ if (!bindDomainCap || !domain || !sessionId) {
77
+ logger.warn('Missing bindDomainCap data from didnames adapter', {
78
+ checkoutSessionId,
79
+ hasBindCap: !!bindDomainCap,
80
+ hasDomain: !!domain,
81
+ hasSessionId: !!sessionId,
82
+ });
83
+ }
84
+
85
+ // Update didnames vendor status
86
+ await updateVendorFulfillmentStatus(
87
+ checkoutSessionId,
88
+ invoiceId,
89
+ didnamesVendorConfig.vendor_id,
90
+ didnamesResult.status === 'completed' ? 'completed' : 'sent',
91
+ {
92
+ orderId: didnamesResult.orderId,
93
+ commissionAmount: didnamesResult.commissionAmount,
94
+ serviceUrl: didnamesResult.serviceUrl,
95
+ }
96
+ );
97
+
98
+ // Step 2: Execute launcher fulfillment with bindDomainCap
99
+ logger.info('Step 2: Executing launcher fulfillment with bindDomainCap', {
100
+ checkoutSessionId,
101
+ hasBindCapData: !!bindDomainCap,
102
+ });
103
+
104
+ const launcherResult = await VendorFulfillmentService.fulfillSingleVendorOrder(orderInfo, launcherVendorConfig, {
105
+ ...context,
106
+ domain,
107
+ sessionId,
108
+ bindDomainCap,
109
+ domainNftDid: nftDid,
110
+ });
111
+
112
+ // Update launcher vendor status
113
+ await updateVendorFulfillmentStatus(
114
+ checkoutSessionId,
115
+ invoiceId,
116
+ launcherVendorConfig.vendor_id,
117
+ launcherResult.status === 'completed' ? 'completed' : 'sent',
118
+ {
119
+ orderId: launcherResult.orderId,
120
+ commissionAmount: launcherResult.commissionAmount,
121
+ serviceUrl: launcherResult.serviceUrl,
122
+ }
123
+ );
124
+
125
+ logger.info('Coordinated fulfillment completed successfully', {
126
+ checkoutSessionId,
127
+ didnamesStatus: didnamesResult.status,
128
+ launcherStatus: launcherResult.status,
129
+ });
130
+
131
+ // Check if all vendors are completed to trigger commission
132
+ await triggerCommissionProcess(checkoutSessionId, invoiceId);
133
+ } catch (error: any) {
134
+ logger.error('Coordinated fulfillment failed', {
135
+ checkoutSessionId,
136
+ error: error.message,
137
+ });
138
+
139
+ // Mark both vendors as failed
140
+ await updateVendorFulfillmentStatus(checkoutSessionId, invoiceId, didnamesVendorConfig.vendor_id, 'failed', {
141
+ lastError: error.message,
142
+ });
143
+
144
+ await updateVendorFulfillmentStatus(checkoutSessionId, invoiceId, launcherVendorConfig.vendor_id, 'failed', {
145
+ lastError: error.message,
146
+ });
147
+
148
+ throw error;
149
+ }
150
+ }
151
+
27
152
  export const fulfillmentCoordinatorQueue = createQueue({
28
153
  name: 'fulfillment-coordinator',
29
154
  onJob: handleFulfillmentCoordination,
30
155
  });
31
156
 
157
+ export const coordinatedFulfillmentQueue = createQueue({
158
+ name: 'coordinated-fulfillment',
159
+ onJob: handleCoordinatedFulfillment,
160
+ options: {
161
+ concurrency: 1,
162
+ maxRetries: 3,
163
+ enableScheduledJob: true,
164
+ },
165
+ });
166
+
167
+ export const startCoordinatedFulfillmentQueue = () => {
168
+ logger.debug('startCoordinatedFulfillmentQueue');
169
+ return Promise.resolve();
170
+ };
171
+
172
+ // Add event listener for coordinated fulfillment
173
+ events.on('vendor.fulfillment.coordinated', async (id, job) => {
174
+ try {
175
+ const exist = await coordinatedFulfillmentQueue.get(id);
176
+ if (!exist) {
177
+ logger.info('Adding coordinated fulfillment job to queue', { id, checkoutSessionId: job.checkoutSessionId });
178
+ coordinatedFulfillmentQueue.push({
179
+ id,
180
+ job,
181
+ });
182
+ } else {
183
+ logger.info('Coordinated fulfillment job already exists, skipping', { id });
184
+ }
185
+ } catch (error: any) {
186
+ logger.error('Failed to handle coordinated fulfillment queue event', {
187
+ id,
188
+ error: error.message,
189
+ });
190
+ }
191
+ });
192
+
32
193
  export async function startVendorFulfillment(checkoutSessionId: string, invoiceId: string): Promise<void> {
33
194
  try {
34
195
  logger.info('Starting vendor fulfillment process', {
@@ -60,17 +221,42 @@ export async function startVendorFulfillment(checkoutSessionId: string, invoiceI
60
221
 
61
222
  await updateVendorInfo(checkoutSessionId, initialVendorInfo);
62
223
 
63
- // Trigger a separate vendor-fulfillment queue for each vendor
64
- for (const vendorConfig of vendorConfigs) {
65
- const vendorFulfillmentJobId = `vendor-fulfillment-${checkoutSessionId}-${vendorConfig.vendor_id}`;
224
+ // Check if we need coordinated fulfillment (didnames + launcher)
225
+ const didnamesVendor = vendorConfigs.find((v) => v.vendor_type === 'didnames');
226
+ const launcherVendor = vendorConfigs.find((v) => v.vendor_type === 'launcher');
227
+
228
+ if (didnamesVendor && launcherVendor) {
229
+ // Coordinated fulfillment: didnames first, then launcher with bindDomainCap
230
+ logger.info('Starting coordinated domain binding fulfillment', {
231
+ checkoutSessionId,
232
+ didnamesVendorId: didnamesVendor.vendor_id,
233
+ launcherVendorId: launcherVendor.vendor_id,
234
+ });
66
235
 
67
- events.emit('vendor.fulfillment.queued', vendorFulfillmentJobId, {
236
+ const coordinatedJobId = `coordinated-fulfillment-${checkoutSessionId}`;
237
+ events.emit('vendor.fulfillment.coordinated', coordinatedJobId, {
68
238
  checkoutSessionId,
69
239
  invoiceId,
70
- vendorId: vendorConfig.vendor_id,
71
- vendorConfig,
240
+ didnamesVendorConfig: didnamesVendor,
241
+ launcherVendorConfig: launcherVendor,
72
242
  retryOnError: true,
73
243
  });
244
+ } else {
245
+ // Regular parallel fulfillment for other vendors
246
+ for (const vendorConfig of vendorConfigs) {
247
+ const vendorFulfillmentJobId = `vendor-fulfillment-${checkoutSessionId}-${vendorConfig.vendor_id}`;
248
+
249
+ events.emit('vendor.fulfillment.queued', vendorFulfillmentJobId, {
250
+ checkoutSessionId,
251
+ invoiceId,
252
+ vendorId: vendorConfig.vendor_id,
253
+ vendorConfig,
254
+ retryOnError: true,
255
+ context: {
256
+ subdomain: `docsmith-${Date.now()}`,
257
+ },
258
+ });
259
+ }
74
260
  }
75
261
 
76
262
  logger.info('Vendor fulfillment process has been triggered', {
@@ -294,7 +480,7 @@ async function updateSingleVendorInfo(
294
480
  logger.error('updateVendorInfo - Update failed', {
295
481
  checkoutSessionId,
296
482
  vendorId,
297
- error: error.message,
483
+ error,
298
484
  });
299
485
  throw error;
300
486
  } finally {
@@ -1,9 +1,8 @@
1
1
  import { getUrl } from '@blocklet/sdk/lib/component';
2
2
  import { Router } from 'express';
3
3
  import Joi from 'joi';
4
+ import { middleware } from '@blocklet/payment-vendor';
4
5
 
5
- import { Auth as VendorAuth, middleware } from '@blocklet/payment-vendor';
6
- import { joinURL } from 'ufo';
7
6
  import { MetadataSchema } from '../libs/api';
8
7
  import { wallet } from '../libs/auth';
9
8
  import dayjs from '../libs/dayjs';
@@ -11,14 +10,16 @@ import logger from '../libs/logger';
11
10
  import { authenticate } from '../libs/security';
12
11
  import { formatToShortUrl } from '../libs/url';
13
12
  import { getBlockletJson } from '../libs/util';
13
+ import { VendorFulfillmentService } from '../libs/vendor-util/fulfillment';
14
14
  import { CheckoutSession, Invoice, Subscription } from '../store/models';
15
15
  import { ProductVendor } from '../store/models/product-vendor';
16
16
 
17
17
  const authAdmin = authenticate<CheckoutSession>({ component: true, roles: ['owner', 'admin'] });
18
+ const loginAuth = authenticate<CheckoutSession>({ component: true, ensureLogin: true, mine: true });
18
19
 
19
20
  const createVendorSchema = Joi.object({
20
21
  vendor_key: Joi.string().max(50).required(),
21
- vendor_type: Joi.string().valid('launcher').default('launcher'),
22
+ vendor_type: Joi.string().valid('launcher', 'didnames').default('launcher'),
22
23
  name: Joi.string().max(255).required(),
23
24
  description: Joi.string().max(1000).allow('').optional(),
24
25
  app_url: Joi.string().uri().max(512).required(),
@@ -32,7 +33,7 @@ const createVendorSchema = Joi.object({
32
33
  }).unknown(false);
33
34
 
34
35
  const updateVendorSchema = Joi.object({
35
- vendor_type: Joi.string().valid('launcher').optional(),
36
+ vendor_type: Joi.string().valid('launcher', 'didnames').optional(),
36
37
  name: Joi.string().max(255).optional(),
37
38
  description: Joi.string().max(1000).allow('').optional(),
38
39
  app_url: Joi.string().uri().max(512).optional(),
@@ -114,7 +115,7 @@ async function getAllVendors(_req: any, res: any) {
114
115
  }
115
116
  }
116
117
 
117
- async function getVendorById(req: any, res: any) {
118
+ async function getVendorInfo(req: any, res: any) {
118
119
  try {
119
120
  const vendor = await ProductVendor.findByPk(req.params.id);
120
121
  if (!vendor) {
@@ -294,57 +295,40 @@ async function testVendorConnection(req: any, res: any) {
294
295
  }
295
296
  }
296
297
 
297
- async function getVendorStatusByVendorId(vendorId: string, orderId: string, isDetail = false) {
298
+ const getVendorById = async (vendorId: string, orderId: string) => {
298
299
  if (!vendorId || !orderId) {
299
- return {};
300
+ throw new Error(`vendorId or orderId is required, vendorId: ${vendorId}, orderId: ${orderId}`);
300
301
  }
301
302
 
302
303
  const vendor = await ProductVendor.findByPk(vendorId);
303
- const url = vendor?.app_url
304
- ? joinURL(
305
- vendor.app_url,
306
- vendor.metadata?.mountPoint || '',
307
- '/api/vendor/',
308
- isDetail ? 'orders' : 'status',
309
- orderId
310
- )
311
- : null;
312
-
313
- const { headers } = VendorAuth.signRequestWithHeaders({});
314
- const name = vendor?.name;
315
- const key = vendor?.vendor_key;
316
-
317
- return url
318
- ? fetch(url, { headers })
319
- .then(async (r) => {
320
- const data = await r.json();
321
-
322
- if (!data.dashboardUrl) {
323
- return {
324
- ...data,
325
- name,
326
- key,
327
- };
328
- }
329
- const validUntil = dayjs().add(20, 'minutes').format('YYYY-MM-DDTHH:mm:ss+00:00');
330
- const maxVisits = 5;
331
-
332
- const homeUrl = await formatToShortUrl({ url: data.homeUrl, maxVisits, validUntil });
333
- const dashboardUrl = await formatToShortUrl({ url: data.dashboardUrl, maxVisits, validUntil });
334
-
335
- return {
336
- ...data,
337
- name,
338
- key,
339
- homeUrl,
340
- dashboardUrl,
341
- };
342
- })
343
- .catch((e) => ({ error: e.message }))
344
- : null;
304
+ if (!vendor) {
305
+ throw new Error(`vendor not found, vendorId: ${vendorId}`);
306
+ }
307
+
308
+ const vendorAdapter = await VendorFulfillmentService.getVendorAdapter(vendor.vendor_key);
309
+
310
+ const data = await vendorAdapter.getOrder(vendor, orderId);
311
+
312
+ return data;
313
+ };
314
+
315
+ async function getVendorStatusById(vendorId: string, orderId: string) {
316
+ if (!vendorId || !orderId) {
317
+ throw new Error(`vendorId or orderId is required, vendorId: ${vendorId}, orderId: ${orderId}`);
318
+ }
319
+
320
+ const vendor = await ProductVendor.findByPk(vendorId);
321
+
322
+ if (!vendor) {
323
+ throw new Error(`vendor not found, vendorId: ${vendorId}`);
324
+ }
325
+
326
+ const vendorAdapter = await VendorFulfillmentService.getVendorAdapter(vendor.vendor_key);
327
+
328
+ return vendorAdapter.getOrderStatus(vendor, orderId);
345
329
  }
346
330
 
347
- async function getVendorStatus(sessionId: string, isDetail = false) {
331
+ async function doRequestVendor(sessionId: string, func: (vendorId: string, orderId: string) => Promise<any>) {
348
332
  const doc = await CheckoutSession.findByPk(sessionId);
349
333
 
350
334
  if (!doc) {
@@ -376,35 +360,36 @@ async function getVendorStatus(sessionId: string, isDetail = false) {
376
360
  }
377
361
 
378
362
  const vendors = doc.vendor_info.map((item) => {
379
- return getVendorStatusByVendorId(item.vendor_id, item.order_id, isDetail).then((status) => {
380
- return {
381
- error_message: item.error_message,
382
- status: item.status,
383
- ...status,
384
- };
385
- });
363
+ return func(item.vendor_id, item.order_id);
386
364
  });
387
365
 
388
- const subscriptionId = doc.subscription_id;
389
- let shortSubscriptionUrl = '';
366
+ return {
367
+ payment_status: doc.payment_status,
368
+ session_status: doc.status,
369
+ vendors: await Promise.all(vendors),
370
+ subscriptionId: doc.subscription_id,
371
+ error: null,
372
+ };
373
+ }
374
+
375
+ async function getVendorStatus(sessionId: string) {
376
+ const result: any = await doRequestVendor(sessionId, getVendorStatusById);
390
377
 
391
- if (isDetail && subscriptionId) {
392
- const subscriptionUrl = getUrl(`/customer/subscription/${subscriptionId}`);
378
+ if (result.subscriptionId) {
379
+ const subscriptionUrl = getUrl(`/customer/subscription/${result.subscriptionId}`);
393
380
 
394
- shortSubscriptionUrl = await formatToShortUrl({
381
+ result.subscriptionUrl = await formatToShortUrl({
395
382
  url: subscriptionUrl,
396
383
  maxVisits: 5,
397
384
  validUntil: dayjs().add(20, 'minutes').format('YYYY-MM-DDTHH:mm:ss+00:00'),
398
385
  });
399
386
  }
400
387
 
401
- return {
402
- payment_status: paymentStatus,
403
- session_status: doc.status,
404
- subscriptionUrl: shortSubscriptionUrl,
405
- vendors: await Promise.all(vendors),
406
- error: null,
407
- };
388
+ return result;
389
+ }
390
+
391
+ function getVendor(sessionId: string) {
392
+ return doRequestVendor(sessionId, getVendorById);
408
393
  }
409
394
 
410
395
  async function getVendorFulfillmentStatus(req: any, res: any) {
@@ -426,7 +411,7 @@ async function getVendorFulfillmentDetail(req: any, res: any) {
426
411
  const { sessionId } = req.params;
427
412
 
428
413
  try {
429
- const detail = await getVendorStatus(sessionId, true);
414
+ const detail = await getVendor(sessionId);
430
415
  if (detail.code) {
431
416
  return res.status(detail.code).json({ error: detail.error });
432
417
  }
@@ -457,7 +442,7 @@ async function redirectToVendor(req: any, res: any) {
457
442
  return res.redirect('/404');
458
443
  }
459
444
 
460
- const detail = await getVendorStatusByVendorId(vendorId, order.order_id || '', true);
445
+ const detail = await getVendorById(vendorId, order.order_id || '');
461
446
  if (!detail) {
462
447
  logger.warn('Vendor status detail not found', {
463
448
  subscriptionId,
@@ -539,8 +524,8 @@ const ensureVendorAuth = middleware.ensureVendorAuth((vendorPk: string) =>
539
524
  );
540
525
 
541
526
  // FIXME: Authentication not yet added, awaiting implementation @Pengfei
542
- router.get('/order/:sessionId/status', validateParams(sessionIdParamSchema), getVendorFulfillmentStatus);
543
- router.get('/order/:sessionId/detail', validateParams(sessionIdParamSchema), getVendorFulfillmentDetail);
527
+ router.get('/order/:sessionId/status', loginAuth, validateParams(sessionIdParamSchema), getVendorFulfillmentStatus);
528
+ router.get('/order/:sessionId/detail', loginAuth, validateParams(sessionIdParamSchema), getVendorFulfillmentDetail);
544
529
 
545
530
  router.get('/subscription/:sessionId/redirect', handleSubscriptionRedirect);
546
531
  router.get('/subscription/:sessionId', ensureVendorAuth, getVendorSubscription);
@@ -554,7 +539,7 @@ router.get(
554
539
  );
555
540
 
556
541
  router.get('/', getAllVendors);
557
- router.get('/:id', authAdmin, validateParams(vendorIdParamSchema), getVendorById);
542
+ router.get('/:id', authAdmin, validateParams(vendorIdParamSchema), getVendorInfo);
558
543
  router.post('/', authAdmin, createVendor);
559
544
  router.put('/:id', authAdmin, validateParams(vendorIdParamSchema), updateVendor);
560
545
  router.delete('/:id', authAdmin, validateParams(vendorIdParamSchema), deleteVendor);
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.20.16
17
+ version: 1.20.18
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.20.16",
3
+ "version": "1.20.18",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
@@ -56,8 +56,9 @@
56
56
  "@blocklet/error": "^0.2.5",
57
57
  "@blocklet/js-sdk": "^1.16.52-beta-20250912-112002-e3499e9c",
58
58
  "@blocklet/logger": "^1.16.52-beta-20250912-112002-e3499e9c",
59
- "@blocklet/payment-react": "1.20.16",
60
- "@blocklet/payment-vendor": "1.20.16",
59
+ "@blocklet/payment-broker-client": "1.20.18",
60
+ "@blocklet/payment-react": "1.20.18",
61
+ "@blocklet/payment-vendor": "1.20.18",
61
62
  "@blocklet/sdk": "^1.16.52-beta-20250912-112002-e3499e9c",
62
63
  "@blocklet/ui-react": "^3.1.41",
63
64
  "@blocklet/uploader": "^0.2.12",
@@ -120,13 +121,14 @@
120
121
  "ufo": "^1.6.1",
121
122
  "umzug": "^3.8.2",
122
123
  "use-bus": "^2.5.2",
124
+ "uuid": "^13.0.0",
123
125
  "validator": "^13.15.15",
124
126
  "web3": "^4.16.0"
125
127
  },
126
128
  "devDependencies": {
127
129
  "@abtnode/types": "^1.16.52-beta-20250912-112002-e3499e9c",
128
130
  "@arcblock/eslint-config-ts": "^0.3.3",
129
- "@blocklet/payment-types": "1.20.16",
131
+ "@blocklet/payment-types": "1.20.18",
130
132
  "@types/cookie-parser": "^1.4.9",
131
133
  "@types/cors": "^2.8.19",
132
134
  "@types/debug": "^4.1.12",
@@ -173,5 +175,5 @@
173
175
  "parser": "typescript"
174
176
  }
175
177
  },
176
- "gitHead": "ebf0677dd4414d4abc4129d6663a7e9ddb415281"
178
+ "gitHead": "94e4020fb0d4121ebf0430de2db1fd642676c8ae"
177
179
  }
@@ -1126,6 +1126,7 @@ export default flat({
1126
1126
  nameRequired: 'Vendor name is required',
1127
1127
  vendorType: 'Vendor Type',
1128
1128
  vendorTypeRequired: 'Vendor type is required',
1129
+ didnames: 'DID Names',
1129
1130
  launcher: 'Launcher',
1130
1131
  vendorKey: 'Vendor Key',
1131
1132
  vendorKeyRequired: 'Vendor key is required',
@@ -1098,6 +1098,7 @@ export default flat({
1098
1098
  nameRequired: '供应商名称是必填项',
1099
1099
  vendorType: '供应商类型',
1100
1100
  vendorTypeRequired: '供应商类型是必填项',
1101
+ didnames: 'DID Names',
1101
1102
  launcher: '启动器',
1102
1103
  vendorKey: '供应商标识',
1103
1104
  vendorKeyRequired: '供应商标识是必填项',
@@ -80,13 +80,13 @@ export default function VendorCreate({
80
80
  }
81
81
  : {
82
82
  vendor_key: '',
83
- vendor_type: 'launcher',
83
+ vendor_type: 'didnames',
84
84
  name: '',
85
85
  description: '',
86
86
  app_url: '',
87
87
  vendor_did: '',
88
88
  status: 'inactive' as const,
89
- metadata: [{ key: 'blockletMetaUrl', value: '' }],
89
+ metadata: [],
90
90
  app_pid: '',
91
91
  app_logo: '',
92
92
  };
@@ -256,6 +256,7 @@ export default function VendorCreate({
256
256
  <InputLabel>{t('admin.vendor.vendorType')}</InputLabel>
257
257
  <Select {...field} label={t('admin.vendor.vendorType')}>
258
258
  <MenuItem value="launcher">{t('admin.vendor.launcher')}</MenuItem>
259
+ <MenuItem value="didnames">{t('admin.vendor.didnames')}</MenuItem>
259
260
  </Select>
260
261
  </FormControl>
261
262
  )}