payment-kit 1.20.13 → 1.20.15

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 (37) hide show
  1. package/api/src/libs/vendor-util/adapters/launcher-adapter.ts +1 -1
  2. package/api/src/libs/vendor-util/adapters/types.ts +2 -3
  3. package/api/src/libs/vendor-util/fulfillment.ts +16 -30
  4. package/api/src/queues/vendors/commission.ts +32 -42
  5. package/api/src/queues/vendors/fulfillment-coordinator.ts +68 -60
  6. package/api/src/queues/vendors/fulfillment.ts +5 -5
  7. package/api/src/queues/vendors/return-processor.ts +0 -1
  8. package/api/src/queues/vendors/status-check.ts +2 -2
  9. package/api/src/routes/checkout-sessions.ts +15 -2
  10. package/api/src/routes/coupons.ts +7 -0
  11. package/api/src/routes/credit-grants.ts +8 -1
  12. package/api/src/routes/credit-transactions.ts +153 -13
  13. package/api/src/routes/invoices.ts +35 -1
  14. package/api/src/routes/meter-events.ts +31 -3
  15. package/api/src/routes/meters.ts +4 -0
  16. package/api/src/routes/payment-currencies.ts +2 -1
  17. package/api/src/routes/promotion-codes.ts +2 -2
  18. package/api/src/routes/subscription-items.ts +4 -0
  19. package/api/src/routes/vendor.ts +13 -4
  20. package/api/src/routes/webhook-endpoints.ts +4 -0
  21. package/api/src/store/migrations/20250919-add-source-data.ts +20 -0
  22. package/api/src/store/models/checkout-session.ts +23 -0
  23. package/api/src/store/models/credit-transaction.ts +5 -0
  24. package/api/src/store/models/meter-event.ts +22 -12
  25. package/api/src/store/models/types.ts +18 -0
  26. package/blocklet.yml +1 -1
  27. package/package.json +5 -5
  28. package/src/components/customer/credit-overview.tsx +1 -1
  29. package/src/components/customer/related-credit-grants.tsx +194 -0
  30. package/src/components/meter/add-usage-dialog.tsx +8 -0
  31. package/src/components/meter/events-list.tsx +93 -96
  32. package/src/components/product/form.tsx +0 -1
  33. package/src/locales/en.tsx +9 -0
  34. package/src/locales/zh.tsx +9 -0
  35. package/src/pages/admin/billing/invoices/detail.tsx +21 -2
  36. package/src/pages/customer/invoice/detail.tsx +11 -2
  37. package/doc/vendor_fulfillment_system.md +0 -929
@@ -89,7 +89,7 @@ export class LauncherAdapter implements VendorAdapter {
89
89
  amount: params.amount,
90
90
  currency: params.currency,
91
91
  quantity: params.quantity,
92
- paymentIntentId: params.paymentIntentId,
92
+ invoiceId: params.invoiceId,
93
93
  customParams: params.customParams,
94
94
  },
