payment-kit 1.18.29 → 1.18.31

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 (56) hide show
  1. package/api/src/crons/index.ts +8 -0
  2. package/api/src/crons/metering-subscription-detection.ts +9 -0
  3. package/api/src/integrations/arcblock/nft.ts +1 -0
  4. package/api/src/integrations/blocklet/passport.ts +1 -1
  5. package/api/src/integrations/stripe/handlers/invoice.ts +2 -2
  6. package/api/src/integrations/stripe/handlers/setup-intent.ts +29 -1
  7. package/api/src/integrations/stripe/handlers/subscription.ts +19 -15
  8. package/api/src/integrations/stripe/resource.ts +81 -1
  9. package/api/src/libs/audit.ts +42 -0
  10. package/api/src/libs/constants.ts +2 -0
  11. package/api/src/libs/env.ts +2 -2
  12. package/api/src/libs/invoice.ts +54 -7
  13. package/api/src/libs/notification/index.ts +72 -4
  14. package/api/src/libs/notification/template/base.ts +2 -0
  15. package/api/src/libs/notification/template/subscription-renew-failed.ts +1 -5
  16. package/api/src/libs/notification/template/subscription-renewed.ts +1 -5
  17. package/api/src/libs/notification/template/subscription-succeeded.ts +8 -18
  18. package/api/src/libs/notification/template/subscription-trial-start.ts +2 -10
  19. package/api/src/libs/notification/template/subscription-upgraded.ts +1 -5
  20. package/api/src/libs/payment.ts +48 -8
  21. package/api/src/libs/product.ts +1 -4
  22. package/api/src/libs/session.ts +600 -8
  23. package/api/src/libs/setting.ts +172 -0
  24. package/api/src/libs/subscription.ts +7 -69
  25. package/api/src/libs/ws.ts +5 -0
  26. package/api/src/queues/checkout-session.ts +42 -36
  27. package/api/src/queues/notification.ts +3 -2
  28. package/api/src/queues/payment.ts +56 -8
  29. package/api/src/queues/usage-record.ts +2 -10
  30. package/api/src/routes/checkout-sessions.ts +324 -187
  31. package/api/src/routes/connect/shared.ts +160 -38
  32. package/api/src/routes/connect/subscribe.ts +123 -64
  33. package/api/src/routes/payment-currencies.ts +11 -0
  34. package/api/src/routes/payment-links.ts +11 -1
  35. package/api/src/routes/payment-stats.ts +2 -2
  36. package/api/src/routes/payouts.ts +2 -1
  37. package/api/src/routes/settings.ts +45 -0
  38. package/api/src/routes/subscriptions.ts +1 -2
  39. package/api/src/store/migrations/20250408-subscription-grouping.ts +39 -0
  40. package/api/src/store/migrations/20250419-subscription-grouping.ts +69 -0
  41. package/api/src/store/models/checkout-session.ts +52 -0
  42. package/api/src/store/models/index.ts +1 -0
  43. package/api/src/store/models/payment-link.ts +6 -0
  44. package/api/src/store/models/subscription.ts +8 -6
  45. package/api/src/store/models/types.ts +32 -1
  46. package/api/tests/libs/session.spec.ts +423 -0
  47. package/api/tests/libs/subscription.spec.ts +0 -110
  48. package/blocklet.yml +3 -1
  49. package/package.json +25 -24
  50. package/scripts/sdk.js +486 -155
  51. package/src/locales/en.tsx +4 -0
  52. package/src/locales/zh.tsx +3 -0
  53. package/src/pages/admin/settings/vault-config/edit-form.tsx +58 -3
  54. package/src/pages/admin/settings/vault-config/index.tsx +35 -1
  55. package/src/pages/customer/subscription/change-payment.tsx +8 -3
  56. package/src/pages/integrations/overview.tsx +1 -1
