payment-kit 1.20.14 → 1.20.16

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.
@@ -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
  });
@@ -7,7 +7,7 @@ import { updateVendorFulfillmentStatus } from './fulfillment-coordinator';
7
7
 
8
8
  type VendorFulfillmentJob = {
9
9
  checkoutSessionId: string;
10
- paymentIntentId: string;
10
+ invoiceId: string;
11
11
  vendorId: string;
12
12
  vendorConfig: any;
13
13
  retryOnError?: boolean;
@@ -19,7 +19,7 @@ export const handleVendorFulfillment = async (job: VendorFulfillmentJob) => {
19
19
  jobKeys: Object.keys(job),
20
20
  });
21
21
 
22
- const { checkoutSessionId, paymentIntentId, vendorId, vendorConfig } = job;
22
+ const { checkoutSessionId, invoiceId, vendorId, vendorConfig } = job;
23
23
 
24
24
  try {
25
25
  const checkoutSession = await CheckoutSession.findByPk(checkoutSessionId);
@@ -31,7 +31,7 @@ export const handleVendorFulfillment = async (job: VendorFulfillmentJob) => {
31
31
  checkoutSessionId,
32
32
  amount_total: checkoutSession.amount_total,
33
33
  customer_id: checkoutSession.customer_id || '',
34
- payment_intent_id: checkoutSession.payment_intent_id || '',
34
+ invoiceId,
35
35
  currency_id: checkoutSession.currency_id,
36
36
  customer_did: checkoutSession.customer_did || '',
37
37
  };
@@ -43,7 +43,7 @@ export const handleVendorFulfillment = async (job: VendorFulfillmentJob) => {
43
43
  status: fulfillmentResult.status,
44
44
  });
45
45
 
