payment-kit 1.20.9 → 1.20.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ import { handlePaymentIntentEvent } from './payment-intent';
6
6
  import { handleSetupIntentEvent } from './setup-intent';
7
7
  import { handleSubscriptionEvent } from './subscription';
8
8
 
9
- export default function handleStripeEvent(event: any, client: Stripe) {
9
+ export default async function handleStripeEvent(event: any, client: Stripe) {
10
10
  switch (event.type) {
11
11
  case 'payment_intent.canceled':
12
12
  case 'payment_intent.created':
@@ -14,8 +14,16 @@ export default function handleStripeEvent(event: any, client: Stripe) {
14
14
  case 'payment_intent.payment_failed':
15
15
  case 'payment_intent.processing':
16
16
  case 'payment_intent.requires_action':
17
- case 'payment_intent.succeeded':
17
+ case 'payment_intent.succeeded': {
18
+ if (event.data?.object?.id) {
19
+ const record = await client.paymentIntents.retrieve(event.data.object.id);
20
+ event.data.object = {
21
+ ...event.data.object,
22
+ ...record,
23
+ };
24
+ }
18
25
  return handlePaymentIntentEvent(event, client);
26
+ }
19
27
 
20
28
  // case 'setup_intent.created':
21
29
  case 'setup_intent.canceled':
@@ -1,5 +1,6 @@
1
1
  import { VendorAuth } from '@blocklet/payment-vendor';
2
2
 
3
+ import { joinURL } from 'ufo';
3
4
  import { ProductVendor } from '../../../store/models';
4
5
  import logger from '../../logger';
5
6
  import { api } from '../../util';
@@ -53,11 +54,14 @@ export class LauncherAdapter implements VendorAdapter {
53
54
  };
54
55
 
55
56
  const { headers, body } = VendorAuth.signRequestWithHeaders(orderData);
56
- const response = await fetch(`${launcherApiUrl}/api/vendor/deliveries`, {
57
- method: 'POST',
58
- headers,
59
- body,
60
- });
57
+ const response = await fetch(
58
+ joinURL(launcherApiUrl, vendorConfig.metadata?.mountPoint || '', '/api/vendor/deliveries'),
59
+ {
60
+ method: 'POST',
61
+ headers,
62
+ body,
63
+ }
64
+ );
61
65
 
62
66
  if (!response.ok) {
63
67
  const errorBody = await response.text();
@@ -125,11 +129,14 @@ export class LauncherAdapter implements VendorAdapter {
125
129
  const launcherApiUrl = vendorConfig.app_url;
126
130
  const { headers, body } = VendorAuth.signRequestWithHeaders(params);
127
131
 
128
- const response = await fetch(`${launcherApiUrl}/api/vendor/return`, {
129
- method: 'POST',
130
- headers,
131
- body,
132
- });
132
+ const response = await fetch(
133
+ joinURL(launcherApiUrl, vendorConfig.metadata?.mountPoint || '', '/api/vendor/return'),
134
+ {
135
+ method: 'POST',
136
+ headers,
137
+ body,
138
+ }
139
+ );
133
140
 
134
141
  if (!response.ok) {
135
142
  const errorBody = await response.text();
@@ -1,13 +1,12 @@
1
1
  import { BN } from '@ocap/util';
2
+ import { Customer, PaymentMethod } from '../../store/models';
2
3
  import { CheckoutSession } from '../../store/models/checkout-session';
3
- import { Product } from '../../store/models/product';
4
- import { ProductVendor } from '../../store/models/product-vendor';
5
- import { Payout } from '../../store/models/payout';
6
4
  import { PaymentIntent } from '../../store/models/payment-intent';
7
- import { Price } from '../../store/models/price';
8
- import { calculateVendorCommission, VendorAdapterFactory } from './adapters/factory';
9
- import { Customer } from '../../store/models';
5
+ import { Payout } from '../../store/models/payout';
6
+ import { ProductVendor } from '../../store/models/product-vendor';
7
+ import env from '../env';
10
8
  import logger from '../logger';
9
+ import { calculateVendorCommission, VendorAdapterFactory } from './adapters/factory';
11
10
 
12
11
  const DEFAULT_COMMISSION_RATE = 20;
13
12
 
@@ -80,11 +79,11 @@ export class VendorFulfillmentService {
80
79
  amount: orderInfo.amount_total,
81
80
  currency: orderInfo.currency_id,
82
81
 
83
- description: 'This is build a instance for Third Party',
82
+ description: `This is an app launched by ${env.appName || 'Third Party'}`,
84
83
  userInfo: {
85
84
  userDid: orderInfo.customer_did!,
86
85
  email: userEmail,
87
- description: 'This is build a instance for Third Party',
86
+ description: `This is an app launched by ${env.appName || 'Third Party'}`,
88
87
  },
89
88
  deliveryParams: {
90
89
  blockletMetaUrl: vendor.metadata?.blockletMetaUrl,
@@ -125,117 +124,6 @@ export class VendorFulfillmentService {
125
124
  }
126
125
  }
127
126
 
128
- // Calculate commission data (no fulfillment, only calculate commission)
129
- private static async calculateCommissionData(checkoutSessionId: string): Promise<VendorFulfillmentResult[]> {
130
- try {
131
- logger.info('Calculating commission data for checkout session', { checkoutSessionId });
132
-
133
- const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId);
134
- if (!checkoutSession) {
135
- throw new Error(`CheckoutSession not found: ${checkoutSessionId}`);
136
- }
137
-
138
- const priceIds = checkoutSession.line_items.map((item: any) => item.price_id).filter(Boolean);
139
-
140
- if (priceIds.length === 0) {
141
- throw new Error(`No price IDs found in checkout session line items: ${checkoutSessionId}`);
142
- }
143
-
144
- // Find corresponding product_ids through price_ids
145
- const prices = await Price.findAll({
146
- where: { id: priceIds },
147
- attributes: ['id', 'product_id'],
148
- });
149
-
150
- // Create a map of price_id to line_item for amount calculation
151
- const priceToLineItemMap = new Map();
152
- checkoutSession.line_items.forEach((item: any) => {
153
- if (item.price_id) {
154
- priceToLineItemMap.set(item.price_id, item);
155
- }
156
- });
157
-
158
- const productIds = prices.map((price: any) => price.product_id).filter(Boolean);
159
-
160
- if (productIds.length === 0) {
161
- throw new Error(`No product IDs found from prices: ${checkoutSessionId}`);
162
- }
163
-
164
- // Get all products with vendor configurations
165
- const products = await Product.findAll({
166
- where: { id: productIds },
167
- attributes: ['id', 'vendor_config'],
168
- });
169
-
170
- const commissionResults: VendorFulfillmentResult[] = [];
171
-
172
- // Process each product separately
173
- for (const product of products) {
174
- if (!product.vendor_config || product.vendor_config.length === 0) {
175
- logger.debug('No vendor configuration found for product', {
176
- productId: product.id,
177
- checkoutSessionId,
178
- });
179
- // eslint-disable-next-line no-continue
180
- continue;
181
- }
182
-
183
- // Find all prices for this product
184
- const productPrices = prices.filter((price: any) => price.product_id === product.id);
185
-
186
- // Calculate total amount for this product across all line items
187
- let productTotalAmount = '0';
188
- for (const price of productPrices) {
189
- const lineItem = priceToLineItemMap.get(price.id);
190
- if (lineItem && lineItem.amount_total) {
191
- productTotalAmount = new BN(productTotalAmount).add(new BN(lineItem.amount_total || '0')).toString();
192
- }
193
- }
194
-
195
- // Calculate commission for each vendor of this product
196
- for (const vendorConfig of product.vendor_config) {
197
- const commissionAmount = calculateVendorCommission(
198
- productTotalAmount, // Use product-specific amount instead of total order amount
199
- vendorConfig.commission_rate || 0,
200
- vendorConfig.commission_type || 'percentage',
201
- vendorConfig.amount
202
- );
203
-
204
- commissionResults.push({
205
- vendorId: vendorConfig.vendor_key,
206
- vendorKey: vendorConfig.vendor_key,
207
- orderId: `commission_${checkoutSessionId}_${product.id}_${vendorConfig.vendor_id}`,
208
- status: 'completed',
209
- commissionAmount,
210
- commissionType: vendorConfig.commission_type || 'percentage',
211
- commissionRate: vendorConfig.commission_rate || 0,
212
- productId: product.id, // Add product ID for tracking
213
- });
214
- }
215
- }
216
-
217
- if (commissionResults.length === 0) {
218
- logger.warn('No vendor configurations found for any products', {
219
- productIds,
220
- checkoutSessionId,
221
- });
222
- }
223
-
224
- logger.info('Commission data calculated', {
225
- checkoutSessionId,
226
- commissionCount: commissionResults.length,
227
- });
228
-
229
- return commissionResults;
230
- } catch (error: any) {
231
- logger.error('Failed to calculate commission data', {
232
- checkoutSessionId,
233
- error: error.message,
234
- });
235
- throw error;
236
- }
237
- }
238
-
239
127
  static async createVendorPayouts(
240
128
  checkoutSessionId: string,
241
129
  fulfillmentResults?: VendorFulfillmentResult[]
@@ -257,13 +145,19 @@ export class VendorFulfillmentService {
257
145
  // If fulfillmentResults not provided, calculate commission info
258
146
  let commissionData = fulfillmentResults;
259
147
  if (!commissionData) {
260
- commissionData = await this.calculateCommissionData(checkoutSessionId);
148
+ commissionData = checkoutSession.vendor_info?.map((vendorInfo: any) => ({
149
+ vendorId: vendorInfo.vendor_id,
150
+ orderId: vendorInfo.order_id,
151
+ status: vendorInfo.status,
152
+ commissionAmount: vendorInfo.commissionAmount,
153
+ })) as VendorFulfillmentResult[];
261
154
  }
262
155
 
263
156
  const payoutPromises = commissionData
264
157
  .filter((result) => result.status !== 'failed' && new BN(result.commissionAmount).gt(new BN('0')))
265
158
  .map(async (result) => {
266
159
  const vendor = await ProductVendor.findByPk(result.vendorId);
160
+ const paymentMethod = await PaymentMethod.findByPk(paymentMethodId);
267
161
 
268
162
  const appPid = vendor?.app_pid;
269
163
  const destination = appPid || vendor?.metadata?.wallet_address || vendor?.metadata?.destination || '';
@@ -278,7 +172,7 @@ export class VendorFulfillmentService {
278
172
  customer_id: checkoutSession.customer_id || '',
279
173
  payment_intent_id: checkoutSession.payment_intent_id || '',
280
174
  payment_method_id: paymentMethodId,
281
- status: 'pending',
175
+ status: paymentMethod?.type === 'stripe' ? 'deferred' : 'pending',
282
176
  attempt_count: 0,
283
177
  attempted: false,
284
178
  vendor_info: {
@@ -373,11 +373,7 @@ export const handlePaymentSucceed = async (
373
373
  );
374
374
  }
375
375
 
376
- // Trigger vendor commission queue
377
- events.emit('vendor.commission.queued', `vendor-commission-${paymentIntent.id}`, {
378
- paymentIntentId: paymentIntent.id,
379
- retryOnError: true,
380
- });
376
+ // Trigger vendor commission queue by invoice.paid
381
377
 
382
378
  let invoice;
383
379
  if (paymentIntent.invoice_id) {
@@ -167,25 +167,27 @@ export const startVendorCommissionQueue = async () => {
167
167
  });
168
168
  };
169
169
 
170
- events.on('vendor.commission.queued', async (id, job, args = {}) => {
170
+ events.on('invoice.paid', async (invoice) => {
171
171
  try {
172
- const { ...extraArgs } = args;
173
- const exist = await vendorCommissionQueue.get(id);
172
+ const paymentIntent = await PaymentIntent.findOne({ where: { invoice_id: invoice.id } });
174
173
 
175
- if (!exist) {
176
- logger.info('Vendor commission job added successfully', { id });
177
- vendorCommissionQueue.push({
178
- id,
179
- job,
180
- ...extraArgs,
181
- });
182
- } else {
174
+ if (!paymentIntent) {
175
+ logger.warn('PaymentIntent not found', { id: invoice.id });
176
+ return;
177
+ }
178
+
179
+ const id = `vendor-commission-${paymentIntent.id}`;
180
+ const exist = await vendorCommissionQueue.get(id);
181
+ if (exist) {
183
182
  logger.info('Vendor commission job already exists, skipping', { id });
183
+ return;
184
184
  }
185
- } catch (error: any) {
186
- logger.error('Failed to handle vendor commission queue event', {
185
+
186
+ vendorCommissionQueue.push({
187
187
  id,
188
- error,
188
+ job: { paymentIntentId: paymentIntent.id, retryOnError: true },
189
189
  });
190
+ } catch (error) {
191
+ logger.error('Failed to trigger vendor commission queue', { invoiceId: invoice.id, error });
190
192
  }
191
193
  });
@@ -273,14 +273,14 @@ async function updateSingleVendorInfo(
273
273
  vendor_id: current[index]?.vendor_id || vendorId,
274
274
  order_id: update.order_id || current[index]?.order_id || '',
275
275
  status: update.status || current[index]?.status || 'pending',
276
- amount: update.amount || current[index]?.amount || '0',
276
+ amount: update.commissionAmount || current[index]?.amount || '0',
277
277
  } as VendorInfo;
278
278
  } else {
279
279
  current.push({
280
280
  vendor_id: vendorId,
281
281
  order_id: update.order_id || '',
282
282
  status: update.status || 'pending',
283
- amount: update.amount || '0',
283
+ amount: update.commissionAmount || '0',
284
284
  ...update,
285
285
  } as VendorInfo);
286
286
  }
@@ -1,6 +1,6 @@
1
1
  import { joinURL } from 'ufo';
2
+ import { VendorAuth } from '@blocklet/payment-vendor';
2
3
  import createQueue from '../../libs/queue';
3
- import { getBlockletJson } from '../../libs/util';
4
4
  import { CheckoutSession } from '../../store/models/checkout-session';
5
5
  import { ProductVendor } from '../../store/models';
6
6
  import { fulfillmentCoordinatorQueue } from './fulfillment-coordinator';
@@ -99,12 +99,17 @@ export const handleVendorStatusCheck = async (job: VendorStatusCheckJob) => {
99
99
  logger.info('found vendor url', { url, productVendor });
100
100
 
101
101
  if (url) {
102
- const vendorMeta = await getBlockletJson(url);
103
- const mountPoint = vendorMeta.componentMountPoints?.find((x: any) => x.appId === vendorId)?.mountPoint;
104
- const serverStatusUrl = joinURL(url, mountPoint, '/api/vendor/status', vendor.order_id);
105
-
106
- const result = await fetch(serverStatusUrl);
102
+ const serverStatusUrl = joinURL(
103
+ url,
104
+ productVendor?.metadata?.mountPoint,
105
+ '/api/vendor/status',
106
+ vendor.order_id
107
+ );
108
+ const { headers } = VendorAuth.signRequestWithHeaders({});
109
+
110
+ const result = await fetch(serverStatusUrl, { headers });
107
111
  const data = await result.json();
112
+
108
113
  vendor.app_url = data?.appUrl || '';
109
114
  }
110
115
  }
@@ -22,6 +22,9 @@ const createVendorSchema = Joi.object({
22
22
  app_url: Joi.string().uri().max(512).required(),
23
23
  app_pid: Joi.string().max(255).allow('').optional(),
24
24
  app_logo: Joi.string().max(512).allow('').optional(),
25
+ vendor_did: Joi.string()
26
+ .pattern(/^(did:abt:)?[1-9A-HJ-NP-Za-km-z]{37}$/)
27
+ .required(),
25
28
  status: Joi.string().valid('active', 'inactive').default('active'),
26
29
  metadata: MetadataSchema,
27
30
  }).unknown(false);
@@ -33,6 +36,9 @@ const updateVendorSchema = Joi.object({
33
36
  app_url: Joi.string().uri().max(512).optional(),
34
37
  app_pid: Joi.string().max(255).allow('').optional(),
35
38
  app_logo: Joi.string().max(512).allow('').optional(),
39
+ vendor_did: Joi.string()
40
+ .pattern(/^(did:abt:)?[1-9A-HJ-NP-Za-km-z]{37}$/)
41
+ .required(),
36
42
  status: Joi.string().valid('active', 'inactive').optional(),
37
43
  metadata: MetadataSchema,
38
44
  }).unknown(true);
@@ -139,6 +145,7 @@ async function createVendor(req: any, res: any) {
139
145
  name,
140
146
  description,
141
147
  app_url: appUrl,
148
+ vendor_did: vendorDid,
142
149
  metadata,
143
150
  app_pid: appPid,
144
151
  app_logo: appLogo,
@@ -158,6 +165,7 @@ async function createVendor(req: any, res: any) {
158
165
  name,
159
166
  description,
160
167
  app_url: appUrl,
168
+ vendor_did: vendorDid?.replace('did:abt:', '').trim(),
161
169
  status: status || 'active',
162
170
  app_pid: appPid,
163
171
  app_logo: appLogo,
@@ -195,6 +203,7 @@ async function updateVendor(req: any, res: any) {
195
203
  name,
196
204
  description,
197
205
  app_url: appUrl,
206
+ vendor_did: vendorDid,
198
207
  status,
199
208
  metadata,
200
209
  app_pid: appPid,
@@ -214,6 +223,7 @@ async function updateVendor(req: any, res: any) {
214
223
  name,
215
224
  description,
216
225
  app_url: appUrl,
226
+ vendor_did: vendorDid,
217
227
  status,
218
228
  metadata,
219
229
  app_pid: appPid,
@@ -276,7 +286,15 @@ async function getVendorStatusByVendorId(vendorId: string, orderId: string, isDe
276
286
  }
277
287
 
278
288
  const vendor = await ProductVendor.findByPk(vendorId);
279
- const url = vendor?.app_url ? joinURL(vendor.app_url, '/api/vendor/', isDetail ? 'orders' : 'status', orderId) : null;
289
+ const url = vendor?.app_url
290
+ ? joinURL(
291
+ vendor.app_url,
292
+ vendor.metadata?.mountPoint || '',
293
+ '/api/vendor/',
294
+ isDetail ? 'orders' : 'status',
295
+ orderId
296
+ )
297
+ : null;
280
298
 
281
299
  const { headers } = VendorAuth.signRequestWithHeaders({});
282
300
  const name = vendor?.name;
@@ -427,6 +445,7 @@ async function redirectToVendor(req: any, res: any) {
427
445
 
428
446
  const router = Router();
429
447
 
448
+ // FIXME: Authentication not yet added, awaiting implementation @Pengfei
430
449
  router.get('/order/:sessionId/status', validateParams(sessionIdParamSchema), getVendorFulfillmentStatus);
431
450
  router.get('/order/:sessionId/detail', validateParams(sessionIdParamSchema), getVendorFulfillmentDetail);
432
451
 
@@ -0,0 +1,20 @@
1
+ import { safeApplyColumnChanges, type Migration } from '../migrate';
2
+
3
+ export const up: Migration = async ({ context }) => {
4
+ // Add vendor_did column to product_vendors table
5
+ await safeApplyColumnChanges(context, {
6
+ product_vendors: [
7
+ {
8
+ name: 'vendor_did',
9
+ field: {
10
+ type: 'STRING',
11
+ allowNull: true,
12
+ },
13
+ },
14
+ ],
15
+ });
16
+ };
17
+
18
+ export const down: Migration = async ({ context }) => {
19
+ await context.removeColumn('product_vendors', 'vendor_did');
20
+ };
@@ -38,7 +38,7 @@ export class Payout extends Model<InferAttributes<Payout>, InferCreationAttribut
38
38
  commission_amount: string;
39
39
  };
40
40
 
41
- declare status: LiteralUnion<'pending' | 'paid' | 'failed' | 'canceled' | 'in_transit', string>;
41
+ declare status: LiteralUnion<'pending' | 'paid' | 'failed' | 'canceled' | 'in_transit' | 'deferred', string>;
42
42
 
43
43
  // retry logic
44
44
  declare failure_message?: string;
@@ -118,7 +118,7 @@ export class Payout extends Model<InferAttributes<Payout>, InferCreationAttribut
118
118
  allowNull: true,
119
119
  },
120
120
  status: {
121
- type: DataTypes.ENUM('paid', 'pending', 'failed', 'canceled', 'in_transit'),
121
+ type: DataTypes.ENUM('paid', 'pending', 'failed', 'canceled', 'in_transit', 'deferred'),
122
122
  allowNull: false,
123
123
  },
124
124
  failure_message: {
@@ -15,6 +15,7 @@ export class ProductVendor extends Model<InferAttributes<ProductVendor>, InferCr
15
15
  declare app_url: string;
16
16
  declare app_pid?: string;
17
17
  declare app_logo?: string;
18
+ declare vendor_did?: string;
18
19
 
19
20
  declare status: 'active' | 'inactive';
20
21
  declare metadata: Record<string, any>;
@@ -60,6 +61,10 @@ export class ProductVendor extends Model<InferAttributes<ProductVendor>, InferCr
60
61
  type: DataTypes.STRING(512),
61
62
  allowNull: true,
62
63
  },
64
+ vendor_did: {
65
+ type: DataTypes.STRING(255),
66
+ allowNull: true,
67
+ },
63
68
  status: {
64
69
  type: DataTypes.ENUM('active', 'inactive'),
65
70
  allowNull: false,
@@ -60,6 +60,7 @@ export class Product extends Model<InferAttributes<Product>, InferCreationAttrib
60
60
  commission_rate?: number;
61
61
  commission_type?: 'percentage' | 'fixed_amount';
62
62
  amount?: string;
63
+ commissionAmount?: string;
63
64
  custom_params?: Record<string, any>;
64
65
  }>;
65
66
 
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.9
17
+ version: 1.20.10
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.9",
3
+ "version": "1.20.10",
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,8 @@
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.9",
60
- "@blocklet/payment-vendor": "1.20.9",
59
+ "@blocklet/payment-react": "1.20.10",
60
+ "@blocklet/payment-vendor": "1.20.10",
61
61
  "@blocklet/sdk": "^1.16.52-beta-20250912-112002-e3499e9c",
62
62
  "@blocklet/ui-react": "^3.1.40",
63
63
  "@blocklet/uploader": "^0.2.10",
@@ -126,7 +126,7 @@
126
126
  "devDependencies": {
127
127
  "@abtnode/types": "^1.16.52-beta-20250912-112002-e3499e9c",
128
128
  "@arcblock/eslint-config-ts": "^0.3.3",
129
- "@blocklet/payment-types": "1.20.9",
129
+ "@blocklet/payment-types": "1.20.10",
130
130
  "@types/cookie-parser": "^1.4.9",
131
131
  "@types/cors": "^2.8.19",
132
132
  "@types/debug": "^4.1.12",
@@ -173,5 +173,5 @@
173
173
  "parser": "typescript"
174
174
  }
175
175
  },
176
- "gitHead": "e89c0a9b6a3f3b7f7cb3cc0845b1775c1f587f6e"
176
+ "gitHead": "1659d63120ced92167ec8681e51db96801b910ec"
177
177
  }
@@ -905,6 +905,9 @@ export default flat({
905
905
  appUrlRequired: 'App URL is required',
906
906
  appUrlInvalid: 'Please enter a valid URL starting with http:// or https://',
907
907
  appUrlHelp: 'The base URL of the vendor application',
908
+ vendorDid: 'Vendor DID',
909
+ vendorDidInvalid: 'Please enter a valid DID',
910
+ vendorDidHelp: 'Optional DID address for the vendor',
908
911
  webhookPath: 'Webhook Path',
909
912
  webhookPathInvalid: 'Please enter a valid path starting with /',
910
913
  webhookPathHelp: 'Optional webhook callback path (e.g., /webhooks/status)',
@@ -881,8 +881,11 @@ export default flat({
881
881
  displayNameHelp: '此数据会展示到付款成功后的安装界面',
882
882
  appUrl: '应用地址',
883
883
  appUrlRequired: '应用地址是必填项',
884
- appUrlInvalid: '请输入以http://或https://开头的有效URL',
885
- appUrlHelp: '供应商应用的基础URL',
884
+ appUrlInvalid: '请输入有效的 URL,以 http:// 或 https:// 开头',
885
+ appUrlHelp: '供应商应用的基础 URL 地址',
886
+ vendorDid: '供应商 DID',
887
+ vendorDidInvalid: '请输入有效的 DID',
888
+ vendorDidHelp: '供应商的可选 DID 地址',
886
889
  webhookPath: 'Webhook路径',
887
890
  webhookPathInvalid: '请输入以/开头的有效路径',
888
891
  webhookPathHelp: '可选的webhook回调路径(如:/webhooks/status)',
@@ -30,6 +30,7 @@ interface Vendor {
30
30
  name: string;
31
31
  description: string;
32
32
  app_url: string;
33
+ vendor_did?: string;
33
34
  status: 'active' | 'inactive';
34
35
  metadata: Record<string, any>;
35
36
  created_at: string;
@@ -49,6 +50,7 @@ interface VendorFormData {
49
50
  name: string;
50
51
  description: string;
51
52
  app_url: string;
53
+ vendor_did?: string;
52
54
  status: 'active' | 'inactive';
53
55
  metadata: Array<{ key: string; value: string }>;
54
56
  app_pid?: string;
@@ -82,6 +84,7 @@ export default function VendorCreate({
82
84
  name: '',
83
85
  description: '',
84
86
  app_url: '',
87
+ vendor_did: '',
85
88
  status: 'inactive' as const,
86
89
  metadata: [{ key: 'blockletMetaUrl', value: '' }],
87
90
  app_pid: '',
@@ -114,6 +117,16 @@ export default function VendorCreate({
114
117
  }
115
118
  };
116
119
 
120
+ const validateDid = (did: string | undefined) => {
121
+ if (!did) return true; // DID 是可选的
122
+ // DID 格式验证
123
+ const didPattern = /^(did:abt:)?[1-9A-HJ-NP-Za-km-z]{37}$/;
124
+ if (!didPattern.test(did.trim())) {
125
+ return t('admin.vendor.vendorDidInvalid');
126
+ }
127
+ return true;
128
+ };
129
+
117
130
  const onSubmit = async (data: VendorFormData) => {
118
131
  try {
119
132
  setLoading(true);
@@ -144,6 +157,7 @@ export default function VendorCreate({
144
157
 
145
158
  const submitData = {
146
159
  ...restData,
160
+ vendor_did: restData.vendor_did?.replace('did:abt:', '').trim(),
147
161
  metadata: metadataObj,
148
162
  };
149
163
 
@@ -167,6 +181,10 @@ export default function VendorCreate({
167
181
  // 从响应中获取appPid和appLogo
168
182
  const blockletInfo = await response.json();
169
183
  if (blockletInfo) {
184
+ const component = blockletInfo.componentMountPoints?.find((x: any) => x.did === submitData.vendor_did);
185
+ if (component && !['', '/'].includes(component.mountPoint) && submitData.metadata) {
186
+ submitData.metadata.mountPoint = component.mountPoint;
187
+ }
170
188
  submitData.app_pid = blockletInfo.pid || blockletInfo.appPid;
171
189
  submitData.app_logo = blockletInfo.logo || blockletInfo.appLogo;
172
190
  }
@@ -307,6 +325,24 @@ export default function VendorCreate({
307
325
  )}
308
326
  />
309
327
 
328
+ <Controller
329
+ name="vendor_did"
330
+ control={control}
331
+ rules={{
332
+ required: t('admin.vendor.vendorDidRequired'),
333
+ validate: validateDid,
334
+ }}
335
+ render={({ field }) => (
336
+ <TextField
337
+ {...field}
338
+ label={t('admin.vendor.vendorDid')}
339
+ fullWidth
340
+ error={!!errors.vendor_did}
341
+ helperText={errors.vendor_did?.message || t('admin.vendor.vendorDidHelp')}
342
+ />
343
+ )}
344
+ />
345
+
310
346
  <MetadataForm title={t('common.metadata.label')} />
311
347
 
312
348
  <Controller