@@ -0,0 +1,172 @@
1
+ import { Op } from 'sequelize';
2
+ import { CheckoutSession, Invoice, Setting, Subscription } from '../store/models';
3
+ import type { EventType, NotificationSetting } from '../store/models/types';
4
+ import logger from './logger';
5
+
6
+ const notificationSettingKey = 'notification_settings';
7
+ const settingIdKey = 'setting_id';
8
+
9
+ export const getSettingForNotification = async (settingKey: string | null): Promise<NotificationSetting | null> => {
10
+ if (!settingKey) {
11
+ return null;
12
+ }
13
+ try {
14
+ const setting = await Setting.findOne({
15
+ where: {
16
+ type: 'notification',
17
+ [Op.or]: [
18
+ {
19
+ id: settingKey,
20
+ },
21
+ {
22
+ component_did: settingKey,
23
+ },
24
+ {
25
+ mount_location: settingKey,
26
+ },
27
+ ],
28
+ },
29
+ });
30
+ return (setting?.settings || null) as NotificationSetting | null;
31
+ } catch (error) {
32
+ logger.error('getSettingForNotification error', error);
33
+ return null;
34
+ }
35
+ };
36
+
37
+ export function getDirectNotificationSetting(obj: {
38
+ metadata?: Record<string, any>;
39
+ subscription_data?: Record<string, any>;
40
+ }): NotificationSetting | null {
41
+ if (obj?.metadata?.[notificationSettingKey]) {
42
+ return obj.metadata[notificationSettingKey];
43
+ }
44
+
45
+ if (obj?.subscription_data?.[notificationSettingKey]) {
46
+ return obj.subscription_data[notificationSettingKey];
47
+ }
48
+
49
+ return null;
50
+ }
51
+
52
+ export function getSettingIdFromObject(obj: { metadata?: Record<string, any> }): string | null {
53
+ return obj?.metadata?.[settingIdKey] || null;
54
+ }
55
+
56
+ export async function getNotificationSettingFromSubscription(
57
+ subscription: Subscription
58
+ ): Promise<NotificationSetting | null> {
59
+ if (!subscription) {
60
+ return null;
61
+ }
62
+ const directSetting = getDirectNotificationSetting(subscription);
63
+ if (directSetting) {
64
+ return directSetting;
65
+ }
66
+
67
+ const settingId = getSettingIdFromObject(subscription);
68
+ if (settingId) {
69
+ return getSettingForNotification(settingId);
70
+ }
71
+
72
+ const checkoutSession = await CheckoutSession.findBySubscriptionId(subscription.id);
73
+ return checkoutSession ? getNotificationSettingFromCheckoutSession(checkoutSession) : null;
74
+ }
75
+
76
+ export async function getNotificationSettingFromCheckoutSession(
77
+ checkoutSession: CheckoutSession
78
+ ): Promise<NotificationSetting | null> {
79
+ if (!checkoutSession) {
80
+ return null;
81
+ }
82
+ const directSetting = getDirectNotificationSetting(checkoutSession);
83
+ if (directSetting) {
84
+ return directSetting;
85
+ }
86
+ const settingId = getSettingIdFromObject(checkoutSession);
87
+ if (settingId) {
88
+ const settings = await getSettingForNotification(settingId);
89
+ return settings || null;
90
+ }
91
+ return null;
92
+ }
93
+
94
+ export async function getNotificationSettingFromInvoice(invoice: Invoice): Promise<NotificationSetting | null> {
95
+ if (!invoice) {
96
+ return null;
97
+ }
98
+ const directSetting = getDirectNotificationSetting(invoice);
99
+ if (directSetting) {
100
+ return directSetting;
101
+ }
102
+
103
+ const settingId = getSettingIdFromObject(invoice);
104
+ if (settingId) {
105
+ return getSettingForNotification(settingId);
106
+ }
107
+
108
+ if (invoice.subscription_id) {
109
+ const subscription = await Subscription.findByPk(invoice.subscription_id);
110
+ if (subscription) {
111
+ return getNotificationSettingFromSubscription(subscription);
112
+ }
113
+ }
114
+
115
+ if (invoice.checkout_session_id) {
116
+ const checkoutSession = await CheckoutSession.findByPk(invoice.checkout_session_id);
117
+ if (checkoutSession) {
118
+ return getNotificationSettingFromCheckoutSession(checkoutSession);
119
+ }
120
+ }
121
+ return null;
122
+ }
123
+
124
+ export type NotificationSettingsProps = {
125
+ subscription?: Subscription;
126
+ invoice?: Invoice;
127
+ checkoutSession?: CheckoutSession;
128
+ };
129
+ export async function getNotificationSettings({
130
+ subscription,
131
+ invoice,
132
+ checkoutSession,
133
+ }: NotificationSettingsProps): Promise<NotificationSetting | null> {
134
+ if (subscription) {
135
+ const settings = await getNotificationSettingFromSubscription(subscription);
136
+ return settings || null;
137
+ }
138
+
139
+ if (checkoutSession) {
140
+ const settings = await getNotificationSettingFromCheckoutSession(checkoutSession);
141
+ return settings || null;
142
+ }
143
+
144
+ if (invoice) {
145
+ const settings = await getNotificationSettingFromInvoice(invoice);
146
+ return settings || null;
147
+ }
148
+
149
+ return null;
150
+ }
151
+
152
+ export function shouldSendSystemNotification(eventType: string, settings?: NotificationSetting | null): boolean {
153
+ if (!settings) {
154
+ return true;
155
+ }
156
+
157
+ const { include_events: includeEvents, exclude_events: excludeEvents, self_handle: selfHandle } = settings;
158
+ if (!selfHandle) {
159
+ // if self_handle is false, then the notification is sent by the system
160
+ return true;
161
+ }
162
+ if (includeEvents && includeEvents.length > 0) {
163
+ const isIncluded = includeEvents.includes(eventType as EventType);
164
+ return !isIncluded;
165
+ }
166
+
167
+ if (excludeEvents && excludeEvents.length > 0) {
168
+ const isExcluded = excludeEvents.includes(eventType as EventType);
169
+ return isExcluded;
170
+ }
171
+ return !selfHandle;
172
+ }
@@ -30,7 +30,12 @@ import { createEvent } from './audit';
30
30
  import dayjs from './dayjs';