46
- await updateVendorFulfillmentStatus(checkoutSessionId, paymentIntentId, vendorId, 'sent', {
46
+ await updateVendorFulfillmentStatus(checkoutSessionId, invoiceId, vendorId, 'sent', {
47
47
  orderId: fulfillmentResult.orderId,
48
48
  commissionAmount: fulfillmentResult.commissionAmount,
49
49
  serviceUrl: fulfillmentResult.serviceUrl,
@@ -55,7 +55,7 @@ export const handleVendorFulfillment = async (job: VendorFulfillmentJob) => {
55
55
  error,
56
56
  });
57
57
 
58
- await updateVendorFulfillmentStatus(checkoutSessionId, paymentIntentId, vendorId, 'failed', {
58
+ await updateVendorFulfillmentStatus(checkoutSessionId, invoiceId, vendorId, 'failed', {
59
59
  lastError: error.message,
60
60
  });
61
61
 
@@ -157,7 +157,6 @@ async function callVendorReturn(
157
157
  const returnResult = await vendorAdapter.requestReturn({
158
158
  orderId: vendor.order_id,
159
159
  reason: 'Subscription canceled',
160
- paymentIntentId: checkoutSession.payment_intent_id || '',
161
160
  customParams: {
162
161
  checkoutSessionId: checkoutSession.id,
163
162
  subscriptionId: checkoutSession.subscription_id,
@@ -83,7 +83,7 @@ export const handleVendorStatusCheck = async (job: VendorStatusCheckJob) => {
83
83
  id: `fulfillment-coordinator-${checkoutSessionId}-${vendorId}`,
84
84
  job: {
85
85
  checkoutSessionId,
86
- paymentIntentId: checkoutSession?.payment_intent_id || '',
86
+ invoiceId: checkoutSession?.invoice_id || '',
87
87
  triggeredBy: 'vendor-status-check-timeout',
88
88
  },
89
89
  });
@@ -158,7 +158,7 @@ export const handleVendorStatusCheck = async (job: VendorStatusCheckJob) => {
158
158
  id: `fulfillment-coordinator-${checkoutSessionId}-${vendorId}`,
159
159
  job: {
160
160
  checkoutSessionId,
161
- paymentIntentId: checkoutSession?.payment_intent_id || '',
161
+ invoiceId: checkoutSession?.invoice_id || '',
162
162
  triggeredBy: 'vendor-status-check',
163
163
  },
164
164
  });
@@ -105,8 +105,8 @@ import {
105
105
  createDiscountRecordsForCheckout,
106
106
  updateSubscriptionDiscountReferences,
107
107
  } from '../libs/discount/coupon';
108
+ import { rollbackDiscountUsageForCheckoutSession, applyDiscountsToLineItems } from '../libs/discount/discount';
108
109
  import { formatToShortUrl } from '../libs/url';
109
- import { applyDiscountsToLineItems } from '../libs/discount/discount';
110
110
 
111
111
  const router = Router();
112
112
 
@@ -2785,6 +2785,7 @@ router.delete('/:id/remove-promotion', user, ensureCheckoutSessionOpen, async (r
2785
2785
  const trialSetup = getSubscriptionTrialSetup(checkoutSession.subscription_data as any, currency.id);
2786
2786
  const isTrialing = trialSetup.trialInDays > 0 || trialSetup.trialEnd > now;
2787
2787
 
2788
+ await rollbackDiscountUsageForCheckoutSession(checkoutSession.id);
2788
2789
  // Calculate original amounts without any discounts
2789
2790
  const originalResult = await applyDiscountsToLineItems({
2790
2791
  lineItems: originalItems,
@@ -52,7 +52,9 @@ export default {
52
52
  amount: fastCheckoutAmount,
53
53
  });
54
54
 
55
- if (delegation.sufficient === false) {
55
+ const requiredStake = !subscription!.billing_thresholds?.no_stake;
56
+
57
+ if (delegation.sufficient === false || !requiredStake) {
56
58
  claimsList.push({
57
59
  signature: await getDelegationTxClaim({
58
60
  mode: 'subscription',
@@ -69,16 +71,22 @@ export default {
69
71
  });
70
72
  }
71
73
 
72
- claimsList.push({
73
- prepareTx: await getStakeTxClaim({
74
- userDid,
75
- userPk,
76
- paymentCurrency,
77
- paymentMethod,
78
- items,
79
- subscription: subscription!,
80
- }),
81
- });
74
+ if (requiredStake) {
75
+ claimsList.push({
76
+ prepareTx: await getStakeTxClaim({
77
+ userDid,
78
+ userPk,
79
+ paymentCurrency,
80
+ paymentMethod,
81
+ items,
82
+ subscription: subscription!,
83
+ }),
84
+ });
85
+ }
86
+
87
+ if (claimsList.length === 0) {
88
+ throw new Error('No available claims for your subscription at this time.');
89
+ }
82
90
 
83
91
  return claimsList;
84
92
  }
@@ -123,9 +131,12 @@ export default {
123
131
  },
124
132
  });
125
133
 
134
+ const requiredStake = !subscription!.billing_thresholds?.no_stake;
135
+
126
136
  // 判断是否为最后一步
127
137
  const staking = result.find((x: any) => x.claim?.type === 'prepareTx' && x.claim?.meta?.purpose === 'staking');
128
- const isFinalStep = (paymentMethod.type === 'arcblock' && staking) || paymentMethod.type !== 'arcblock';
138
+ const isFinalStep =
139
+ (paymentMethod.type === 'arcblock' && (staking || !requiredStake)) || paymentMethod.type !== 'arcblock';
129
140
 
130
141
  if (!isFinalStep) {
131
142
  await updateSession({
@@ -68,6 +68,7 @@ export default {
68
68
  paymentCurrency,
69
69
  paymentMethod,
70
70
  items,
71
+ requiredStake: false,
71
72
  });
72
73
  }
73
74
 
@@ -42,6 +42,7 @@ export default {
42
42
  trialing: true,
43
43
  billingThreshold,
44
44
  items: subscription!.items as TLineItemExpanded[],
45
+ requiredStake: false,
45
46
  }),
46
47
  ],
47
48
  };
@@ -774,6 +774,7 @@ export async function getDelegationTxClaim({
774
774
  paymentCurrency,
775
775
  requiredStake,
776
776
  });
777
+
777
778
  if (mode === 'delegation') {
778
779
  tokenRequirements = [];
779
780
  }
@@ -1080,9 +1081,15 @@ export async function getTokenRequirements({
1080
1081
  billingThreshold = 0,
1081
1082
  requiredStake,
1082
1083
  }: TokenRequirementArgs) {
1083
- const tokenRequirements = [];
1084
+ const tokenRequirements: { address: string; value: string }[] = [];
1084
1085
  let amount = await getFastCheckoutAmount({ items, mode, currencyId: paymentCurrency.id, trialing: !!trialing });
1085
1086
 
1087
+ const addStakeRequired = requiredStake && ((paymentMethod.type === 'arcblock' && mode !== 'delegation') || mode === 'setup');
1088
+
1089
+ if (!addStakeRequired && amount === '0') {
1090
+ return tokenRequirements;
1091
+ }
1092
+
1086
1093
  // If the app has not staked, we need to add the gas fee to the amount
1087
1094
  if ((await hasStakedForGas(paymentMethod)) === false) {
1088
1095
  const maxGas = await estimateMaxGasForTx(paymentMethod);
@@ -1106,7 +1113,7 @@ export async function getTokenRequirements({
1106
1113
  }
1107
1114
 
1108
1115
  // Add stake requirement to token requirement
1109
- if (requiredStake && ((paymentMethod.type === 'arcblock' && mode !== 'delegation') || mode === 'setup')) {
1116
+ if (addStakeRequired) {
1110
1117
  const staking = getSubscriptionStakeSetup(
1111
1118
  items,
1112
1119
  paymentCurrency.id,
@@ -1115,6 +1122,8 @@ export async function getTokenRequirements({
1115
1122
  const exist = tokenRequirements.find((x) => x.address === paymentCurrency.contract);
1116
1123
  if (exist) {
1117
1124
  exist.value = new BN(exist.value).add(staking.licensed).add(staking.metered).toString();
1125
+ } else {
1126
+ tokenRequirements.push({ address: paymentCurrency.contract as string, value: staking.licensed.add(staking.metered).toString() });
1118
1127
  }
1119
1128
  }
1120
1129
 
@@ -320,7 +320,14 @@ router.get('/pending-amount', authMine, async (req, res) => {
320
320
  where['payload.subscription_id'] = req.query.subscription_id;
321
321
  }
322
322
  if (req.query.customer_id) {
323
- where['payload.customer_id'] = req.query.customer_id;
323
+ if (typeof req.query.customer_id !== 'string') {
324
+ return res.status(400).json({ error: 'Customer ID must be a string' });
325
+ }
326
+ const customer = await Customer.findByPkOrDid(req.query.customer_id);
327
+ if (!customer) {
328
+ return res.status(404).json({ error: 'Customer not found' });
329
+ }
330
+ where['payload.customer_id'] = customer.id;
324
331
  }
325
332
  const [summary] = await MeterEvent.getPendingAmounts({
326
333
  subscriptionId: req.query.subscription_id as string,
@@ -44,6 +44,7 @@ const ProductAndPriceSchema = Joi.object({
44
44
  'string.pattern.base':
45
45
  'statement_descriptor should be at least one letter and cannot include Chinese characters and special characters such as <, >、"、’ or \\',
46
46
  })
47
+ .allow(null, '')
47
48
  .empty('')
48
49
  .optional(),
49
50
  unit_label: Joi.string().max(12).empty('').optional(),
@@ -355,9 +355,12 @@ async function getVendorStatus(sessionId: string, isDetail = false) {
355
355
  };
356
356
  }
357
357
 
358
+ // FIXME: will remove payment_status @pengfei
359
+ const paymentStatus = doc.status === 'complete' ? 'paid' : 'unpaid';
360
+
358
361
  if (doc.status !== 'complete') {
359
362
  return {
360
- payment_status: doc.payment_status,
363
+ payment_status: paymentStatus,
361
364
  session_status: doc.status,
362
365
  error: 'CheckoutSession not complete',
363
366
  vendors: [],
@@ -365,7 +368,7 @@ async function getVendorStatus(sessionId: string, isDetail = false) {
365
368
  }
366
369
  if (!doc.vendor_info) {
367
370
  return {
368
- payment_status: doc.payment_status,
371
+ payment_status: paymentStatus,
369
372
  session_status: doc.status,
370
373
  error: 'Vendor info not found',
371
374
  vendors: [],
@@ -373,7 +376,13 @@ async function getVendorStatus(sessionId: string, isDetail = false) {
373
376
  }
374
377
 
375
378
  const vendors = doc.vendor_info.map((item) => {
376
- return getVendorStatusByVendorId(item.vendor_id, item.order_id, isDetail);
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
+ });
377
386
  });
378
387
 
379
388
  const subscriptionId = doc.subscription_id;
@@ -390,7 +399,7 @@ async function getVendorStatus(sessionId: string, isDetail = false) {
390
399
  }
391
400
 
392
401
  return {
393
- payment_status: doc.payment_status,
402
+ payment_status: paymentStatus,
394
403
  session_status: doc.status,
395
404
  subscriptionUrl: shortSubscriptionUrl,
396
405
  vendors: await Promise.all(vendors),
@@ -0,0 +1,21 @@
1
+ import { DataTypes } from 'sequelize';
2
+ import { Migration, safeApplyColumnChanges } from '../migrate';
3
+
4
+ export const up: Migration = async ({ context }) => {
5
+ await safeApplyColumnChanges(context, {
6
+ discounts: [
7
+ {
8
+ name: 'confirmed',
9
+ field: {
10
+ type: DataTypes.BOOLEAN,
11
+ defaultValue: true,
12
+ allowNull: false,
13
+ },
14
+ },
15
+ ],
16
+ });
17
+ };
18
+
19
+ export const down: Migration = async ({ context }) => {
20
+ await context.removeColumn('discounts', 'confirmed');
21
+ };