payment-kit 1.20.5 → 1.20.7

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.
Files changed (39) hide show
  1. package/api/src/crons/index.ts +11 -3
  2. package/api/src/index.ts +18 -14
  3. package/api/src/libs/env.ts +7 -0
  4. package/api/src/libs/url.ts +77 -0
  5. package/api/src/libs/vendor/adapters/factory.ts +40 -0
  6. package/api/src/libs/vendor/adapters/launcher-adapter.ts +179 -0
  7. package/api/src/libs/vendor/adapters/types.ts +91 -0
  8. package/api/src/libs/vendor/fulfillment.ts +317 -0
  9. package/api/src/queues/payment.ts +14 -10
  10. package/api/src/queues/payout.ts +1 -0
  11. package/api/src/queues/vendor/commission.ts +192 -0
  12. package/api/src/queues/vendor/fulfillment-coordinator.ts +625 -0
  13. package/api/src/queues/vendor/fulfillment.ts +98 -0
  14. package/api/src/queues/vendor/status-check.ts +178 -0
  15. package/api/src/routes/checkout-sessions.ts +12 -0
  16. package/api/src/routes/index.ts +2 -0
  17. package/api/src/routes/products.ts +72 -1
  18. package/api/src/routes/vendor.ts +527 -0
  19. package/api/src/store/migrations/20250820-add-product-vendor.ts +102 -0
  20. package/api/src/store/migrations/20250822-add-vendor-config-to-products.ts +56 -0
  21. package/api/src/store/models/checkout-session.ts +84 -18
  22. package/api/src/store/models/index.ts +3 -0
  23. package/api/src/store/models/payout.ts +11 -0
  24. package/api/src/store/models/product-vendor.ts +118 -0
  25. package/api/src/store/models/product.ts +15 -0
  26. package/blocklet.yml +8 -2
  27. package/doc/vendor_fulfillment_system.md +929 -0
  28. package/package.json +5 -4
  29. package/src/components/collapse.tsx +1 -0
  30. package/src/components/product/edit.tsx +9 -0
  31. package/src/components/product/form.tsx +11 -0
  32. package/src/components/product/vendor-config.tsx +249 -0
  33. package/src/components/vendor/actions.tsx +145 -0
  34. package/src/locales/en.tsx +89 -0
  35. package/src/locales/zh.tsx +89 -0
  36. package/src/pages/admin/products/index.tsx +11 -1
  37. package/src/pages/admin/products/products/detail.tsx +79 -2
  38. package/src/pages/admin/products/vendors/create.tsx +418 -0
  39. package/src/pages/admin/products/vendors/index.tsx +313 -0