95
95
  installationInfo: {
@@ -14,7 +14,7 @@ export interface FulfillOrderParams {
14
14
  productCode: string;
15
15
  customerId: string;
16
16
  quantity: number;
17
- paymentIntentId: string;
17
+ invoiceId: string;
18
18
  amount: string;
19
19
  currency: string;
20
20
 
@@ -38,7 +38,7 @@ export interface OrderDetails {
38
38
  amount: string;
39
39
  currency: string;
40
40
  quantity: number;
41
- paymentIntentId: string;
41
+ invoiceId: string;
42
42
  customParams?: Record<string, any>;
43
43
  }
44
44
 
@@ -66,7 +66,6 @@ export interface ReturnRequestParams {
66
66
  orderId: string;
67
67
  vendorOrderId?: string;
68
68
  reason: string;
69
- paymentIntentId: string;
70
69
  customParams?: Record<string, any>;
71
70
  }
72
71
 
@@ -43,7 +43,7 @@ export class VendorFulfillmentService {
43
43
  checkoutSessionId: string;
44
44
  amount_total: string;
45
45
  customer_id: string;
46
- payment_intent_id: string | null;
46
+ invoiceId: string;
47
47
  currency_id: string;
48
48
  customer_did: string;
49
49
  },
@@ -75,7 +75,7 @@ export class VendorFulfillmentService {
75
75
  productCode: vendorConfig.vendor_id,
76
76
  customerId: orderInfo.customer_id || '',
77
77
  quantity: 1,
78
- paymentIntentId: orderInfo.payment_intent_id || '',
78
+ invoiceId: orderInfo.invoiceId,
79
79
  amount: orderInfo.amount_total,
80
80
  currency: orderInfo.currency_id,
81
81
 
@@ -124,34 +124,18 @@ export class VendorFulfillmentService {
124
124
  }
125
125
  }
126
126
 
127
- static async createVendorPayouts(
128
- checkoutSessionId: string,
129
- fulfillmentResults?: VendorFulfillmentResult[]
130
- ): Promise<void> {
127
+ static async createVendorPayouts(checkoutSession: CheckoutSession, paymentIntent: PaymentIntent): Promise<void> {
131
128
  try {
132
- const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId);
133
- if (!checkoutSession) {
134
- throw new Error(`CheckoutSession not found: ${checkoutSessionId}`);
135
- }
136
-
137
- let paymentMethodId = '';
138
- if (checkoutSession.payment_intent_id) {
139
- const paymentIntent = await PaymentIntent.findByPk(checkoutSession.payment_intent_id);
140
- if (paymentIntent) {
141
- paymentMethodId = paymentIntent.payment_method_id;
142
- }
143
- }
129
+ const paymentMethodId = paymentIntent.payment_method_id;
130
+ const paymentIntentId = paymentIntent.id;
144
131
 
145
132
  // If fulfillmentResults not provided, calculate commission info
146
- let commissionData = fulfillmentResults;
147
- if (!commissionData) {
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[];
154
- }
133
+ const commissionData = checkoutSession.vendor_info?.map((vendorInfo: any) => ({
134
+ vendorId: vendorInfo.vendor_id,
135
+ orderId: vendorInfo.order_id,
136
+ status: vendorInfo.status,
137
+ commissionAmount: vendorInfo.commissionAmount,
138
+ })) as VendorFulfillmentResult[];
155
139
 
156
140
  const payoutPromises = commissionData
157
141
  .filter((result) => result.status !== 'failed' && new BN(result.commissionAmount).gt(new BN('0')))
@@ -170,7 +154,7 @@ export class VendorFulfillmentService {
170
154
  amount: result.commissionAmount,
171
155
  currency_id: checkoutSession.currency_id,
172
156
  customer_id: checkoutSession.customer_id || '',
173
- payment_intent_id: checkoutSession.payment_intent_id || '',
157
+ payment_intent_id: paymentIntentId,
174
158
  payment_method_id: paymentMethodId,
175
159
  status: paymentMethod?.type === 'stripe' ? 'deferred' : 'pending',
176
160
  attempt_count: 0,
@@ -187,12 +171,14 @@ export class VendorFulfillmentService {
187
171
  await Promise.all(payoutPromises);
188
172
 
189
173
  logger.info('Vendor payouts created', {
190
- checkoutSessionId,
174
+ checkoutSessionId: checkoutSession.id,
175
+ paymentIntentId: paymentIntent.id,
191
176
  payoutCount: commissionData.filter((r) => r.status !== 'failed').length,
192
177
  });
193
178
  } catch (error: any) {
194
179
  logger.error('Failed to create vendor payouts', {
195
- checkoutSessionId,
180
+ checkoutSessionId: checkoutSession.id,
181
+ paymentIntentId: paymentIntent.id,
196
182
  error: error.message,
197
183
  });
198
184
  throw error;
@@ -1,29 +1,26 @@
1
1
  import { events } from '../../libs/event';
2
2
  import logger from '../../libs/logger';
3
3
  import createQueue from '../../libs/queue';
4
+ import { Invoice } from '../../store/models';
4
5
  import { CheckoutSession } from '../../store/models/checkout-session';
5
6
  import { PaymentIntent } from '../../store/models/payment-intent';
6
- import { Product } from '../../store/models/product';
7
7
  import { Price } from '../../store/models/price';
8
+ import { Product } from '../../store/models/product';
8
9
  import { depositVaultQueue } from '../payment';
9
10
  import { startVendorFulfillment, triggerCommissionProcess, triggerCoordinatorCheck } from './fulfillment-coordinator';
10
11
 
11
12
  type VendorCommissionJob = {
12
- paymentIntentId: string;
13
+ invoiceId: string;
13
14
  retryOnError?: boolean;
14
15
  };
15
16
 
16
- async function checkIfPaymentIntentHasVendors(
17
- paymentIntent: PaymentIntent,
18
- checkoutSession: CheckoutSession
19
- ): Promise<boolean> {
17
+ async function checkIfPaymentIntentHasVendors(checkoutSession: CheckoutSession): Promise<boolean> {
20
18
  try {
21
19
  // Extract price_ids from line_items, then find corresponding product_ids
22
20
  const priceIds = checkoutSession.line_items.map((item: any) => item.price_id).filter(Boolean);
23
21
 
24
22
  if (priceIds.length === 0) {
25
23
  logger.warn('No price IDs found in checkout session line items', {
26
- paymentIntentId: paymentIntent.id,
27
24
  checkoutSessionId: checkoutSession.id,
28
25
  });
29
26
  return false;
@@ -39,7 +36,6 @@ async function checkIfPaymentIntentHasVendors(
39
36
 
40
37
  if (productIds.length === 0) {
41
38
  logger.warn('No product IDs found from prices', {
42
- paymentIntentId: paymentIntent.id,
43
39
  checkoutSessionId: checkoutSession.id,
44
40
  priceIds,
45
41
  });
@@ -57,28 +53,28 @@ async function checkIfPaymentIntentHasVendors(
57
53
  return hasVendorConfig;
58
54
  } catch (error: any) {
59
55
  logger.error('Failed to check vendor configuration', {
60
- paymentIntentId: paymentIntent.id,
61
56
  error,
62
57
  });
63
58
  return false;
64
59
  }
65
60
  }
66
61
 
67
- async function executeDirectDepositVault(paymentIntent: PaymentIntent): Promise<void> {
68
- const exist = await depositVaultQueue.get(`deposit-vault-${paymentIntent.currency_id}`);
62
+ async function executeDirectDepositVault(invoice: Invoice): Promise<void> {
63
+ const currencyId = invoice.currency_id;
64
+ const exist = await depositVaultQueue.get(`deposit-vault-${currencyId}`);
69
65
  if (!exist) {
70
66
  depositVaultQueue.push({
71
- id: `deposit-vault-${paymentIntent.currency_id}`,
72
- job: { currencyId: paymentIntent.currency_id },
67
+ id: `deposit-vault-${currencyId}`,
68
+ job: { currencyId },
73
69
  });
74
70
  logger.info('Deposit vault job queued', {
75
- paymentIntentId: paymentIntent.id,
76
- currencyId: paymentIntent.currency_id,
71
+ paymentIntentId: invoice.payment_intent_id,
72
+ currencyId,
77
73
  });
78
74
  } else {
79
75
  logger.info('Deposit vault job already exists', {
80
- paymentIntentId: paymentIntent.id,
81
- currencyId: paymentIntent.currency_id,
76
+ paymentIntentId: invoice.payment_intent_id,
77
+ currencyId,
82
78
  });
83
79
  }
84
80
  }
@@ -88,53 +84,53 @@ export const handleVendorCommission = async (job: VendorCommissionJob) => {
88
84
 
89
85
  let checkoutSession: CheckoutSession | null = null;
90
86
  try {
91
- const paymentIntent = await PaymentIntent.findByPk(job.paymentIntentId);
92
- if (!paymentIntent) {
93
- logger.warn('PaymentIntent not found', { id: job.paymentIntentId });
87
+ const invoice = await Invoice.findByPk(job.invoiceId);
88
+ if (!invoice) {
89
+ logger.warn('invoice not found', { id: job.invoiceId });
94
90
  return;
95
91
  }
96
92
 
97
93
  // Find CheckoutSession through PaymentIntent
98
- checkoutSession = await CheckoutSession.findByPaymentIntentId(paymentIntent.id);
94
+ checkoutSession = await CheckoutSession.findByInvoiceId(invoice.id);
99
95
  if (!checkoutSession) {
100
- await executeDirectDepositVault(paymentIntent);
96
+ await executeDirectDepositVault(invoice);
101
97
  return;
102
98
  }
103
99
 
104
- const hasVendorConfig = await checkIfPaymentIntentHasVendors(paymentIntent, checkoutSession);
100
+ const hasVendorConfig = await checkIfPaymentIntentHasVendors(checkoutSession);
105
101
  if (!hasVendorConfig) {
106
- await executeDirectDepositVault(paymentIntent);
102
+ await executeDirectDepositVault(invoice);
107
103
  return;
108
104
  }
109
105
 
110
106
  logger.info('Vendor configuration found, starting fulfillment process', {
111
- paymentIntentId: paymentIntent.id,
107
+ invoiceId: invoice.id,
112
108
  });
113
109
 
114
110
  if (checkoutSession.fulfillment_status === 'completed') {
115
111
  logger.info('CheckoutSession already completed, directly trigger commission process', {
116
112
  checkoutSessionId: checkoutSession.id,
117
113
  });
118
- await triggerCommissionProcess(checkoutSession.id, paymentIntent.id);
114
+ await triggerCommissionProcess(checkoutSession.id, invoice.id);
119
115
  return;
120
116
  }
121
117
 
122
- await startVendorFulfillment(checkoutSession.id, paymentIntent.id);
118
+ await startVendorFulfillment(checkoutSession.id, invoice.id);
123
119
  } catch (error: any) {
124
120
  logger.error('Vendor commission decision failed, fallback to direct deposit vault', {
125
- paymentIntentId: job.paymentIntentId,
121
+ invoiceId: job.invoiceId,
126
122
  error,
127
123
  });
128
124
 
129
125
  if (!checkoutSession) {
130
126
  logger.error('CheckoutSession not found via any method[handleVendorCommission]', {
131
- paymentIntentId: job.paymentIntentId,
127
+ invoiceId: job.invoiceId,
132
128
  });
133
129
  return;
134
130
  }
135
131
 
136
132
  try {
137
- triggerCoordinatorCheck(checkoutSession.id, job.paymentIntentId, 'vendor_commission_decision_failed');
133
+ triggerCoordinatorCheck(checkoutSession.id, job.invoiceId, 'vendor_commission_decision_failed');
138
134
  } catch (err: any) {
139
135
  logger.error('Failed to trigger coordinator check[handleVendorCommission]', { error: err });
140
136
  }
@@ -160,23 +156,17 @@ export const startVendorCommissionQueue = async () => {
160
156
  });
161
157
 
162
158
  payments.forEach(async (x) => {
163
- const exist = await vendorCommissionQueue.get(`vendor-commission-${x.id}`);
164
- if (!exist) {
165
- vendorCommissionQueue.push({ id: x.id, job: { paymentIntentId: x.id, retryOnError: true } });
159
+ const id = `vendor-commission-${x.invoice_id}`;
160
+ const exist = await vendorCommissionQueue.get(id);
161
+ if (!exist && x.invoice_id) {
162
+ vendorCommissionQueue.push({ id, job: { invoiceId: x.invoice_id, retryOnError: true } });
166
163
  }
167
164
  });
168
165
  };
169
166
 
170
167
  events.on('invoice.paid', async (invoice) => {
171
168
  try {
172
- const paymentIntent = await PaymentIntent.findOne({ where: { invoice_id: invoice.id } });
173
-
174
- if (!paymentIntent) {
175
- logger.warn('PaymentIntent not found', { id: invoice.id });
176
- return;
177
- }
178
-
179
- const id = `vendor-commission-${paymentIntent.id}`;
169
+ const id = `vendor-commission-${invoice.id}`;
180
170
  const exist = await vendorCommissionQueue.get(id);
181
171
  if (exist) {
182
172
  logger.info('Vendor commission job already exists, skipping', { id });
@@ -185,7 +175,7 @@ events.on('invoice.paid', async (invoice) => {
185
175
 
186
176
  vendorCommissionQueue.push({
187
177
  id,
188
- job: { paymentIntentId: paymentIntent.id, retryOnError: true },
178
+ job: { invoiceId: invoice.id, retryOnError: true },
189
179
  });
190
180
  } catch (error) {
191
181
  logger.error('Failed to trigger vendor commission queue', { invoiceId: invoice.id, error });
@@ -12,12 +12,13 @@ import { Product } from '../../store/models/product';
12
12
  import { Refund } from '../../store/models/refund';
13
13
  import { sequelize } from '../../store/sequelize';
14
14
  import { depositVaultQueue } from '../payment';
15
+ import { Invoice } from '../../store/models';
15
16
 
16
17
  export type VendorInfo = NonNullable<CheckoutSession['vendor_info']>[number];
17
18
 
18
19
  interface CoordinatorJob {
19
20
  checkoutSessionId: string;
20
- paymentIntentId: string;
21
+ invoiceId: string;
21
22
  triggeredBy: string;
22
23
  }
23
24
 
@@ -28,11 +29,11 @@ export const fulfillmentCoordinatorQueue = createQueue({
28
29
  onJob: handleFulfillmentCoordination,
29
30
  });
30
31
 
31
- export async function startVendorFulfillment(checkoutSessionId: string, paymentIntentId: string): Promise<void> {
32
+ export async function startVendorFulfillment(checkoutSessionId: string, invoiceId: string): Promise<void> {
32
33
  try {
33
34
  logger.info('Starting vendor fulfillment process', {
34
35
  checkoutSessionId,
35
- paymentIntentId,
36
+ invoiceId,
36
37
  });
37
38
 
38
39
  const vendorConfigs = await getVendorConfigurations(checkoutSessionId);
@@ -43,7 +44,7 @@ export async function startVendorFulfillment(checkoutSessionId: string, paymentI
43
44
  checkoutSessionId,
44
45
  });
45
46
 
46
- await triggerCommissionProcess(checkoutSessionId, paymentIntentId);
47
+ await triggerCommissionProcess(checkoutSessionId, invoiceId);
47
48
  return;
48
49
  }
49
50
 
@@ -65,7 +66,7 @@ export async function startVendorFulfillment(checkoutSessionId: string, paymentI
65
66
 
66
67
  events.emit('vendor.fulfillment.queued', vendorFulfillmentJobId, {
67
68
  checkoutSessionId,
68
- paymentIntentId,
69
+ invoiceId,
69
70
  vendorId: vendorConfig.vendor_id,
70
71
  vendorConfig,
71
72
  retryOnError: true,
@@ -79,7 +80,7 @@ export async function startVendorFulfillment(checkoutSessionId: string, paymentI
79
80
  } catch (error: any) {
80
81
  logger.error('Failed to start vendor fulfillment', {
81
82
  checkoutSessionId,
82
- paymentIntentId,
83
+ invoiceId,
83
84
  error: error.message,
84
85
  });
85
86
  throw error;
@@ -88,7 +89,7 @@ export async function startVendorFulfillment(checkoutSessionId: string, paymentI
88
89
 
89
90
  export async function updateVendorFulfillmentStatus(
90
91
  checkoutSessionId: string,
91
- paymentIntentId: string,
92
+ invoiceId: string,
92
93
  vendorId: string,
93
94
  result: 'completed' | 'failed' | 'max_retries_exceeded' | 'return_requested' | 'sent',
94
95
  details?: {
@@ -110,11 +111,11 @@ export async function updateVendorFulfillmentStatus(
110
111
  lastAttemptAt: new Date().toISOString(),
111
112
  });
112
113
 
113
- await triggerCoordinatorCheck(checkoutSessionId, paymentIntentId, `vendor_${vendorId}_${result}`);
114
+ await triggerCoordinatorCheck(checkoutSessionId, invoiceId, `vendor_${vendorId}_${result}`);
114
115
  }
115
116
 
116
117
  export async function handleFulfillmentCoordination(job: CoordinatorJob) {
117
- const { checkoutSessionId, paymentIntentId, triggeredBy } = job;
118
+ const { checkoutSessionId, invoiceId, triggeredBy } = job;
118
119
 
119
120
  logger.info('Processing fulfillment coordination', {
120
121
  checkoutSessionId,
@@ -129,7 +130,7 @@ export async function handleFulfillmentCoordination(job: CoordinatorJob) {
129
130
  logger.info('No vendors to coordinate, triggering commission directly', {
130
131
  checkoutSessionId,
131
132
  });
132
- await triggerCommissionProcess(checkoutSessionId, paymentIntentId);
133
+ await triggerCommissionProcess(checkoutSessionId, invoiceId);
133
134
  return;
134
135
  }
135
136
 
@@ -146,7 +147,7 @@ export async function handleFulfillmentCoordination(job: CoordinatorJob) {
146
147
  successfulVendors: analysis.successfulVendors.length,
147
148
  });
148
149
 
149
- await triggerCommissionProcess(checkoutSessionId, paymentIntentId);
150
+ await triggerCommissionProcess(checkoutSessionId, invoiceId);
150
151
  } else if (analysis.anyMaxRetriesExceeded || analysis.shouldTimeout || analysis.failedVendors.length > 0) {
151
152
  logger.warn('Some vendors failed, initiating full refund', {
152
153
  checkoutSessionId,
@@ -155,7 +156,7 @@ export async function handleFulfillmentCoordination(job: CoordinatorJob) {
155
156
  timeoutVendors: analysis.shouldTimeout ? 'detected' : 'none',
156
157
  });
157
158
 
158
- await initiateFullRefund(paymentIntentId, 'vendor_failure_detected');
159
+ await initiateFullRefund(invoiceId, 'vendor_failure_detected');
159
160
  } else {
160
161
  logger.info('Some vendors still in progress, waiting for completion', {
161
162
  checkoutSessionId,
@@ -170,7 +171,7 @@ export async function handleFulfillmentCoordination(job: CoordinatorJob) {
170
171
  error: error.message,
171
172
  });
172
173
 
173
- await initiateFullRefund(paymentIntentId, 'coordination_failed');
174
+ await initiateFullRefund(invoiceId, 'coordination_failed');
174
175
  }
175
176
  }
176
177
 
@@ -355,74 +356,94 @@ function analyzeVendorStates(vendorInfo: VendorInfo[], vendorConfigs: any[]) {
355
356
  };
356
357
  }
357
358
 
358
- export function triggerCoordinatorCheck(checkoutSessionId: string, paymentIntentId: string, triggeredBy: string) {
359
+ export function triggerCoordinatorCheck(checkoutSessionId: string, invoiceId: string, triggeredBy: string) {
359
360
  const jobId = `coordinator-${checkoutSessionId}-${Date.now()}`;
360
361
 
361
362
  return fulfillmentCoordinatorQueue.push({
362
363
  id: jobId,
363
364
  job: {
364
365
  checkoutSessionId,
365
- paymentIntentId,
366
+ invoiceId,
366
367
  triggeredBy,
367
368
  },
368
369
  });
369
370
  }
370
371
 
371
- export async function triggerCommissionProcess(checkoutSessionId: string, paymentIntentId: string): Promise<void> {
372
- logger.info('Triggering commission process', {
373
- checkoutSessionId,
374
- paymentIntentId,
372
+ export async function triggerCommissionProcess(checkoutSessionId: string, invoiceId: string): Promise<void> {
373
+ logger.info('Triggering commission process', { checkoutSessionId });
374
+
375
+ const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId);
376
+ if (!checkoutSession) {
377
+ logger.error('Checkout session not found[triggerCommissionProcess]', { checkoutSessionId });
378
+ return;
379
+ }
380
+ const invoice = await Invoice.findByPk(invoiceId);
381
+ if (!invoice) {
382
+ logger.error('Invoice not found[triggerCommissionProcess]', { invoiceId });
383
+ return;
384
+ }
385
+ const paymentIntent = await PaymentIntent.findOne({
386
+ where: {
387
+ [Op.or]: [{ id: invoice.payment_intent_id || checkoutSession.payment_intent_id }, { invoice_id: invoiceId }],
388
+ },
375
389
  });
376
390
 
377
- await VendorFulfillmentService.createVendorPayouts(checkoutSessionId);
378
- await CheckoutSession.update({ fulfillment_status: 'completed' }, { where: { id: checkoutSessionId } });
391
+ if (!paymentIntent) {
392
+ logger.error('Payment intent not found[triggerCommissionProcess]', { checkoutSessionId });
393
+ return;
394
+ }
395
+
396
+ await VendorFulfillmentService.createVendorPayouts(checkoutSession, paymentIntent);
397
+ await checkoutSession.update({ fulfillment_status: 'completed' });
379
398
 
380
- const paymentIntent = await PaymentIntent.findByPk(paymentIntentId);
381
399
  if (paymentIntent) {
382
400
  const jobId = `deposit-vault-${paymentIntent.currency_id}`;
383
401
  const existingJob = await depositVaultQueue.get(jobId);
384
402
 
385
403
  if (!existingJob) {
386
- await depositVaultQueue.push({
404
+ depositVaultQueue.push({
387
405
  id: jobId,
388
406
  job: { currencyId: paymentIntent.currency_id },
389
407
  });
390
408
  }
409
+ } else {
410
+ logger.error('Payment intent not found for invoice', { invoiceId });
391
411
  }
392
412
 
393
413
  logger.info('Commission process triggered successfully', {
394
414
  checkoutSessionId,
395
- paymentIntentId,
396
415
  });
397
416
  }
398
417
 
399
- export async function initiateFullRefund(paymentIntentId: string, reason: string): Promise<void> {
418
+ export async function initiateFullRefund(invoiceId: string, reason: string): Promise<void> {
400
419
  logger.warn('Initiating full refund with compensation', {
401
- paymentIntentId,
420
+ invoiceId,
402
421
  reason,
403
422
  });
404
423
 
405
424
  try {
406
- const paymentIntent = await PaymentIntent.findByPk(paymentIntentId);
407
- const checkoutSession = await CheckoutSession.findByPaymentIntentId(paymentIntentId);
425
+ const paymentIntent = await PaymentIntent.findOne({ where: { invoice_id: invoiceId } });
426
+ const checkoutSession = await CheckoutSession.findByInvoiceId(invoiceId);
408
427
 
409
428
  if (!checkoutSession || !paymentIntent) {
410
429
  logger.error('Missing data for full refund', {
411
- paymentIntentId,
430
+ invoiceId,
431
+ paymentIntentId: paymentIntent?.id,
432
+ checkoutSessionId: checkoutSession?.id,
412
433
  hasCheckoutSession: !!checkoutSession,
413
434
  hasPaymentIntent: !!paymentIntent,
414
435
  });
415
436
  return;
416
437
  }
417
438
 
418
- await CheckoutSession.update({ fulfillment_status: 'cancelled' }, { where: { id: checkoutSession.id } });
419
- await requestReturnsFromCompletedVendors(checkoutSession.id, paymentIntentId, reason);
439
+ await checkoutSession.update({ fulfillment_status: 'cancelled' });
440
+ await requestReturnsFromCompletedVendors(checkoutSession, reason);
420
441
 
421
442
  // Calculate remaining amount using the same logic as subscription createProration
422
443
  const refunds = await Refund.findAll({
423
444
  where: {
424
445
  status: { [Op.not]: 'canceled' },
425
- payment_intent_id: paymentIntentId,
446
+ payment_intent_id: paymentIntent.id,
426
447
  type: 'refund',
427
448
  },
428
449
  });
@@ -437,7 +458,7 @@ export async function initiateFullRefund(paymentIntentId: string, reason: string
437
458
  // If no remaining amount or already fully refunded, skip
438
459
  if (new BN(remaining).lte(new BN('0'))) {
439
460
  logger.info('Payment already fully refunded, skipping', {
440
- paymentIntentId,
461
+ invoiceId,
441
462
  paymentAmount: paymentIntent.amount,
442
463
  totalRefundAmount: refundAmount.toString(),
443
464
  remaining,
@@ -478,52 +499,41 @@ export async function initiateFullRefund(paymentIntentId: string, reason: string
478
499
  logger.info('Full refund created, triggering refund processing', {
479
500
  refundId: refund.id,
480
501
  checkoutSessionId: checkoutSession.id,
481
- paymentIntentId,
502
+ invoiceId,
482
503
  amount: refund.amount,
483
504
  reason,
484
505
  });
485
506
  } catch (error: any) {
486
507
  logger.error('Failed to create full refund', {
487
- paymentIntentId,
508
+ invoiceId,
488
509
  reason,
489
510
  error: error.message,
490
511
  });
491
512
  }
492
513
  }
493
514
 
494
- async function requestReturnsFromCompletedVendors(
495
- checkoutSessionId: string,
496
- paymentIntentId: string,
497
- reason: string
498
- ): Promise<void> {
515
+ async function requestReturnsFromCompletedVendors(checkoutSession: CheckoutSession, reason: string): Promise<void> {
499
516
  logger.info('Starting return request process', {
500
- checkoutSessionId,
501
- paymentIntentId,
517
+ checkoutSessionId: checkoutSession.id,
502
518
  reason,
503
519
  });
504
520
 
505
521
  try {
506
- const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId);
507
- if (!checkoutSession) {
508
- logger.error('CheckoutSession not found for return request', { checkoutSessionId });
509
- return;
510
- }
511
-
512
522
  const vendorInfos = (checkoutSession.vendor_info as VendorInfo[]) || [];
513
523
  const completedVendors = vendorInfos.filter((vendor) => vendor.status === 'completed');
514
524
 
515
525
  if (completedVendors.length === 0) {
516
- logger.info('No completed vendors to request returns from', { checkoutSessionId });
526
+ logger.info('No completed vendors to request returns from', { checkoutSessionId: checkoutSession.id });
517
527
  return;
518
528
  }
519
529
 
520
530
  logger.info(`Found ${completedVendors.length} completed vendors requiring return requests`, {
521
- checkoutSessionId,
531
+ checkoutSessionId: checkoutSession.id,
522
532
  completedVendorIds: completedVendors.map((v) => v.vendor_id),
523
533
  });
524
534
 
525
535
  const returnRequestPromises = completedVendors.map((vendor) => {
526
- return requestReturnFromSingleVendor(checkoutSessionId, paymentIntentId, vendor, reason);
536
+ return requestReturnFromSingleVendor(checkoutSession, vendor, reason);
527
537
  });
528
538
 
529
539
  const returnResults = await Promise.allSettled(returnRequestPromises);
@@ -547,27 +557,26 @@ async function requestReturnsFromCompletedVendors(
547
557
  });
548
558
 
549
559
  logger.info('Return request process completed', {
550
- checkoutSessionId,
560
+ checkoutSessionId: checkoutSession.id,
551
561
  totalVendors: completedVendors.length,
552
562
  successful: returnResults.filter((r) => r.status === 'fulfilled').length,
553
563
  failed: returnResults.filter((r) => r.status === 'rejected').length,
554
564
  });
555
565
  } catch (error: any) {
556
566
  logger.error('Return request process failed', {
557
- checkoutSessionId,
558
- paymentIntentId,
567
+ checkoutSessionId: checkoutSession.id,
559
568
  error: error.message,
560
569
  });
561
570
  }
562
571
  }
563
572
 
564
573
  async function requestReturnFromSingleVendor(
565
- checkoutSessionId: string,
566
- paymentIntentId: string,
574
+ checkoutSession: CheckoutSession,
567
575
  vendor: VendorInfo,
568
576
  reason: string
569
577
  ): Promise<void> {
570
578
  logger.info('Requesting return for vendor', {
579
+ checkoutSessionId: checkoutSession.id,
571
580
  vendorId: vendor.vendor_id,
572
581
  orderId: vendor.order_id,
573
582
  reason,
@@ -582,7 +591,6 @@ async function requestReturnFromSingleVendor(
582
591
  const returnResult = await vendorAdapter.requestReturn({
583
592
  orderId: vendor.order_id,
584
593
  reason: `Return request due to: ${reason}`,
585
- paymentIntentId,
586
594
  customParams: {
587
595
  returnType: 'order_failure',
588
596
  originalAmount: vendor.amount,
@@ -596,7 +604,7 @@ async function requestReturnFromSingleVendor(
596
604
  status = 'rejected' as 'rejected';
597
605
  }
598
606
 
599
- await updateSingleVendorInfo(checkoutSessionId, vendor.vendor_id, {
607
+ await updateSingleVendorInfo(checkoutSession.id, vendor.vendor_id, {
600
608
  status: 'return_requested',
601
609
  returnRequest: {
602
610
  reason,
@@ -615,10 +623,10 @@ async function requestReturnFromSingleVendor(
615
623
  logger.error('Return request failed', {
616
624
  vendorId: vendor.vendor_id,
617
625
  orderId: vendor.order_id,
618
- error: error.message,
626
+ error,
619
627
  });
620
628
 
621
- await updateSingleVendorInfo(checkoutSessionId, vendor.vendor_id, {
629
+ await updateSingleVendorInfo(checkoutSession.id, vendor.vendor_id, {
622
630
  status: 'return_requested',
623
631
  error_message: `Return request failed: ${error.message}`,
624
632
  });