payment-kit 1.23.10 → 1.23.11
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.
package/api/src/libs/invoice.ts
CHANGED
|
@@ -1121,20 +1121,27 @@ export async function retryUncollectibleInvoices(options: {
|
|
|
1121
1121
|
failed: [] as Array<{ id: string; reason: string }>,
|
|
1122
1122
|
};
|
|
1123
1123
|
|
|
1124
|
+
const now = dayjs().unix();
|
|
1125
|
+
const BATCH_SIZE = 10;
|
|
1126
|
+
const BATCH_DELAY_SECONDS = 0.1;
|
|
1127
|
+
|
|
1124
1128
|
const settledResults = await Promise.allSettled(
|
|
1125
1129
|
overdueInvoices.map(async (invoice, index) => {
|
|
1126
1130
|
const { paymentIntent } = invoice;
|
|
1127
|
-
const delay = index * 2;
|
|
1128
1131
|
if (!paymentIntent) {
|
|
1129
1132
|
throw new Error('No payment intent found');
|
|
1130
1133
|
}
|
|
1131
1134
|
|
|
1132
1135
|
await paymentIntent.update({ status: 'requires_capture' });
|
|
1136
|
+
|
|
1137
|
+
const batchIndex = Math.floor(index / BATCH_SIZE);
|
|
1138
|
+
const runAt = now + batchIndex * BATCH_DELAY_SECONDS;
|
|
1139
|
+
|
|
1133
1140
|
await emitAsync(
|
|
1134
1141
|
'payment.queued',
|
|
1135
1142
|
paymentIntent.id,
|
|
1136
1143
|
{ paymentIntentId: paymentIntent.id, retryOnError: true, ignoreMaxRetryCheck: true },
|
|
1137
|
-
{ sync: false,
|
|
1144
|
+
{ sync: false, runAt }
|
|
1138
1145
|
);
|
|
1139
1146
|
|
|
1140
1147
|
return invoice;
|
|
@@ -2,6 +2,7 @@ import { BN, fromUnitToToken } from '@ocap/util';
|
|
|
2
2
|
|
|
3
3
|
import { getLock } from '../libs/lock';
|
|
4
4
|
import logger from '../libs/logger';
|
|
5
|
+
import dayjs from '../libs/dayjs';
|
|
5
6
|
import createQueue from '../libs/queue';
|
|
6
7
|
import { createEvent } from '../libs/audit';
|
|
7
8
|
import {
|
|
@@ -551,7 +552,7 @@ export async function handleCreditConsumption(job: CreditConsumptionJob) {
|
|
|
551
552
|
});
|
|
552
553
|
await context.meterEvent.markAsCompleted();
|
|
553
554
|
if (context.subscription && context.subscription.status === 'past_due') {
|
|
554
|
-
handlePastDueSubscriptionRecovery(context.subscription, null);
|
|
555
|
+
await handlePastDueSubscriptionRecovery(context.subscription, null);
|
|
555
556
|
}
|
|
556
557
|
} else {
|
|
557
558
|
logger.warn('Credit consumption partially completed - insufficient balance', {
|
|
@@ -622,6 +623,7 @@ export const creditQueue = createQueue<CreditConsumptionJob>({
|
|
|
622
623
|
options: {
|
|
623
624
|
concurrency: 1,
|
|
624
625
|
maxRetries: 0,
|
|
626
|
+
enableScheduledJob: true,
|
|
625
627
|
},
|
|
626
628
|
});
|
|
627
629
|
|
|
@@ -755,58 +757,128 @@ events.on('customer.credit_grant.granted', async (creditGrant: CreditGrant) => {
|
|
|
755
757
|
});
|
|
756
758
|
|
|
757
759
|
async function retryFailedEventsForCustomer(creditGrant: CreditGrant): Promise<void> {
|
|
760
|
+
const grant = await CreditGrant.findByPk(creditGrant.id);
|
|
761
|
+
if (!grant) {
|
|
762
|
+
logger.error('Credit grant not found', {
|
|
763
|
+
creditGrantId: creditGrant.id,
|
|
764
|
+
});
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const customerId = grant.customer_id;
|
|
769
|
+
const currencyId = grant.currency_id;
|
|
770
|
+
const lock = getLock(`retry-failed-events-${customerId}-${currencyId}`);
|
|
771
|
+
|
|
758
772
|
try {
|
|
773
|
+
await lock.acquire();
|
|
774
|
+
|
|
759
775
|
logger.info('Retrying failed events for customer', {
|
|
760
|
-
customerId
|
|
761
|
-
currencyId
|
|
762
|
-
livemode:
|
|
776
|
+
customerId,
|
|
777
|
+
currencyId,
|
|
778
|
+
livemode: grant.livemode,
|
|
779
|
+
grantAmount: grant.amount,
|
|
780
|
+
grantRemaining: grant.remaining_amount,
|
|
763
781
|
});
|
|
782
|
+
|
|
783
|
+
const availableGrants = await CreditGrant.getAvailableCreditsForCustomer(customerId, currencyId);
|
|
784
|
+
const totalAvailableCredit = availableGrants.reduce((sum, g) => sum.add(new BN(g.remaining_amount)), new BN(0));
|
|
785
|
+
|
|
764
786
|
const [, , failedEvents] = await MeterEvent.getPendingAmounts({
|
|
765
|
-
customerId
|
|
766
|
-
currencyId
|
|
787
|
+
customerId,
|
|
788
|
+
currencyId,
|
|
767
789
|
status: ['requires_action', 'requires_capture'],
|
|
768
|
-
livemode:
|
|
790
|
+
livemode: grant.livemode,
|
|
769
791
|
});
|
|
770
792
|
|
|
771
793
|
if (failedEvents.length === 0) {
|
|
772
794
|
logger.debug('No failed events with pending credit found', {
|
|
773
|
-
customerId
|
|
774
|
-
currencyId
|
|
795
|
+
customerId,
|
|
796
|
+
currencyId,
|
|
775
797
|
});
|
|
776
798
|
return;
|
|
777
799
|
}
|
|
778
800
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
801
|
+
if (totalAvailableCredit.lte(new BN(0))) {
|
|
802
|
+
logger.debug('No available credit to retry failed events', {
|
|
803
|
+
customerId,
|
|
804
|
+
currencyId,
|
|
805
|
+
totalAvailableCredit: totalAvailableCredit.toString(),
|
|
806
|
+
});
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const sortedEvents = failedEvents.sort((a, b) => {
|
|
811
|
+
const aPending = new BN(a.credit_pending);
|
|
812
|
+
const bPending = new BN(b.credit_pending);
|
|
813
|
+
return aPending.cmp(bPending);
|
|
783
814
|
});
|
|
784
815
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
next_attempt: undefined,
|
|
791
|
-
})
|
|
792
|
-
)
|
|
793
|
-
);
|
|
816
|
+
let cumulativePending = new BN(0);
|
|
817
|
+
const affordableEvents: MeterEvent[] = [];
|
|
818
|
+
const now = dayjs().unix();
|
|
819
|
+
const BATCH_SIZE = 10;
|
|
820
|
+
const BATCH_DELAY_SECONDS = 0.1;
|
|
794
821
|
|
|
795
|
-
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
822
|
+
for (let i = 0; i < sortedEvents.length; i++) {
|
|
823
|
+
const event = sortedEvents[i];
|
|
824
|
+
if (!event) {
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
799
827
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
828
|
+
const eventPending = new BN(event.credit_pending);
|
|
829
|
+
const newCumulative = cumulativePending.add(eventPending);
|
|
830
|
+
|
|
831
|
+
if (newCumulative.gt(totalAvailableCredit)) {
|
|
832
|
+
if (eventPending.gt(totalAvailableCredit)) {
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// eslint-disable-next-line no-await-in-loop
|
|
838
|
+
await event.update({
|
|
839
|
+
status: 'pending',
|
|
840
|
+
attempt_count: 0,
|
|
841
|
+
next_attempt: undefined,
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
const batchIndex = Math.floor(i / BATCH_SIZE);
|
|
845
|
+
const runAt = now + batchIndex * BATCH_DELAY_SECONDS;
|
|
846
|
+
|
|
847
|
+
try {
|
|
848
|
+
// eslint-disable-next-line no-await-in-loop
|
|
849
|
+
await addCreditConsumptionJob(event.id, true, { runAt });
|
|
850
|
+
} catch (jobError: any) {
|
|
851
|
+
logger.error('Failed to add credit consumption job after status update', {
|
|
852
|
+
eventId: event.id,
|
|
853
|
+
customerId,
|
|
854
|
+
currencyId,
|
|
855
|
+
error: jobError.message,
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
affordableEvents.push(event);
|
|
860
|
+
cumulativePending = newCumulative;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const skippedEvents = failedEvents.length - affordableEvents.length;
|
|
864
|
+
|
|
865
|
+
logger.info('Completed retrying failed events', {
|
|
866
|
+
customerId,
|
|
867
|
+
currencyId,
|
|
868
|
+
totalEvents: failedEvents.length,
|
|
869
|
+
affordableEvents: affordableEvents.length,
|
|
870
|
+
skippedEvents,
|
|
871
|
+
totalAvailableCredit: totalAvailableCredit.toString(),
|
|
872
|
+
totalAffordablePending: cumulativePending.toString(),
|
|
873
|
+
grantCount: availableGrants.length,
|
|
804
874
|
});
|
|
805
875
|
} catch (error: any) {
|
|
806
|
-
logger.error('Failed to
|
|
807
|
-
customerId:
|
|
808
|
-
currencyId:
|
|
876
|
+
logger.error('Failed to retry failed events for customer', {
|
|
877
|
+
customerId: grant.customer_id,
|
|
878
|
+
currencyId: grant.currency_id,
|
|
809
879
|
error: error.message,
|
|
810
880
|
});
|
|
881
|
+
} finally {
|
|
882
|
+
lock.release();
|
|
811
883
|
}
|
|
812
884
|
}
|
|
@@ -327,7 +327,6 @@ router.post('/', auth, async (req, res) => {
|
|
|
327
327
|
router.get('/pending-amount', authMine, async (req, res) => {
|
|
328
328
|
try {
|
|
329
329
|
const params: any = {
|
|
330
|
-
status: ['requires_action', 'requires_capture'],
|
|
331
330
|
livemode: !!req.livemode,
|
|
332
331
|
};
|
|
333
332
|
if (req.query.subscription_id) {
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.11",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"prelint": "npm run types",
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
"@blocklet/error": "^0.3.5",
|
|
60
60
|
"@blocklet/js-sdk": "^1.17.8-beta-20260104-120132-cb5b1914",
|
|
61
61
|
"@blocklet/logger": "^1.17.8-beta-20260104-120132-cb5b1914",
|
|
62
|
-
"@blocklet/payment-broker-client": "1.23.
|
|
63
|
-
"@blocklet/payment-react": "1.23.
|
|
64
|
-
"@blocklet/payment-vendor": "1.23.
|
|
62
|
+
"@blocklet/payment-broker-client": "1.23.11",
|
|
63
|
+
"@blocklet/payment-react": "1.23.11",
|
|
64
|
+
"@blocklet/payment-vendor": "1.23.11",
|
|
65
65
|
"@blocklet/sdk": "^1.17.8-beta-20260104-120132-cb5b1914",
|
|
66
66
|
"@blocklet/ui-react": "^3.3.10",
|
|
67
67
|
"@blocklet/uploader": "^0.3.19",
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
"devDependencies": {
|
|
132
132
|
"@abtnode/types": "^1.17.8-beta-20260104-120132-cb5b1914",
|
|
133
133
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
134
|
-
"@blocklet/payment-types": "1.23.
|
|
134
|
+
"@blocklet/payment-types": "1.23.11",
|
|
135
135
|
"@types/cookie-parser": "^1.4.9",
|
|
136
136
|
"@types/cors": "^2.8.19",
|
|
137
137
|
"@types/debug": "^4.1.12",
|
|
@@ -178,5 +178,5 @@
|
|
|
178
178
|
"parser": "typescript"
|
|
179
179
|
}
|
|
180
180
|
},
|
|
181
|
-
"gitHead": "
|
|
181
|
+
"gitHead": "72af80fc6e91e88058665212985002966f55787c"
|
|
182
182
|
}
|