@@ -0,0 +1,317 @@
1
+ import { BN } from '@ocap/util';
2
+ 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
+ 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';
10
+ import logger from '../logger';
11
+
12
+ export interface VendorFulfillmentResult {
13
+ vendorId: string;
14
+ vendorKey: string;
15
+ orderId: string;
16
+ status: 'pending' | 'processing' | 'completed' | 'failed';
17
+ commissionAmount: string;
18
+ commissionType: 'percentage' | 'fixed_amount';
19
+ commissionRate: number;
20
+ productId?: string; // Add product ID for multi-product tracking
21
+ errorMessage?: string;
22
+ serviceUrl?: string;
23
+ estimatedTime?: number;
24
+ vendorOrderId?: string;
25
+ progress?: number;
26
+ orderDetails?: any;
27
+ installationInfo?: any;
28
+ }
29
+
30
+ export interface MultiVendorFulfillmentResult {
31
+ checkoutSessionId: string;
32
+ success: boolean;
33
+ results: VendorFulfillmentResult[];
34
+ totalCommissionAmount: string;
35
+ errorMessage?: string;
36
+ }
37
+
38
+ export class VendorFulfillmentService {
39
+ // Execute single vendor fulfillment (public interface for coordinator system)
40
+ static async fulfillSingleVendorOrder(
41
+ orderInfo: {
42
+ checkoutSessionId: string;
43
+ amount_total: string;
44
+ customer_id: string;
45
+ payment_intent_id: string | null;
46
+ currency_id: string;
47
+ customer_did: string;
48
+ },
49
+ vendorConfig: any
50
+ ): Promise<VendorFulfillmentResult> {
51
+ try {
52
+ const vendor = await ProductVendor.findByPk(vendorConfig.vendor_id);
53
+
54
+ if (!vendor) {
55
+ throw new Error(`Vendor not found: ${vendorConfig.vendor_id}`);
56
+ }
57
+
58
+ const adapter = this.getVendorAdapter(vendor.vendor_key);
59
+
60
+ // Calculate commission amount
61
+ const commissionRate = vendorConfig.commission_rate || vendor.default_commission_rate;
62
+ const commissionType = vendorConfig.commission_type || vendor.default_commission_type;
63
+ const commissionAmount = calculateVendorCommission(
64
+ orderInfo.amount_total,
65
+ commissionRate,
66
+ commissionType,
67
+ vendorConfig.amount
68
+ );
69
+
70
+ const userEmail = (await Customer.findByPk(orderInfo.customer_id))?.email || '';
71
+
72
+ const fulfillmentResult = await adapter.fulfillOrder({
73
+ checkoutSessionId: orderInfo.checkoutSessionId,
74
+ productCode: vendorConfig.vendor_id,
75
+ customerId: orderInfo.customer_id || '',
76
+ quantity: 1,
77
+ paymentIntentId: orderInfo.payment_intent_id || '',
78
+ amount: orderInfo.amount_total,
79
+ currency: orderInfo.currency_id,
80
+
81
+ description: 'This is build a instance for Third Party',
82
+ userInfo: {
83
+ userDid: orderInfo.customer_did!,
84
+ email: userEmail,
85
+ description: 'This is build a instance for Third Party',
86
+ },
87
+ deliveryParams: {
88
+ blockletMetaUrl: vendor.metadata?.blockletMetaUrl,
89
+ customParams: vendorConfig.custom_params,
90
+ },
91
+ });
92
+
93
+ logger.info('Single vendor fulfillment completed', {
94
+ vendorId: vendorConfig.vendor_id,
95
+ orderId: fulfillmentResult.orderId,
96
+ status: fulfillmentResult.status,
97
+ commissionAmount,
98
+ });
99
+
100
+ return {
101
+ vendorId: vendorConfig.vendor_id,
102
+ vendorKey: vendorConfig.vendor_id,
103
+ orderId: fulfillmentResult.orderId,
104
+ status: fulfillmentResult.status,
105
+ commissionAmount,
106
+ commissionType,
107
+ commissionRate,
108
+ errorMessage: fulfillmentResult.message,
109
+ serviceUrl: fulfillmentResult.serviceUrl,
110
+ estimatedTime: fulfillmentResult.estimatedTime,
111
+ vendorOrderId: fulfillmentResult.vendorOrderId,
112
+ progress: fulfillmentResult.progress,
113
+ orderDetails: fulfillmentResult.orderDetails,
114
+ installationInfo: fulfillmentResult.installationInfo,
115
+ };
116
+ } catch (error: any) {
117
+ logger.error('Single vendor fulfillment failed', {
118
+ vendorId: vendorConfig.vendor_id,
119
+ error: error.message,
120
+ });
121
+
122
+ throw error;
123
+ }
124
+ }
125
+
126
+ // Calculate commission data (no fulfillment, only calculate commission)
127
+ private static async calculateCommissionData(checkoutSessionId: string): Promise<VendorFulfillmentResult[]> {
128
+ try {
129
+ logger.info('Calculating commission data for checkout session', { checkoutSessionId });
130
+
131
+ const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId);
132
+ if (!checkoutSession) {
133
+ throw new Error(`CheckoutSession not found: ${checkoutSessionId}`);
134
+ }
135
+
136
+ const priceIds = checkoutSession.line_items.map((item: any) => item.price_id).filter(Boolean);
137
+
138
+ if (priceIds.length === 0) {
139
+ throw new Error(`No price IDs found in checkout session line items: ${checkoutSessionId}`);
140
+ }
141
+
142
+ // Find corresponding product_ids through price_ids
143
+ const prices = await Price.findAll({
144
+ where: { id: priceIds },
145
+ attributes: ['id', 'product_id'],
146
+ });
147
+
148
+ // Create a map of price_id to line_item for amount calculation
149
+ const priceToLineItemMap = new Map();
150
+ checkoutSession.line_items.forEach((item: any) => {
151
+ if (item.price_id) {
152
+ priceToLineItemMap.set(item.price_id, item);
153
+ }
154
+ });
155
+
156
+ const productIds = prices.map((price: any) => price.product_id).filter(Boolean);
157
+
158
+ if (productIds.length === 0) {
159
+ throw new Error(`No product IDs found from prices: ${checkoutSessionId}`);
160
+ }
161
+
162
+ // Get all products with vendor configurations
163
+ const products = await Product.findAll({
164
+ where: { id: productIds },
165
+ attributes: ['id', 'vendor_config'],
166
+ });
167
+
168
+ const commissionResults: VendorFulfillmentResult[] = [];
169
+
170
+ // Process each product separately
171
+ for (const product of products) {
172
+ if (!product.vendor_config || product.vendor_config.length === 0) {
173
+ logger.debug('No vendor configuration found for product', {
174
+ productId: product.id,
175
+ checkoutSessionId,
176
+ });
177
+ // eslint-disable-next-line no-continue
178
+ continue;
179
+ }
180
+
181
+ // Find all prices for this product
182
+ const productPrices = prices.filter((price: any) => price.product_id === product.id);
183
+
184
+ // Calculate total amount for this product across all line items
185
+ let productTotalAmount = '0';
186
+ for (const price of productPrices) {
187
+ const lineItem = priceToLineItemMap.get(price.id);
188
+ if (lineItem && lineItem.amount_total) {
189
+ productTotalAmount = new BN(productTotalAmount).add(new BN(lineItem.amount_total || '0')).toString();
190
+ }
191
+ }
192
+
193
+ // Calculate commission for each vendor of this product
194
+ for (const vendorConfig of product.vendor_config) {
195
+ const commissionAmount = calculateVendorCommission(
196
+ productTotalAmount, // Use product-specific amount instead of total order amount
197
+ vendorConfig.commission_rate || 0,
198
+ vendorConfig.commission_type || 'percentage',
199
+ vendorConfig.amount
200
+ );
201
+
202
+ commissionResults.push({
203
+ vendorId: vendorConfig.vendor_key || vendorConfig.vendor_id,
204
+ vendorKey: vendorConfig.vendor_key || vendorConfig.vendor_id,
205
+ orderId: `commission_${checkoutSessionId}_${product.id}_${vendorConfig.vendor_id}`,
206
+ status: 'completed',
207
+ commissionAmount,
208
+ commissionType: vendorConfig.commission_type || 'percentage',
209
+ commissionRate: vendorConfig.commission_rate || 0,
210
+ productId: product.id, // Add product ID for tracking
211
+ });
212
+ }
213
+ }
214
+
215
+ if (commissionResults.length === 0) {
216
+ logger.warn('No vendor configurations found for any products', {
217
+ productIds,
218
+ checkoutSessionId,
219
+ });
220
+ }
221
+
222
+ logger.info('Commission data calculated', {
223
+ checkoutSessionId,
224
+ commissionCount: commissionResults.length,
225
+ });
226
+
227
+ return commissionResults;
228
+ } catch (error: any) {
229
+ logger.error('Failed to calculate commission data', {
230
+ checkoutSessionId,
231
+ error: error.message,
232
+ });
233
+ throw error;
234
+ }
235
+ }
236
+
237
+ static async createVendorPayouts(
238
+ checkoutSessionId: string,
239
+ fulfillmentResults?: VendorFulfillmentResult[]
240
+ ): Promise<void> {
241
+ try {
242
+ const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId);
243
+ if (!checkoutSession) {
244
+ throw new Error(`CheckoutSession not found: ${checkoutSessionId}`);
245
+ }
246
+
247
+ let paymentMethodId = '';
248
+ if (checkoutSession.payment_intent_id) {
249
+ const paymentIntent = await PaymentIntent.findByPk(checkoutSession.payment_intent_id);
250
+ if (paymentIntent) {
251
+ paymentMethodId = paymentIntent.payment_method_id;
252
+ }
253
+ }
254
+
255
+ // If fulfillmentResults not provided, calculate commission info
256
+ let commissionData = fulfillmentResults;
257
+ if (!commissionData) {
258
+ commissionData = await this.calculateCommissionData(checkoutSessionId);
259
+ }
260
+
261
+ const payoutPromises = commissionData
262
+ .filter((result) => result.status !== 'failed' && new BN(result.commissionAmount).gt(new BN('0')))
263
+ .map(async (result) => {
264
+ const vendor = await ProductVendor.findByPk(result.vendorId);
265
+
266
+ const appPid = vendor?.app_pid;
267
+ const destination = appPid || vendor?.metadata?.wallet_address || vendor?.metadata?.destination || '';
268
+
269
+ await Payout.create({
270
+ livemode: checkoutSession.livemode,
271
+ automatic: true,
272
+ description: `Vendor commission for ${result.vendorId}${appPid ? ` (${appPid})` : ''}`,
273
+ destination,
274
+ amount: result.commissionAmount,
275
+ currency_id: checkoutSession.currency_id,
276
+ customer_id: checkoutSession.customer_id || '',
277
+ payment_intent_id: checkoutSession.payment_intent_id || '',
278
+ payment_method_id: paymentMethodId,
279
+ status: 'pending',
280
+ attempt_count: 0,
281
+ attempted: false,
282
+ vendor_info: {
283
+ vendor_id: result.vendorId,
284
+ app_pid: appPid,
285
+ order_id: result.orderId,
286
+ commission_amount: result.commissionAmount,
287
+ },
288
+ });
289
+ });
290
+
291
+ await Promise.all(payoutPromises);
292
+
293
+ logger.info('Vendor payouts created', {
294
+ checkoutSessionId,
295
+ payoutCount: commissionData.filter((r) => r.status !== 'failed').length,
296
+ });
297
+ } catch (error: any) {
298
+ logger.error('Failed to create vendor payouts', {
299
+ checkoutSessionId,
300
+ error: error.message,
301
+ });
302
+ throw error;
303
+ }
304
+ }
305
+
306
+ static getVendorAdapter(vendorKey: string) {
307
+ try {
308
+ return VendorAdapterFactory.create(vendorKey);
309
+ } catch (error: any) {
310
+ logger.error('Failed to get vendor adapter', {
311
+ vendorKey,
312
+ error: error.message,
313
+ });
314
+ throw error;
315
+ }
316
+ }
317
+ }
@@ -26,11 +26,13 @@ import { MAX_RETRY_COUNT, MIN_RETRY_MAIL, getNextRetry } from '../libs/util';
26
26
  import { CheckoutSession } from '../store/models/checkout-session';