31
31
  import env from './env';
32
32
  import logger from './logger';
33
- import { getPriceCurrencyOptions, getPriceUintAmountByCurrency, getRecurringPeriod } from './session';
33
+ import {
34
+ getPriceCurrencyOptions,
35
+ getPriceUintAmountByCurrency,
36
+ getRecurringPeriod,
37
+ getSubscriptionCreateSetup,
38
+ } from './session';
34
39
  import { getConnectQueryParam, getCustomerStakeAddress } from './util';
35
40
  import { wallet } from './auth';
36
41
  import { getGasPayerExtra } from './payment';
@@ -184,66 +189,6 @@ export function getSubscriptionStakeSetup(items: TLineItemExpanded[], currencyId
184
189
  return staking;
185
190
  }
186
191
 
187
- export function getSubscriptionCreateSetup(
188
- items: TLineItemExpanded[],
189
- currencyId: string,
190
- trialInDays = 0,
191
- trialEnd = 0
192
- ) {
193
- let setup = new BN(0);
194
-
195
- items.forEach((x) => {
196
- const price = getSubscriptionItemPrice(x);
197
- const unit = getPriceUintAmountByCurrency(price, currencyId);
198
- const amount = new BN(unit).mul(new BN(x.quantity));
199
- if (price.type === 'recurring') {
200
- if (price.recurring?.usage_type === 'licensed') {
201
- setup = setup.add(amount);
202
- }
203
- }
204
- if (price.type === 'one_time') {
205
- setup = setup.add(amount);
206
- }
207
- });
208
-
209
- const now = dayjs().unix();
210
- const item = items.find((x) => getSubscriptionItemPrice(x).type === 'recurring');
211
- const recurring = (item?.upsell_price || item?.price)?.recurring as PriceRecurring;
212
- const cycle = getRecurringPeriod(recurring);
213
-
214
- let trialStartAt = 0;
215
- let trialEndAt = 0;
216
- if (+trialEnd && trialEnd > now) {
217
- trialStartAt = now;
218
- trialEndAt = trialEnd;
219
- } else if (trialInDays) {
220
- trialStartAt = now;
221
- trialEndAt = dayjs().add(trialInDays, 'day').unix();
222
- }
223
-
224
- const periodStart = trialStartAt || now;
225
- const periodEnd = trialEndAt || dayjs().add(cycle, 'millisecond').unix();
226
-
227
- return {
228
- recurring,
229
- cycle: {
230
- duration: cycle,
231
- anchor: periodEnd,
232
- },
233
- trial: {
234
- start: trialStartAt,
235
- end: trialEndAt,
236
- },
237
- period: {
238
- start: periodStart,
239
- end: periodEnd,
240
- },
241
- amount: {
242
- setup: setup.toString(),
243
- },
244
- };
245
- }
246
-
247
192
  export function getSubscriptionCycleSetup(recurring: PriceRecurring, previousPeriodEnd: number) {
248
193
  const cycle = getRecurringPeriod(recurring);
249
194
 
@@ -262,7 +207,7 @@ export function getSubscriptionCycleAmount(items: TLineItemExpanded[], currencyI
262
207
 
263
208
  items.forEach((x) => {
264
209
  amount = amount.add(
265
- new BN(getPriceUintAmountByCurrency(getSubscriptionItemPrice(x), currencyId)).mul(new BN(x.quantity))
210
+ new BN(getPriceUintAmountByCurrency(getSubscriptionItemPrice(x) as any, currencyId)).mul(new BN(x.quantity))
266
211
  );
267
212
  });
268
213
 
@@ -458,13 +403,6 @@ export function shouldCancelSubscription(
458
403
  return false;
459
404
  }
460
405
 
461
- export function formatSubscriptionProduct(items: TLineItemExpanded[], maxLength = 3) {
462
- const names = items.map((x) => x.price?.product?.name).filter(Boolean);
463
- return (
464
- names.slice(0, maxLength).join(', ') + (names.length > maxLength ? ` and ${names.length - maxLength} more` : '')
465
- );
466
- }
467
-
468
406
  export async function canChangePaymentMethod(subscriptionId: string) {
469
407
  const item = await SubscriptionItem.findOne({ where: { subscription_id: subscriptionId } });
470
408
  const expanded = await Price.findOne({ where: { id: item!.price_id } });
@@ -88,4 +88,9 @@ export function initEventBroadcast() {
88
88
  events.on('customer.subscription.trial_end', (data: Subscription, extraParams?: Record<string, any>) => {
89
89
  broadcast('customer.subscription.trial_end', data, extraParams);
90
90
  });
91
+
92
+ // notification events
93
+ events.on('manual.notification', (data: Record<string, any>) => {
94
+ broadcast('manual.notification', data);
95
+ });
91
96
  }
@@ -20,6 +20,7 @@ import {
20
20
  Subscription,
21
21
  SubscriptionItem,
22
22
  } from '../store/models';
23
+ import { getCheckoutSessionSubscriptionIds } from '../libs/session';
23
24
 
24
25
  type CheckoutSessionJob = {
25
26
  id: string;
@@ -194,44 +195,49 @@ events.on('checkout.session.expired', async (checkoutSession: CheckoutSession) =
194
195
  });
195
196
  }
196
197
 
197
- if (checkoutSession.subscription_id) {
198
- const subscription = await Subscription.findByPk(checkoutSession.subscription_id);
199
- const stripeSubscriptionId = subscription?.payment_details?.stripe?.subscription_id;
200
- if (subscription && stripeSubscriptionId) {
201
- const method = await PaymentMethod.findByPk(subscription.default_payment_method_id);
202
- if (method?.type === 'stripe') {
203
- const client = method.getStripeClient();
204
- try {
205
- await client.subscriptions.cancel(stripeSubscriptionId, {
206
- prorate: false,
207
- invoice_now: false,
208
- cancellation_details: {
209
- comment: 'checkout_session_expired',
210
- feedback: 'unused',
211
- },
212
- });
213
- logger.info('Stripe Subscription for checkout session canceled on expire', {
214
- checkoutSession: checkoutSession.id,
215
- subscription: checkoutSession.subscription_id,
216
- stripeSubscription: stripeSubscriptionId,
217
- });
218
- } catch (err) {
219
- logger.error('Stripe Subscription for checkout session cancel failed on expire', {
220
- checkoutSession: checkoutSession.id,
221
- subscription: checkoutSession.subscription_id,
222
- stripeSubscription: stripeSubscriptionId,
223
- error: err,
224
- });
198
+ const subscriptionIds = getCheckoutSessionSubscriptionIds(checkoutSession);
199
+ if (subscriptionIds.length > 0) {
200
+ const subscriptions = await Subscription.findAll({ where: { id: { [Op.in]: subscriptionIds } } });
201
+ await Promise.all(
202
+ subscriptions.map(async (subscription) => {
203
+ const stripeSubscriptionId = subscription?.payment_details?.stripe?.subscription_id;
204
+ if (subscription && stripeSubscriptionId) {
205
+ const method = await PaymentMethod.findByPk(subscription.default_payment_method_id);
206
+ if (method?.type === 'stripe') {
207
+ const client = method.getStripeClient();
208
+ try {
209
+ await client.subscriptions.cancel(stripeSubscriptionId, {
210
+ prorate: false,
211
+ invoice_now: false,
212
+ cancellation_details: {
213
+ comment: 'checkout_session_expired',
214
+ feedback: 'unused',
215
+ },
216
+ });
217
+ logger.info('Stripe Subscription for checkout session canceled on expire', {
218
+ checkoutSession: checkoutSession.id,
219
+ subscription: checkoutSession.subscription_id,
220
+ stripeSubscription: stripeSubscriptionId,
221
+ });
222
+ } catch (err) {
223
+ logger.error('Stripe Subscription for checkout session cancel failed on expire', {
224
+ checkoutSession: checkoutSession.id,
225
+ subscription: checkoutSession.subscription_id,
226
+ stripeSubscription: stripeSubscriptionId,
227
+ error: err,
228
+ });
229
+ }
230
+ }
225
231
  }
226
- }
227
- }
228
232
 
229
- await SubscriptionItem.destroy({ where: { subscription_id: checkoutSession.subscription_id } });
230
- await Subscription.destroy({ where: { id: checkoutSession.subscription_id } });
231
- logger.info('Subscription and SubscriptionItem for checkout session deleted on expire', {
232
- checkoutSession: checkoutSession.id,
233
- subscription: checkoutSession.subscription_id,
234
- });
233
+ await SubscriptionItem.destroy({ where: { subscription_id: subscription.id } });
234
+ await Subscription.destroy({ where: { id: subscription.id } });
235
+ logger.info('Subscription and SubscriptionItem for checkout session deleted on expire', {
236
+ checkoutSession: checkoutSession.id,
237
+ subscription: subscription.id,
238
+ });
239
+ })
240
+ );
235
241
  }
236
242
 
237
243
  // update price lock status
@@ -235,7 +235,8 @@ function getNotificationTemplate(job: NotificationQueueJob): BaseEmailTemplate {
235
235
  async function handleNotificationJob(job: NotificationQueueJob): Promise<void> {
236
236
  try {
237
237
  const template = getNotificationTemplate(job);
238
- await new Notification(template).send();
238
+
239
+ await new Notification(template, job.type).send();
239
240
  logger.info('handleImmediateNotificationJob.success', { job });
240
241
  } catch (error) {
241
242
  logger.error('handleImmediateNotificationJob.error', error);
@@ -384,7 +385,7 @@ function getAggregatedNotificationTemplate(job: AggregatedNotificationJob): Base
384
385
  async function handleAggregatedNotificationJob(job: AggregatedNotificationJob): Promise<void> {
385
386
  try {
386
387
  const template = await getAggregatedNotificationTemplate(job);
387
- await new Notification(template).send();
388
+ await new Notification(template, job.type).send();
388
389
  logger.info('handleAggregatedNotificationJob.success', { job });
389
390
  } catch (error) {
390
391
  logger.error('handleAggregatedNotificationJob.error', error);
@@ -1,6 +1,7 @@
1
1
  import isEmpty from 'lodash/isEmpty';
2
2
 
3
3
  import { BN } from '@ocap/util';
4
+ import pAll from 'p-all';
4
5
  import { ensureStakedForGas } from '../integrations/arcblock/stake';
5
6
  import { transferErc20FromUser } from '../integrations/ethereum/token';
6
7
  import { createEvent } from '../libs/audit';
@@ -17,7 +18,6 @@ import {
17
18
  getDueUnit,
18
19
  getMaxRetryCount,
19
20
  getMinRetryMail,
20
- getSubscriptionCreateSetup,
21
21
  getSubscriptionStakeAddress,
22
22
  isSubscriptionOverdraftProtectionEnabled,
23
23
  shouldCancelSubscription,
@@ -39,7 +39,8 @@ import { ensureOverdraftProtectionInvoiceAndItems } from '../libs/invoice';
39
39
  import { Lock } from '../store/models';
40
40
  import { ensureOverdraftProtectionPrice } from '../libs/overdraft-protection';
41
41
  import createQueue from '../libs/queue';
42
- import { EVM_CHAIN_TYPES } from '../libs/constants';
42
+ import { CHARGE_SUPPORTED_CHAIN_TYPES, EVM_CHAIN_TYPES } from '../libs/constants';
43
+ import { getCheckoutSessionSubscriptionIds, getSubscriptionCreateSetup } from '../libs/session';
43
44
 
44
45
  type PaymentJob = {
45
46
  paymentIntentId: string;
@@ -231,6 +232,19 @@ export const handlePaymentSucceed = async (
231
232
  if (isEmpty(result)) {
232
233
  // reset billing cycle anchor and cancel_* if we are recovering from payment failed
233
234
  if (subscription.cancel_at && subscription.cancel_at !== subscription.current_period_end) {
235
+ const now = dayjs().unix();
236
+ if (now <= subscription.current_period_end) {
237
+ // if payment succeeds before current_period_end, we should active this subscription
238
+ await subscription.update({
239
+ status: 'active',
240
+ cancel_at: 0,
241
+ cancel_at_period_end: false,
242
+ // @ts-ignore
243
+ cancelation_details: null,
244
+ });
245
+ logger.info(`Subscription ${subscription.id} recovered on payment done ${paymentIntent.id}: cancel rest`);
246
+ return;
247
+ }
234
248
  const subscriptionItems = await SubscriptionItem.findAll({ where: { subscription_id: subscription.id } });
235
249
  const lineItems = await Price.expand(subscriptionItems.map((x) => x.toJSON()));
236
250
  const setup = getSubscriptionCreateSetup(lineItems, subscription.currency_id, 0);
@@ -287,11 +301,24 @@ export const handlePaymentSucceed = async (
287
301
  checkoutSessionId: checkoutSession.id,
288
302
  });
289
303
  });
290
- await checkoutSession.update({
291
- status: 'complete',
292
- payment_status: 'paid',
293
- payment_details: paymentIntent.payment_details,
294
- });
304
+ if (['subscription', 'setup'].includes(checkoutSession.mode) && invoice.subscription_id) {
305
+ await checkoutSession.increment('success_subscription_count', { by: 1 });
306
+ await checkoutSession.reload();
307
+ const subscriptionIds = getCheckoutSessionSubscriptionIds(checkoutSession);
308
+ if (checkoutSession.success_subscription_count === subscriptionIds.length) {
309
+ await checkoutSession.update({
310
+ status: 'complete',
311
+ payment_status: 'paid',
312
+ payment_details: paymentIntent.payment_details,
313
+ });
314
+ }
315
+ } else {
316
+ await checkoutSession.update({
317
+ status: 'complete',
318
+ payment_status: 'paid',
319
+ payment_details: paymentIntent.payment_details,
320
+ });
321
+ }
295
322
  logger.info(`CheckoutSession ${checkoutSession.id} updated on payment done ${paymentIntent.id}`);
296
323
  }
297
324
  }
@@ -509,6 +536,7 @@ export const handlePaymentFailed = async (
509
536
  logger.warn('Subscription moved to past_due after payment failed', {
510
537
  subscription: subscription.id,
511
538
  payment: paymentIntent.id,
539
+ cancelUpdates,
512
540
  gracePeriodStart,
513
541
  graceDuration,
514
542
  dueUnit,
@@ -802,14 +830,16 @@ export const handlePayment = async (job: PaymentJob) => {
802
830
  wallet,
803
831
  delegator: result.delegator,
804
832
  });
833
+ logger.info('PaymentIntent signed', { signed });
805
834
  // @ts-ignore
806
835
  const { buffer } = await client.encodeTransferV2Tx({ tx: signed });
836
+ logger.info('PaymentIntent buffer', { buffer, gas: getGasPayerExtra(buffer) });
807
837
  const txHash = await client.sendTransferV2Tx(
808
838
  // @ts-ignore
809
839
  { tx: signed, wallet, delegator: result.delegator },
810
840
  getGasPayerExtra(buffer)
811
841
  );
812
-
842
+ logger.info('PaymentIntent txHash', { txHash });
813
843
  logger.info('PaymentIntent capture done', { id: paymentIntent.id, txHash });
814
844
 
815
845
  await paymentIntent.update({
@@ -1022,3 +1052,21 @@ events.on('payment.queued', async (id, job, args = {}) => {
1022
1052
  events.emit('payment.queued.error', { id, job, error });
1023
1053
  }
1024
1054
  });
1055
+
1056
+ export async function startDepositVaultQueue() {
1057
+ logger.debug('startDepositVaultQueue');
1058
+ const paymentCurrencies = (await PaymentCurrency.scope('withVaultConfig').findAll({
1059
+ include: [{ model: PaymentMethod, as: 'payment_method' }],
1060
+ })) as (PaymentCurrency & { payment_method: PaymentMethod })[];
1061
+ await pAll(
1062
+ paymentCurrencies.map((x) => {
1063
+ if (CHARGE_SUPPORTED_CHAIN_TYPES.includes(x.payment_method.type) && x.vault_config?.enabled === true) {
1064
+ depositVaultQueue.push({ id: `deposit-vault-${x.id}`, job: { currencyId: x.id } });
1065
+ }
1066
+ return async () => {};
1067
+ }),
1068
+ {
1069
+ concurrency: 5,
1070
+ }
1071
+ );
1072
+ }
@@ -5,15 +5,7 @@ import { getLock } from '../libs/lock';
5
5
  import logger from '../libs/logger';
6
6
  import createQueue from '../libs/queue';
7
7
  import { getPriceUintAmountByCurrency } from '../libs/session';
8
- import {
9
- Invoice,
10
- PaymentCurrency,
11
- Price,
12
- SubscriptionItem,
13
- TLineItemExpanded,
14
- TPrice,
15
- UsageRecord,
16
- } from '../store/models';
8
+ import { Invoice, PaymentCurrency, Price, SubscriptionItem, TLineItemExpanded, UsageRecord } from '../store/models';
17
9
  import { Subscription } from '../store/models/subscription';
18
10
  import { invoiceQueue } from './invoice';
19
11
  import { handleSubscriptionInvoice } from './subscription';
@@ -103,7 +95,7 @@ export const doHandleUsageRecord = async (job: UsageRecordJob) => {
103
95
  });
104
96
  // @ts-ignore
105
97
  const quantity = expanded?.price.transformQuantity(rawQuantity);
106
- const unitAmount = getPriceUintAmountByCurrency(expanded?.price as TPrice, subscription.currency_id);
98
+ const unitAmount = expanded?.price ? getPriceUintAmountByCurrency(expanded?.price, subscription.currency_id) : 0;
107
99
  const totalAmount = new BN(quantity).mul(new BN(unitAmount));
108
100
  const threshold = fromTokenToUnit(subscription.billing_thresholds.amount_gte, currency.decimal);
109
101
  logger.info('SubscriptionItem Usage check', {