payment-kit 1.18.54 → 1.18.55

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.
@@ -8,6 +8,7 @@ import { nanoid } from 'nanoid';
8
8
  import { AsyncLocalStorage } from 'async_hooks';
9
9
  import logger from '../logger';
10
10
  import { sleep, tryWithTimeout } from '../util';
11
+ import dayjs from '../dayjs';
11
12
  import createQueueStore from './store';
12
13
  import { Job } from '../../store/models/job';
13
14
  import { sequelize } from '../../store/sequelize';
@@ -112,6 +113,7 @@ export default function createQueue<T = any>({ name, onJob, options = defaults }
112
113
  queueEvents.emit(e, data);
113
114
  jobEvents.emit(e, data);
114
115
  };
116
+ const now = dayjs().unix();
115
117
 
116
118
  if (!job) {
117
119
  throw new Error('Can not queue empty job');
@@ -134,7 +136,8 @@ export default function createQueue<T = any>({ name, onJob, options = defaults }
134
136
  attrs.delay = delay;
135
137
  attrs.will_run_at = Date.now() + delay * 1000;
136
138
  }
137
- if (runAt) {
139
+ if (runAt && runAt > now) {
140
+ // 如果 runAt 大于当前时间,则延迟执行,否则直接执行
138
141
  attrs.delay = 1;
139
142
  attrs.will_run_at = runAt * 1000;
140
143
  }
@@ -42,6 +42,7 @@ import createQueue from '../libs/queue';
42
42
  import { CHARGE_SUPPORTED_CHAIN_TYPES, EVM_CHAIN_TYPES } from '../libs/constants';
43
43
  import { getCheckoutSessionSubscriptionIds, getSubscriptionCreateSetup } from '../libs/session';
44
44
  import { syncStripeSubscriptionAfterRecovery } from '../integrations/stripe/handlers/subscription';
45
+ import { getLock } from '../libs/lock';
45
46
 
46
47
  type PaymentJob = {
47
48
  paymentIntentId: string;
@@ -798,11 +799,26 @@ export const handlePayment = async (job: PaymentJob) => {
798
799
  // try payment capture and reschedule on error
799
800
  logger.info('PaymentIntent capture attempt', { id: paymentIntent.id, attempt: invoice?.attempt_count });
800
801
  let result;
802
+
803
+ // Use lock to prevent race condition with subscription queue
804
+ const lock = getLock(`payment-${paymentIntent.id}`);
805
+
801
806
  try {
802
- await paymentIntent.update({ status: 'processing', last_payment_error: null });
803
- logger.info('PaymentIntent status updated to processing', {
804
- paymentIntentId: paymentIntent.id,
807
+ await lock.acquire();
808
+ logger.debug('Acquired lock for payment processing', {
809
+ paymentIntent: paymentIntent.id,
810
+ invoice: invoice?.id,
805
811
  });
812
+
813
+ // Re-check payment intent status after acquiring lock
814
+ await paymentIntent.reload();
815
+ if (paymentIntent.status === 'succeeded') {
816
+ logger.info('PaymentIntent already succeeded, skipping', { id: paymentIntent.id });
817
+ return;
818
+ }
819
+
820
+ await paymentIntent.update({ status: 'processing', last_payment_error: null });
821
+
806
822
  if (paymentMethod.type === 'arcblock') {
807
823
  if (invoice?.billing_reason === 'slash_stake') {
808
824
  await handleStakeSlash(invoice, paymentIntent, paymentMethod, customer, paymentCurrency);
@@ -984,6 +1000,8 @@ export const handlePayment = async (job: PaymentJob) => {
984
1000
  paymentQueue.delete(paymentIntent.id);
985
1001
  }
986
1002
  }
1003
+ } finally {
1004
+ await lock.release();
987
1005
  }
988
1006
  };
989
1007
 
@@ -1,5 +1,6 @@
1
1
  import type { LiteralUnion } from 'type-fest';
2
2
 
3
+ import { Op } from 'sequelize';
3
4
  import { createEvent } from '../libs/audit';
4
5
  import { ensurePassportRevoked } from '../integrations/blocklet/passport';
5
6
  import { batchHandleStripeSubscriptions } from '../integrations/stripe/resource';
@@ -8,7 +9,7 @@ import dayjs from '../libs/dayjs';
8
9
  import { events } from '../libs/event';
9
10
  import { getLock } from '../libs/lock';
10
11
  import logger from '../libs/logger';
11
- import { getGasPayerExtra } from '../libs/payment';
12
+ import { getGasPayerExtra, isDelegationSufficientForPayment } from '../libs/payment';
12
13
  import createQueue from '../libs/queue';
13
14
  import { getStatementDescriptor } from '../libs/session';
14
15
  import {
@@ -240,9 +241,17 @@ const handleSubscriptionBeforeCancel = async (subscription: Subscription) => {
240
241
  });
241
242
 
242
243
  if (invoice) {
243
- // schedule invoice job
244
- invoiceQueue.push({ id: invoice.id, job: { invoiceId: invoice.id, retryOnError: false } });
245
- logger.info('Invoice job scheduled before cancel', { invoice: invoice.id, subscription: subscription.id });
244
+ // Just schedule the invoice processing, don't wait for completion
245
+ // The actual payment will be handled by handleFinalInvoicePayment during stake operations
246
+ await invoiceQueue.pushAndWait({
247
+ id: invoice.id,
248
+ job: { invoiceId: invoice.id, retryOnError: false },
249
+ });
250
+
251
+ logger.info('Final invoice job scheduled for async processing', {
252
+ invoice: invoice.id,
253
+ subscription: subscription.id,
254
+ });
246
255
 
247
256
  // persist invoice id
248
257
  await subscription.update({ latest_invoice_id: invoice.id });
@@ -341,9 +350,94 @@ const handleSubscriptionAfterRecover = async (subscription: Subscription) => {
341
350
  logger.info(`Subscription job scheduled for next billing cycle after recover: ${subscription.id}`);
342
351
  };
343
352
 
344
- const handleStakeSlashAfterCancel = async (subscription: Subscription) => {
353
+ /**
354
+ * Handle final metered invoice payment before stake operations
355
+ * Checks user balance and slashes stake if insufficient funds
356
+ */
357
+ const handleFinalInvoicePayment = async (
358
+ subscription: Subscription,
359
+ paymentMethod: PaymentMethod,
360
+ paymentCurrency: PaymentCurrency
361
+ ) => {
362
+ // Check if there's any unpaid final metered invoice
363
+ const lastInvoice = await Invoice.findOne({
364
+ where: {
365
+ subscription_id: subscription.id,
366
+ billing_reason: 'subscription_cancel',
367
+ status: { [Op.in]: ['open', 'uncollectible'] },
368
+ amount_remaining: { [Op.gt]: '0' },
369
+ },
370
+ order: [['created_at', 'DESC']],
371
+ });
372
+
373
+ if (!lastInvoice) {
374
+ logger.info('No unpaid final invoice found, skipping payment handling', {
375
+ subscription: subscription.id,
376
+ });
377
+ return;
378
+ }
379
+
380
+ // Check payment status and handle accordingly
381
+ const paymentIntent = await PaymentIntent.findByPk(lastInvoice.payment_intent_id);
382
+ if (!paymentIntent) {
383
+ logger.warn('PaymentIntent not found for final invoice', {
384
+ subscription: subscription.id,
385
+ invoice: lastInvoice.id,
386
+ });
387
+ return;
388
+ }
389
+
390
+ // If payment already succeeded, skip processing
391
+ if (paymentIntent.status === 'succeeded') {
392
+ logger.info('Final invoice already paid successfully, skipping', {
393
+ subscription: subscription.id,
394
+ invoice: lastInvoice.id,
395
+ paymentIntentStatus: paymentIntent.status,
396
+ });
397
+ return;
398
+ }
399
+
400
+ const customer = await Customer.findByPk(subscription.customer_id);
401
+ if (!customer) {
402
+ logger.warn('Final invoice settlement skipped because customer not found', {
403
+ subscription: subscription.id,
404
+ invoice: lastInvoice.id,
405
+ });
406
+ return;
407
+ }
408
+
409
+ logger.info('Found unpaid final invoice, checking user delegation balance', {
410
+ subscription: subscription.id,
411
+ invoice: lastInvoice.id,
412
+ amount: lastInvoice.amount_remaining,
413
+ });
414
+
415
+ const paymentSettings = lastInvoice.payment_settings;
416
+ const payer = paymentSettings?.payment_method_options?.arcblock?.payer as string;
417
+
418
+ const hasSufficientBalance = await isDelegationSufficientForPayment({
419
+ paymentMethod,
420
+ paymentCurrency,
421
+ userDid: payer || customer.did,
422
+ amount: lastInvoice.amount_remaining,
423
+ });
424
+
425
+ if (!hasSufficientBalance.sufficient) {
426
+ logger.info('User has insufficient balance, slashing stake to pay final invoice', {
427
+ subscription: subscription.id,
428
+ invoice: lastInvoice.id,
429
+ userBalance: hasSufficientBalance,
430
+ });
431
+
432
+ await handleStakeSlashAfterCancel(subscription, true);
433
+
434
+ logger.info('Stake slashed for final invoice, proceeding with remaining stake return');
435
+ }
436
+ };
437
+
438
+ export const handleStakeSlashAfterCancel = async (subscription: Subscription, forceSlash: boolean = false) => {
345
439
  const invoice = await Invoice.findByPk(subscription.latest_invoice_id);
346
- if (!invoice || invoice.status !== 'uncollectible') {
440
+ if (!invoice || (invoice.status !== 'uncollectible' && !forceSlash)) {
347
441
  logger.warn('Stake slashing aborted because invoice status', {
348
442
  subscription: subscription.id,
349
443
  invoice: invoice?.id,
@@ -379,125 +473,155 @@ const handleStakeSlashAfterCancel = async (subscription: Subscription) => {
379
473
  return;
380
474
  }
381
475
  const paymentIntent = await PaymentIntent.findByPk(invoice.payment_intent_id);
382
- if (!paymentIntent || paymentIntent.status === 'succeeded') {
383
- logger.warn('Stake slashing aborted because payment intent', {
476
+ if (!paymentIntent) {
477
+ logger.warn('Stake slashing aborted because payment intent not found', {
384
478
  subscription: subscription.id,
385
- paymentIntent: paymentIntent?.id,
386
- status: paymentIntent?.status,
479
+ invoice: invoice.id,
387
480
  });
388
481
  return;
389
482
  }
390
483
 
391
- // check the staking
392
- const client = method.getOcapClient();
393
- const address = await getSubscriptionStakeAddress(subscription, customer.did);
394
- const { state } = await client.getStakeState({ address });
395
- if (!state || !state.data?.value) {
396
- logger.warn('Stake slashing aborted because no staking state', {
484
+ // Use lock to prevent race condition with payment queue
485
+ const lock = getLock(`payment-${paymentIntent.id}`);
486
+
487
+ try {
488
+ await lock.acquire();
489
+ logger.debug('Acquired lock for stake slashing', {
397
490
  subscription: subscription.id,
398
- address,
491
+ paymentIntent: paymentIntent.id,
399
492
  });
400
- return;
401
- }
402
- // check for staking for this subscription
403
- const data = JSON.parse(state.data.value || '{}');
404
- if (!data[subscription.id] && !state.nonce) {
405
- logger.warn('Stake slashing aborted because no staking for subscription', {
493
+
494
+ // Re-check payment intent status after acquiring lock
495
+ await paymentIntent.reload();
496
+ if (paymentIntent.status === 'succeeded') {
497
+ logger.info('Payment already succeeded, skipping stake slashing', {
498
+ subscription: subscription.id,
499
+ paymentIntent: paymentIntent.id,
500
+ status: paymentIntent.status,
501
+ });
502
+ return;
503
+ }
504
+
505
+ // check the staking
506
+ const client = method.getOcapClient();
507
+ const address = await getSubscriptionStakeAddress(subscription, customer.did);
508
+ const { state } = await client.getStakeState({ address });
509
+ if (!state || !state.data?.value) {
510
+ logger.warn('Stake slashing aborted because no staking state', {
511
+ subscription: subscription.id,
512
+ address,
513
+ });
514
+ return;
515
+ }
516
+ // check for staking for this subscription
517
+ const data = JSON.parse(state.data.value || '{}');
518
+ if (!data[subscription.id] && !state.nonce) {
519
+ logger.warn('Stake slashing aborted because no staking for subscription', {
520
+ subscription: subscription.id,
521
+ address,
522
+ data,
523
+ });
524
+ return;
525
+ }
526
+
527
+ // check for staking for amount
528
+ const stakeEnough = await checkRemainingStake(method, currency, address, invoice.amount_remaining);
529
+ if (!stakeEnough.enough) {
530
+ logger.warn('Stake slashing aborted because no enough staking', {
531
+ subscription: subscription.id,
532
+ address,
533
+ staked: stakeEnough.staked,
534
+ revoked: stakeEnough.revoked,
535
+ });
536
+ return;
537
+ }
538
+
539
+ if (invoice.amount_remaining === '0') {
540
+ logger.warn('Stake slashing aborted because amount_remaining is 0', {
541
+ subscription: subscription.id,
542
+ address,
543
+ invoice: invoice.id,
544
+ });
545
+ return;
546
+ }
547
+
548
+ // do the slash
549
+ const signed = await client.signSlashStakeTx({
550
+ tx: {
551
+ itx: {
552
+ address,
553
+ outputs: [
554
+ { owner: wallet.address, tokens: [{ address: currency.contract, value: invoice.amount_remaining }] },
555
+ ],
556
+ message: 'uncollectible_past_due_invoice',
557
+ data: {
558
+ typeUrl: 'json',
559
+ // @ts-ignore
560
+ value: {
561
+ appId: wallet.address,
562
+ reason: 'subscription_cancel',
563
+ subscriptionId: subscription.id,
564
+ invoiceId: invoice.id,
565
+ paymentIntentId: paymentIntent.id,
566
+ },
567
+ },
568
+ },
569
+ },
570
+ wallet,
571
+ });
572
+ // @ts-ignore
573
+ const { buffer } = await client.encodeSlashStakeTx({ tx: signed });
574
+ // @ts-ignore
575
+ const txHash = await client.sendSlashStakeTx({ tx: signed, wallet }, getGasPayerExtra(buffer));
576
+ logger.info('Stake slashing done', {
406
577
  subscription: subscription.id,
578
+ amount: invoice.amount_remaining,
407
579
  address,
408
- data,
580
+ txHash,
581
+ invoice: invoice.id,
409
582
  });
410
- return;
411
- }
412
583
 
413
- // check for staking for amount
414
- const stakeEnough = await checkRemainingStake(method, currency, address, invoice.amount_remaining);
415
- if (!stakeEnough.enough) {
416
- logger.warn('Stake slashing aborted because no enough staking', {
584
+ await paymentIntent.update({
585
+ status: 'succeeded',
586
+ amount_received: invoice.amount_remaining,
587
+ capture_method: 'manual',
588
+ last_payment_error: null,
589
+ payment_details: {
590
+ arcblock: {
591
+ tx_hash: txHash,
592
+ payer: getSubscriptionPaymentAddress(subscription, 'arcblock'),
593
+ type: 'slash',
594
+ },
595
+ },
596
+ });
597
+ logger.info('PaymentIntent updated after stake slash', {
417
598
  subscription: subscription.id,
418
- address,
419
- staked: stakeEnough.staked,
420
- revoked: stakeEnough.revoked,
599
+ paymentIntent: paymentIntent.id,
600
+ status: 'succeeded',
601
+ });
602
+ await invoice.update({
603
+ paid: true,
604
+ status: 'paid',
605
+ amount_paid: paymentIntent.amount,
606
+ amount_remaining: '0',
607
+ attempt_count: invoice.attempt_count + 1,
608
+ attempted: true,
609
+ status_transitions: { ...invoice.status_transitions, paid_at: dayjs().unix() },
421
610
  });
422
- return;
423
- }
424
611
 
425
- if (invoice.amount_remaining === '0') {
426
- logger.warn('Stake slashing aborted because amount_remaining is 0', {
612
+ logger.info('Invoice updated after stake slash', {
427
613
  subscription: subscription.id,
428
- address,
429
614
  invoice: invoice.id,
615
+ status: 'paid',
616
+ });
617
+ } finally {
618
+ // Always release the lock
619
+ await lock.release();
620
+ logger.debug('Released lock for stake slashing', {
621
+ subscription: subscription.id,
622
+ paymentIntent: paymentIntent.id,
430
623
  });
431
- return;
432
624
  }
433
-
434
- // do the slash
435
- const signed = await client.signSlashStakeTx({
436
- tx: {
437
- itx: {
438
- address,
439
- outputs: [{ owner: wallet.address, tokens: [{ address: currency.contract, value: invoice.amount_remaining }] }],
440
- message: 'uncollectible_past_due_invoice',
441
- data: {
442
- typeUrl: 'json',
443
- // @ts-ignore
444
- value: {
445
- appId: wallet.address,
446
- reason: 'subscription_cancel',
447
- subscriptionId: subscription.id,
448
- invoiceId: invoice.id,
449
- paymentIntentId: paymentIntent.id,
450
- },
451
- },
452
- },
453
- },
454
- wallet,
455
- });
456
- // @ts-ignore
457
- const { buffer } = await client.encodeSlashStakeTx({ tx: signed });
458
- // @ts-ignore
459
- const txHash = await client.sendSlashStakeTx({ tx: signed, wallet }, getGasPayerExtra(buffer));
460
- logger.info('Stake slashing done', {
461
- subscription: subscription.id,
462
- amount: invoice.amount_remaining,
463
- address,
464
- txHash,
465
- invoice: invoice.id,
466
- });
467
-
468
- await paymentIntent.update({
469
- status: 'succeeded',
470
- amount_received: invoice.amount_remaining,
471
- capture_method: 'manual',
472
- last_payment_error: null,
473
- payment_details: {
474
- arcblock: {
475
- tx_hash: txHash,
476
- payer: getSubscriptionPaymentAddress(subscription, 'arcblock'),
477
- type: 'slash',
478
- },
479
- },
480
- });
481
- logger.info('PaymentIntent updated after stake slash', {
482
- subscription: subscription.id,
483
- paymentIntent: paymentIntent.id,
484
- status: 'succeeded',
485
- });
486
- await invoice.update({
487
- paid: true,
488
- status: 'paid',
489
- amount_paid: paymentIntent.amount,
490
- amount_remaining: '0',
491
- attempt_count: invoice.attempt_count + 1,
492
- attempted: true,
493
- status_transitions: { ...invoice.status_transitions, paid_at: dayjs().unix() },
494
- });
495
-
496
- logger.info('Invoice updated after stake slash', {
497
- subscription: subscription.id,
498
- invoice: invoice.id,
499
- status: 'paid',
500
- });
501
625
  };
502
626
 
503
627
  const ensureReturnStake = async (subscription: Subscription, paymentCurrencyId?: string, stakingAddress?: string) => {
@@ -528,6 +652,9 @@ const ensureReturnStake = async (subscription: Subscription, paymentCurrencyId?:
528
652
  return;
529
653
  }
530
654
 
655
+ // Handle any unpaid final invoice before stake return
656
+ await handleFinalInvoicePayment(subscription, paymentMethod, paymentCurrency);
657
+
531
658
  const result = await getSubscriptionStakeReturnSetup(subscription, address, paymentMethod, paymentCurrencyId);
532
659
 
533
660
  const stakeEnough = await checkRemainingStake(paymentMethod, paymentCurrency, address, result.return_amount);
@@ -562,7 +689,7 @@ const ensureReturnStake = async (subscription: Subscription, paymentCurrencyId?:
562
689
  invoice_id: invoice?.id,
563
690
  customer_id: subscription.customer_id,
564
691
  payment_method_id: paymentMethod.id,
565
- payment_intent_id: result?.lastInvoice?.payment_intent_id as string,
692
+ payment_intent_id: invoice?.payment_intent_id || '',
566
693
  subscription_id: subscription.id,
567
694
  attempt_count: 0,
568
695
  attempted: false,
@@ -959,6 +1086,13 @@ export const startSubscriptionQueue = async () => {
959
1086
  return;
960
1087
  }
961
1088
  if (['past_due', 'paused'].includes(x.status)) {
1089
+ const willCancel = x.cancel_at || x.cancel_at_period_end;
1090
+ if (x.status === 'past_due' && willCancel) {
1091
+ const existingJob = await subscriptionQueue.get(`cancel-${x.id}`);
1092
+ if (!existingJob) {
1093
+ await addSubscriptionJob(x, 'cancel', true, x.cancel_at || x.current_period_end);
1094
+ }
1095
+ }
962
1096
  logger.info(`skip add cycle subscription job because status is ${x.status}`, {
963
1097
  subscription: x.id,
964
1098
  action: 'cycle',
@@ -1086,10 +1220,11 @@ export async function addSubscriptionJob(
1086
1220
  sync?: boolean
1087
1221
  ) {
1088
1222
  const fn = sync ? 'pushAndWait' : 'push';
1089
- const jobId = action === 'cycle' ? subscription.id : `${action}-${subscription.id}`;
1090
- const cycleJob = await subscriptionQueue.get(jobId);
1223
+ const cycleJobId = subscription.id;
1224
+ const jobId = action === 'cycle' ? cycleJobId : `${action}-${subscription.id}`;
1225
+ const cycleJob = await subscriptionQueue.get(cycleJobId);
1091
1226
  if (replace && cycleJob) {
1092
- await subscriptionQueue.delete(jobId);
1227
+ await subscriptionQueue.delete(cycleJobId);
1093
1228
  logger.info(`subscription cycle job replaced with ${action} job`, { subscription: subscription.id });
1094
1229
  }
1095
1230
  if (action === 'cycle') {
@@ -45,8 +45,8 @@ export default {
45
45
  amount: fastCheckoutAmount,
46
46
  });
47
47
  const needDelegation = delegation.sufficient === false;
48
- const noStake = subscription.billing_thresholds?.no_stake;
49
- if (needDelegation || noStake) {
48
+ const requiredStake = !subscription.billing_thresholds?.no_stake;
49
+ if (needDelegation || !requiredStake) {
50
50
  claimsList.push({
51
51
  signature: await getDelegationTxClaim({
52
52
  mode: 'setup',
@@ -59,11 +59,12 @@ export default {
59
59
  trialing,
60
60
  billingThreshold,
61
61
  items,
62
+ requiredStake,
62
63
  }),
63
64
  });
64
65
  }
65
66
 
66
- if (!noStake) {
67
+ if (requiredStake) {
67
68
  claimsList.push({
68
69
  prepareTx: await getStakeTxClaim({
69
70
  userDid,
@@ -109,7 +110,7 @@ export default {
109
110
  const { setupIntent, subscription, paymentMethod, paymentCurrency, customer } =
110
111
  await ensureChangePaymentContext(subscriptionId);
111
112
 
112
- const noStake = subscription.billing_thresholds?.no_stake;
113
+ const requiredStake = !subscription.billing_thresholds?.no_stake;
113
114
 
114
115
  const result = request?.context?.store?.result || [];
115
116
  result.push({
@@ -123,7 +124,7 @@ export default {
123
124
  // 判断是否为最后一步
124
125
  const staking = result.find((x: any) => x.claim?.type === 'prepareTx' && x.claim?.meta?.purpose === 'staking');
125
126
  const isFinalStep =
126
- (paymentMethod.type === 'arcblock' && (staking || noStake)) || paymentMethod.type !== 'arcblock';
127
+ (paymentMethod.type === 'arcblock' && (staking || !requiredStake)) || paymentMethod.type !== 'arcblock';
127
128
 
128
129
  if (!isFinalStep) {
129
130
  await updateSession({
@@ -691,6 +691,7 @@ export async function getDelegationTxClaim({
691
691
  paymentMethod,
692
692
  trialing = false,
693
693
  billingThreshold = 0,
694
+ requiredStake = true,
694
695
  }: {
695
696
  userDid: string;
696
697
  userPk: string;
@@ -702,6 +703,7 @@ export async function getDelegationTxClaim({
702
703
  paymentMethod: PaymentMethod;
703
704
  trialing: boolean;
704
705
  billingThreshold?: number;
706
+ requiredStake?: boolean;
705
707
  }) {
706
708
  const amount = getFastCheckoutAmount(items, mode, paymentCurrency.id);
707
709
  const address = toDelegateAddress(userDid, wallet.address);
@@ -713,8 +715,8 @@ export async function getDelegationTxClaim({
713
715
  billingThreshold,
714
716
  paymentMethod,
715
717
  paymentCurrency,
718
+ requiredStake
716
719
  });
717
-
718
720
  if (mode === 'delegation') {
719
721
  tokenRequirements = [];
720
722
  }
@@ -1003,6 +1005,7 @@ export type TokenRequirementArgs = {
1003
1005
  paymentCurrency: PaymentCurrency;
1004
1006
  trialing: boolean;
1005
1007
  billingThreshold: number;
1008
+ requiredStake?: boolean;
1006
1009
  };
1007
1010
 
1008
1011
  export async function getTokenRequirements({
@@ -1012,6 +1015,7 @@ export async function getTokenRequirements({
1012
1015
  paymentCurrency,
1013
1016
  trialing = false,
1014
1017
  billingThreshold = 0,
1018
+ requiredStake
1015
1019
  }: TokenRequirementArgs) {
1016
1020
  const tokenRequirements = [];
1017
1021
  let amount = getFastCheckoutAmount(items, mode, paymentCurrency.id, !!trialing);
@@ -1039,7 +1043,7 @@ export async function getTokenRequirements({
1039
1043
  }
1040
1044
 
1041
1045
  // Add stake requirement to token requirement
1042
- if ((paymentMethod.type === 'arcblock' && mode !== 'delegation') || mode === 'setup') {
1046
+ if (requiredStake && ((paymentMethod.type === 'arcblock' && mode !== 'delegation') || mode === 'setup')) {
1043
1047
  const staking = getSubscriptionStakeSetup(
1044
1048
  items,
1045
1049
  paymentCurrency.id,
@@ -66,6 +66,7 @@ export default {
66
66
  const claimsList: any[] = [];
67
67
 
68
68
  const allSubscriptionIds = subscriptions.map((sub) => sub.id);
69
+ const requiredStake = !checkoutSession.subscription_data?.no_stake;
69
70
  if (paymentMethod.type === 'arcblock') {
70
71
  const delegation = await isDelegationSufficientForPayment({
71
72
  paymentMethod,
@@ -76,7 +77,7 @@ export default {
76
77
 
77
78
  // if we can complete purchase without any wallet interaction
78
79
  // we forced to delegate if we can skip stake
79
- if (delegation.sufficient === false || checkoutSession.subscription_data?.no_stake) {
80
+ if (delegation.sufficient === false || !requiredStake) {
80
81
  claimsList.push({
81
82
  signature: await getDelegationTxClaim({
82
83
  mode: checkoutSession.mode,
@@ -93,11 +94,12 @@ export default {
93
94
  trialing,
94
95
  billingThreshold: Math.max(minStakeAmount, billingThreshold),
95
96
  items,
97
+ requiredStake,
96
98
  }),
97
99
  });
98
100
  }
99
101
 
100
- if (!checkoutSession.subscription_data?.no_stake) {
102
+ if (requiredStake) {
101
103
  claimsList.push({
102
104
  prepareTx: await getStakeTxClaim({
103
105
  userDid,
@@ -162,10 +164,10 @@ export default {
162
164
  headers: request?.headers,
163
165
  },
164
166
  });
167
+ const requiredStake = !checkoutSession.subscription_data?.no_stake;
165
168
  const staking = result.find((x: any) => x.claim?.type === 'prepareTx' && x.claim?.meta?.purpose === 'staking');
166
169
  const isFinalStep =
167
- (paymentMethod.type === 'arcblock' && (staking || checkoutSession.subscription_data?.no_stake)) ||
168
- paymentMethod.type !== 'arcblock';
170
+ (paymentMethod.type === 'arcblock' && (staking || !requiredStake)) || paymentMethod.type !== 'arcblock';
169
171
  if (!isFinalStep) {
170
172
  await updateSession({
171
173
  result,
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.18.54
17
+ version: 1.18.55
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.18.54",
3
+ "version": "1.18.55",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -45,30 +45,30 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@abtnode/cron": "^1.16.44",
48
- "@arcblock/did": "^1.20.13",
48
+ "@arcblock/did": "^1.20.14",
49
49
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
50
- "@arcblock/did-connect": "^2.13.62",
51
- "@arcblock/did-util": "^1.20.13",
52
- "@arcblock/jwt": "^1.20.13",
53
- "@arcblock/ux": "^2.13.62",
54
- "@arcblock/validator": "^1.20.13",
55
- "@blocklet/did-space-js": "^1.0.58",
50
+ "@arcblock/did-connect": "^2.13.66",
51
+ "@arcblock/did-util": "^1.20.14",
52
+ "@arcblock/jwt": "^1.20.14",
53
+ "@arcblock/ux": "^2.13.66",
54
+ "@arcblock/validator": "^1.20.14",
55
+ "@blocklet/did-space-js": "^1.0.60",
56
56
  "@blocklet/js-sdk": "^1.16.44",
57
57
  "@blocklet/logger": "^1.16.44",
58
- "@blocklet/payment-react": "1.18.54",
58
+ "@blocklet/payment-react": "1.18.55",
59
59
  "@blocklet/sdk": "^1.16.44",
60
- "@blocklet/ui-react": "^2.13.62",
60
+ "@blocklet/ui-react": "^2.13.66",
61
61
  "@blocklet/uploader": "^0.1.95",
62
62
  "@blocklet/xss": "^0.1.36",
63
63
  "@mui/icons-material": "^5.16.6",
64
64
  "@mui/lab": "^5.0.0-alpha.173",
65
65
  "@mui/material": "^5.16.6",
66
66
  "@mui/system": "^5.16.6",
67
- "@ocap/asset": "^1.20.13",
68
- "@ocap/client": "^1.20.13",
69
- "@ocap/mcrypto": "^1.20.13",
70
- "@ocap/util": "^1.20.13",
71
- "@ocap/wallet": "^1.20.13",
67
+ "@ocap/asset": "^1.20.14",
68
+ "@ocap/client": "^1.20.14",
69
+ "@ocap/mcrypto": "^1.20.14",
70
+ "@ocap/util": "^1.20.14",
71
+ "@ocap/wallet": "^1.20.14",
72
72
  "@stripe/react-stripe-js": "^2.7.3",
73
73
  "@stripe/stripe-js": "^2.4.0",
74
74
  "ahooks": "^3.8.0",
@@ -123,7 +123,7 @@
123
123
  "devDependencies": {
124
124
  "@abtnode/types": "^1.16.44",
125
125
  "@arcblock/eslint-config-ts": "^0.3.3",
126
- "@blocklet/payment-types": "1.18.54",
126
+ "@blocklet/payment-types": "1.18.55",
127
127
  "@types/cookie-parser": "^1.4.7",
128
128
  "@types/cors": "^2.8.17",
129
129
  "@types/debug": "^4.1.12",
@@ -169,5 +169,5 @@
169
169
  "parser": "typescript"
170
170
  }
171
171
  },
172
- "gitHead": "6315d7f4272e88e6a840a1948fd9745db9011e16"
172
+ "gitHead": "87893b50ad72026312cd6b5d35e7f6f1181ad53a"
173
173
  }