27
27
  import { Customer } from '../store/models/customer';
28
28
  import { Invoice } from '../store/models/invoice';
29
+
29
30
  import { PaymentCurrency } from '../store/models/payment-currency';
30
31
  import { PaymentIntent } from '../store/models/payment-intent';
31
32
  import { PaymentMethod } from '../store/models/payment-method';
32
33
  import { Payout } from '../store/models/payout';
33
34
  import { Price } from '../store/models/price';
35
+
34
36
  import { Subscription } from '../store/models/subscription';
35
37
  import { SubscriptionItem } from '../store/models/subscription-item';
36
38
  import type { EVMChainType, PaymentError, PaymentSettings } from '../store/models/types';
@@ -371,13 +373,11 @@ export const handlePaymentSucceed = async (
371
373
  );
372
374
  }
373
375
 
374
- const exist = await depositVaultQueue.get(`deposit-vault-${paymentIntent.currency_id}`);
375
- if (!exist) {
376
- depositVaultQueue.push({
377
- id: `deposit-vault-${paymentIntent.currency_id}`,
378
- job: { currencyId: paymentIntent.currency_id },
379
- });
380
- }
376
+ // Trigger vendor commission queue
377
+ events.emit('vendor.commission.queued', `vendor-commission-${paymentIntent.id}`, {
378
+ paymentIntentId: paymentIntent.id,
379
+ retryOnError: true,
380
+ });
381
381
 
382
382
  let invoice;
383
383
  if (paymentIntent.invoice_id) {
@@ -929,19 +929,21 @@ export const handlePayment = async (job: PaymentJob) => {
929
929
 
930
930
  const payer = paymentSettings?.payment_method_options.arcblock?.payer as string;
931
931
 
932
- // check balance before capture with transaction
933
932
  result = await isDelegationSufficientForPayment({
934
933
  paymentMethod,
935
934
  paymentCurrency,
936
935
  userDid: payer || customer.did,
937
936
  amount: paymentIntent.amount,
938
937
  });
938
+
939
939
  if (result.sufficient === false) {
940
- logger.error('PaymentIntent capture aborted on preCheck', { id: paymentIntent.id, result });
940
+ logger.error('PaymentIntent capture aborted on preCheck', {
941
+ id: paymentIntent.id,
942
+ result,
943
+ });
941
944
  throw new CustomError(result.reason, 'payer balance or delegation not sufficient for this payment');
942
945
  }
943
946
 
944
- // do the capture
945
947
  const signed = await client.signTransferV2Tx({
946
948
  tx: {
947
949
  itx: {
@@ -963,8 +965,10 @@ export const handlePayment = async (job: PaymentJob) => {
963
965
  wallet,
964
966
  delegator: result.delegator,
965
967
  });
968
+
966
969
  // @ts-ignore
967
970
  const { buffer } = await client.encodeTransferV2Tx({ tx: signed });
971
+
968
972
  const txHash = await client.sendTransferV2Tx(
969
973
  // @ts-ignore
970
974
  { tx: signed, wallet, delegator: result.delegator },
@@ -78,6 +78,7 @@ async function processArcblockPayout(payout: Payout, paymentMethod: PaymentMetho
78
78
  },
79
79
  wallet,
80
80
  });
81
+
81
82
  // @ts-ignore
82
83
  const { buffer } = await client.encodeTransferV2Tx({ tx: signed });
83
84
  // @ts-ignore
@@ -0,0 +1,192 @@
1
+ import { events } from '../../libs/event';
2
+ import logger from '../../libs/logger';
3
+ import createQueue from '../../libs/queue';
4
+ import { CheckoutSession } from '../../store/models/checkout-session';
5
+ import { PaymentIntent } from '../../store/models/payment-intent';
6
+ import { Product } from '../../store/models/product';
7
+ import { Price } from '../../store/models/price';
8
+ import { depositVaultQueue } from '../payment';
9
+ import { startVendorFulfillment, triggerCommissionProcess, triggerCoordinatorCheck } from './fulfillment-coordinator';
10
+
11
+ type VendorCommissionJob = {
12
+ paymentIntentId: string;
13
+ retryOnError?: boolean;
14
+ };
15
+
16
+ async function checkIfPaymentIntentHasVendors(
17
+ paymentIntent: PaymentIntent,
18
+ checkoutSession: CheckoutSession
19
+ ): Promise<boolean> {
20
+ try {
21
+ // Extract price_ids from line_items, then find corresponding product_ids
22
+ const priceIds = checkoutSession.line_items.map((item: any) => item.price_id).filter(Boolean);
23
+
24
+ if (priceIds.length === 0) {
25
+ logger.warn('No price IDs found in checkout session line items', {
26
+ paymentIntentId: paymentIntent.id,
27
+ checkoutSessionId: checkoutSession.id,
28
+ });
29
+ return false;
30
+ }
31
+
32
+ // Find corresponding product_ids through price_ids
33
+ const prices = await Price.findAll({
34
+ where: { id: priceIds },
35
+ attributes: ['id', 'product_id'],
36
+ });
37
+
38
+ const productIds = prices.map((price) => price.product_id).filter(Boolean);
39
+
40
+ if (productIds.length === 0) {
41
+ logger.warn('No product IDs found from prices', {
42
+ paymentIntentId: paymentIntent.id,
43
+ checkoutSessionId: checkoutSession.id,
44
+ priceIds,
45
+ });
46
+ return false;
47
+ }
48
+
49
+ // Get product information
50
+ const products = await Product.findAll({
51
+ where: { id: productIds },
52
+ attributes: ['id', 'vendor_config'],
53
+ });
54
+
55
+ // Check if any product has vendor configuration
56
+ const hasVendorConfig = products.some((product) => product.vendor_config && product.vendor_config.length > 0);
57
+ return hasVendorConfig;
58
+ } catch (error: any) {
59
+ logger.error('Failed to check vendor configuration', {
60
+ paymentIntentId: paymentIntent.id,
61
+ error: error.message,
62
+ });
63
+ return false;
64
+ }
65
+ }
66
+
67
+ async function executeDirectDepositVault(paymentIntent: PaymentIntent): Promise<void> {
68
+ const exist = await depositVaultQueue.get(`deposit-vault-${paymentIntent.currency_id}`);
69
+ if (!exist) {
70
+ depositVaultQueue.push({
71
+ id: `deposit-vault-${paymentIntent.currency_id}`,
72
+ job: { currencyId: paymentIntent.currency_id },
73
+ });
74
+ logger.info('Deposit vault job queued', {
75
+ paymentIntentId: paymentIntent.id,
76
+ currencyId: paymentIntent.currency_id,
77
+ });
78
+ } else {
79
+ logger.info('Deposit vault job already exists', {
80
+ paymentIntentId: paymentIntent.id,
81
+ currencyId: paymentIntent.currency_id,
82
+ });
83
+ }
84
+ }
85
+
86
+ export const handleVendorCommission = async (job: VendorCommissionJob) => {
87
+ logger.info('handle vendor commission', job);
88
+
89
+ let checkoutSession: CheckoutSession | null = null;
90
+ try {
91
+ const paymentIntent = await PaymentIntent.findByPk(job.paymentIntentId);
92
+ if (!paymentIntent) {
93
+ logger.warn('PaymentIntent not found', { id: job.paymentIntentId });
94
+ return;
95
+ }
96
+
97
+ // Find CheckoutSession through PaymentIntent
98
+ checkoutSession = await CheckoutSession.findByPaymentIntentId(paymentIntent.id);
99
+ if (!checkoutSession) {
100
+ await executeDirectDepositVault(paymentIntent);
101
+ return;
102
+ }
103
+
104
+ const hasVendorConfig = await checkIfPaymentIntentHasVendors(paymentIntent, checkoutSession);
105
+ if (!hasVendorConfig) {
106
+ await executeDirectDepositVault(paymentIntent);
107
+ return;
108
+ }
109
+
110
+ logger.info('Vendor configuration found, starting fulfillment process', {
111
+ paymentIntentId: paymentIntent.id,
112
+ });
113
+
114
+ if (checkoutSession.fulfillment_status === 'completed') {
115
+ logger.info('CheckoutSession already completed, directly trigger commission process', {
116
+ checkoutSessionId: checkoutSession.id,
117
+ });
118
+ await triggerCommissionProcess(checkoutSession.id, paymentIntent.id);
119
+ return;
120
+ }
121
+
122
+ await startVendorFulfillment(checkoutSession.id, paymentIntent.id);
123
+ } catch (error: any) {
124
+ logger.error('Vendor commission decision failed, fallback to direct deposit vault', {
125
+ paymentIntentId: job.paymentIntentId,
126
+ error: error.message,
127
+ stack: error.stack,
128
+ });
129
+
130
+ if (!checkoutSession) {
131
+ logger.error('CheckoutSession not found via any method[handleVendorCommission]', {
132
+ paymentIntentId: job.paymentIntentId,
133
+ });
134
+ return;
135
+ }
136
+
137
+ try {
138
+ triggerCoordinatorCheck(checkoutSession.id, job.paymentIntentId, 'vendor_commission_decision_failed');
139
+ } catch (err: any) {
140
+ logger.error('Failed to trigger coordinator check[handleVendorCommission]', { error: err.message });
141
+ }
142
+ }
143
+ };
144
+
145
+ export const vendorCommissionQueue = createQueue<VendorCommissionJob>({
146
+ name: 'vendor-commission',
147
+ onJob: handleVendorCommission,
148
+ options: {
149
+ concurrency: 3,
150
+ maxRetries: 3,
151
+ enableScheduledJob: true,
152
+ },
153
+ });
154
+
155
+ export const startVendorCommissionQueue = async () => {
156
+ const payments = await PaymentIntent.findAll({
157
+ where: {
158
+ status: ['requires_capture', 'processing'],
159
+ capture_method: 'automatic',
160
+ },
161
+ });
162
+
163
+ payments.forEach(async (x) => {
164
+ const exist = await vendorCommissionQueue.get(`vendor-commission-${x.id}`);
165
+ if (!exist) {
166
+ vendorCommissionQueue.push({ id: x.id, job: { paymentIntentId: x.id, retryOnError: true } });
167
+ }
168
+ });
169
+ };
170
+
171
+ events.on('vendor.commission.queued', async (id, job, args = {}) => {
172
+ try {
173
+ const { ...extraArgs } = args;
174
+ const exist = await vendorCommissionQueue.get(id);
175
+
176
+ if (!exist) {
177
+ logger.info('Vendor commission job added successfully', { id });
178
+ vendorCommissionQueue.push({
179
+ id,
180
+ job,
181
+ ...extraArgs,
182
+ });
183
+ } else {
184
+ logger.info('Vendor commission job already exists, skipping', { id });
185
+ }
186
+ } catch (error: any) {
187
+ logger.error('Failed to handle vendor commission queue event', {
188
+ id,
189
+ error: error.message,
190
+ });
191
+ }
192
+ });