payment-kit 1.18.34 → 1.18.36

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.
@@ -0,0 +1,661 @@
1
+ import { getUrl } from '@blocklet/sdk';
2
+ import { literal, Op } from 'sequelize';
3
+ import { events } from '../libs/event';
4
+ import logger from '../libs/logger';
5
+ import createQueue from '../libs/queue';
6
+ import { uploadBillingInfo, BillingInfo, getEndpointAndSpaceDid } from '../libs/did-space';
7
+ import {
8
+ Customer,
9
+ EVMChainType,
10
+ Invoice,
11
+ Job,
12
+ PaymentCurrency,
13
+ PaymentIntent,
14
+ PaymentMethod,
15
+ PaymentMethodSettings,
16
+ Refund,
17
+ Subscription,
18
+ TInvoiceExpanded,
19
+ TRefundExpanded,
20
+ } from '../store/models';
21
+ import { CHARGE_SUPPORTED_CHAIN_TYPES } from '../libs/constants';
22
+ import env from '../libs/env';
23
+ import dayjs from '../libs/dayjs';
24
+ import { getExplorerTxUrl } from '../libs/util';
25
+
26
+ // Types
27
+ export type SpaceUploadType = 'invoice' | 'refund' | 'customer' | 'customerInvoiceChunk' | 'customerRefundChunk';
28
+ export type SpaceUploadData = {
29
+ invoice: { id: string };
30
+ refund: { id: string };
31
+ customer: { id: string };
32
+ customerInvoiceChunk: {
33
+ customerId: string;
34
+ chunkIndex: number;
35
+ totalChunks: number;
36
+ ids: string[];
37
+ processTime: number;
38
+ };
39
+ customerRefundChunk: {
40
+ customerId: string;
41
+ chunkIndex: number;
42
+ totalChunks: number;
43
+ ids: string[];
44
+ processTime: number;
45
+ };
46
+ };
47
+
48
+ export type SpaceUploadJob = {
49
+ type: SpaceUploadType;
50
+ data: SpaceUploadData[SpaceUploadType];
51
+ };
52
+
53
+ // Utility functions
54
+ const categoryMap = {
55
+ stake: 'stake',
56
+ slash_stake: 'slashStake',
57
+ overdraft_protection: 'fee',
58
+ stake_overdraft_protection: 'stake',
59
+ recharge: 'recharge',
60
+ return_stake: 'returnStake',
61
+ } as const;
62
+
63
+ const getBillingCategory = (billingReason: string): string => {
64
+ if (
65
+ billingReason.includes('stake') ||
66
+ billingReason.includes('recharge') ||
67
+ billingReason === 'overdraft_protection'
68
+ ) {
69
+ return categoryMap[billingReason as keyof typeof categoryMap] || 'payment';
70
+ }
71
+ return 'payment';
72
+ };
73
+
74
+ const createSubscriptionInfo = (subscription: Subscription) => ({
75
+ type: 'subscription' as const,
76
+ id: subscription.id,
77
+ name: subscription.description || subscription.id,
78
+ status: subscription.status,
79
+ period_start: subscription.current_period_start,
80
+ period_end: subscription.current_period_end,
81
+ link: getUrl(`customer/subscription/${subscription.id}`),
82
+ });
83
+
84
+ const handleInvoicePaid = async (invoiceId: string) => {
85
+ const invoice = (await Invoice.findByPk(invoiceId, {
86
+ include: [
87
+ {
88
+ model: Customer,
89
+ as: 'customer',
90
+ attributes: ['did'],
91
+ },
92
+ {
93
+ model: PaymentCurrency,
94
+ as: 'paymentCurrency',
95
+ attributes: ['symbol', 'decimal'],
96
+ },
97
+ {
98
+ model: PaymentMethod,
99
+ as: 'paymentMethod',
100
+ attributes: ['type', 'settings'],
101
+ },
102
+ {
103
+ model: Subscription,
104
+ as: 'subscription',
105
+ attributes: ['id', 'description', 'status', 'current_period_start', 'current_period_end'],
106
+ },
107
+ {
108
+ model: PaymentIntent,
109
+ as: 'paymentIntent',
110
+ },
111
+ ],
112
+ })) as TInvoiceExpanded | null;
113
+
114
+ // Validation
115
+ if (!invoice) {
116
+ logger.info('Upload invoice skipped because invoice not found:', { invoiceId });
117
+ return;
118
+ }
119
+
120
+ if (invoice.metadata?.did_space_uploaded) {
121
+ logger.info('Upload invoice skipped because invoice already uploaded:', { invoiceId });
122
+ return;
123
+ }
124
+
125
+ const paymentDetails = invoice.paymentIntent?.payment_details || invoice.metadata?.payment_details;
126
+ if (!paymentDetails) {
127
+ logger.info('Upload invoice skipped because payment details not found:', { invoiceId });
128
+ return;
129
+ }
130
+
131
+ if (!CHARGE_SUPPORTED_CHAIN_TYPES.includes(invoice?.paymentMethod?.type)) {
132
+ logger.info('Upload invoice skipped because payment method is not supported:', {
133
+ invoiceId,
134
+ paymentMethod: invoice?.paymentMethod?.type,
135
+ });
136
+ return;
137
+ }
138
+
139
+ const txHash = paymentDetails[invoice.paymentMethod?.type].tx_hash;
140
+ if (!txHash) {
141
+ logger.info('Upload invoice skipped because tx hash not found:', { invoiceId });
142
+ return;
143
+ }
144
+
145
+ let spaceDid = null;
146
+ let endpoint = null;
147
+ try {
148
+ const result = await getEndpointAndSpaceDid(invoice.customer?.did);
149
+ spaceDid = result.spaceDid;
150
+ endpoint = result.endpoint;
151
+ } catch (error) {
152
+ logger.info('Customer space endpoint not available:', {
153
+ invoiceId,
154
+ did: invoice.customer?.did,
155
+ error: (error as Error).message,
156
+ });
157
+ return;
158
+ }
159
+
160
+ const methodInfo = (invoice.paymentMethod?.settings as PaymentMethodSettings)?.[
161
+ invoice.paymentMethod?.type as 'arcblock' | EVMChainType
162
+ ];
163
+ // Create billing info
164
+ const billInfo: BillingInfo = {
165
+ tx_hash: txHash,
166
+ timestamp: dayjs(invoice.updated_at).unix(),
167
+ invoice_id: invoice.id,
168
+ category: getBillingCategory(invoice.billing_reason),
169
+ description: invoice.description || 'Payment received',
170
+ amount: invoice.amount_paid,
171
+ currency: {
172
+ symbol: invoice.paymentCurrency?.symbol,
173
+ decimal: invoice.paymentCurrency?.decimal || 18,
174
+ type: invoice.paymentMethod?.type || '',
175
+ chain_id: methodInfo?.chain_id || '',
176
+ explorer_host: methodInfo?.explorer_host || '',
177
+ explorer_tx_url: methodInfo?.explorer_host
178
+ ? getExplorerTxUrl({
179
+ explorerHost: methodInfo?.explorer_host,
180
+ txHash,
181
+ type: invoice.paymentMethod?.type || '',
182
+ })
183
+ : '',
184
+ },
185
+ customer_did: invoice.customer?.did,
186
+ link: getUrl(`customer/invoice/${invoice.id}`),
187
+ app_pid: env.appPid,
188
+ };
189
+
190
+ if (invoice.subscription_id && invoice.subscription) {
191
+ billInfo.related = createSubscriptionInfo(invoice.subscription as Subscription);
192
+ }
193
+
194
+ // Upload billing info
195
+ const result = await uploadBillingInfo(invoice.customer?.did, billInfo, endpoint);
196
+ if (result) {
197
+ // @ts-ignore
198
+ await invoice.update({
199
+ metadata: {
200
+ ...invoice.metadata,
201
+ did_space_uploaded: true,
202
+ did_space: spaceDid,
203
+ },
204
+ });
205
+ logger.info('Successfully uploaded paid invoice:', {
206
+ invoiceId: invoice.id,
207
+ txHash,
208
+ customerDid: invoice.customer?.did,
209
+ });
210
+ } else {
211
+ logger.error('Failed to upload paid invoice:', {
212
+ invoiceId: invoice.id,
213
+ txHash,
214
+ customerDid: invoice.customer?.did,
215
+ });
216
+ }
217
+ };
218
+
219
+ const handleRefundPaid = async (refundId: string) => {
220
+ const refund = (await Refund.findByPk(refundId, {
221
+ include: [
222
+ { model: Customer, as: 'customer', attributes: ['did'] },
223
+ {
224
+ model: PaymentMethod,
225
+ as: 'paymentMethod',
226
+ attributes: ['type', 'settings'],
227
+ },
228
+ {
229
+ model: PaymentIntent,
230
+ as: 'paymentIntent',
231
+ },
232
+ {
233
+ model: PaymentCurrency,
234
+ as: 'paymentCurrency',
235
+ attributes: ['symbol', 'decimal'],
236
+ },
237
+ ],
238
+ })) as TRefundExpanded | null;
239
+ if (!refund) {
240
+ logger.info('Upload refund skipped because refund not found:', { refundId });
241
+ return;
242
+ }
243
+
244
+ if (refund.metadata?.did_space_uploaded) {
245
+ logger.info('Upload refund skipped because refund already uploaded:', { refundId });
246
+ return;
247
+ }
248
+
249
+ if (!CHARGE_SUPPORTED_CHAIN_TYPES.includes(refund.paymentMethod?.type)) {
250
+ logger.info('Upload refund skipped because payment method is not supported:', {
251
+ refundId,
252
+ paymentMethod: refund.paymentMethod?.type,
253
+ });
254
+ return;
255
+ }
256
+ // @ts-ignore
257
+ const txHash = refund?.payment_details?.[refund.paymentMethod?.type]?.tx_hash;
258
+ if (!txHash) {
259
+ logger.info('Upload refund skipped because tx hash not found:', { refundId });
260
+ return;
261
+ }
262
+
263
+ let spaceDid = null;
264
+ let endpoint = null;
265
+ try {
266
+ const result = await getEndpointAndSpaceDid(refund.customer?.did);
267
+ spaceDid = result.spaceDid;
268
+ endpoint = result.endpoint;
269
+ } catch (error) {
270
+ logger.info('Customer space endpoint not available:', {
271
+ refundId,
272
+ did: refund.customer?.did,
273
+ error: (error as Error).message,
274
+ });
275
+ return;
276
+ }
277
+
278
+ let invoice = null;
279
+ if (refund.invoice_id) {
280
+ invoice = await Invoice.findByPk(refund.invoice_id, {
281
+ include: [{ model: Customer, as: 'customer', attributes: ['did'] }],
282
+ });
283
+ }
284
+ const methodInfo = (refund.paymentMethod?.settings as PaymentMethodSettings)?.[
285
+ refund.paymentMethod?.type as 'arcblock' | EVMChainType
286
+ ];
287
+ const billInfo: BillingInfo = {
288
+ tx_hash: txHash,
289
+ timestamp: dayjs(refund.updated_at).unix(),
290
+ invoice_id: refund.id,
291
+ category: 'refund',
292
+ description: refund.description || 'Refund',
293
+ amount: refund.amount,
294
+ currency: {
295
+ symbol: refund.paymentCurrency?.symbol,
296
+ decimal: refund.paymentCurrency?.decimal || 18,
297
+ type: refund.paymentMethod?.type || '',
298
+ chain_id: methodInfo?.chain_id || '',
299
+ explorer_host: methodInfo?.explorer_host || '',
300
+ explorer_tx_url: methodInfo?.explorer_host
301
+ ? getExplorerTxUrl({
302
+ explorerHost: methodInfo?.explorer_host,
303
+ txHash,
304
+ type: refund.paymentMethod?.type || '',
305
+ })
306
+ : '',
307
+ },
308
+ customer_did: refund.customer?.did,
309
+ link: getUrl(`customer/invoice/${refund.invoice_id}`),
310
+ app_pid: env.appPid,
311
+ };
312
+ if (invoice) {
313
+ billInfo.related = {
314
+ type: 'invoice',
315
+ id: invoice.id,
316
+ name: invoice.description || invoice.id,
317
+ link: getUrl(`customer/invoice/${invoice.id}`),
318
+ };
319
+ }
320
+
321
+ const result = await uploadBillingInfo(refund.customer?.did, billInfo, endpoint);
322
+ if (result) {
323
+ // @ts-ignore
324
+ await refund.update({
325
+ metadata: {
326
+ ...refund.metadata,
327
+ did_space_uploaded: true,
328
+ did_space: spaceDid,
329
+ },
330
+ });
331
+ logger.info('Successfully uploaded paid refund:', {
332
+ refundId: refund.id,
333
+ txHash,
334
+ customerDid: refund.customer?.did,
335
+ });
336
+ } else {
337
+ logger.error('Failed to upload paid refund:', { refundId: refund.id, txHash, customerDid: refund.customer?.did });
338
+ }
339
+ };
340
+
341
+ /**
342
+ * create batch tasks for invoice or refund
343
+ * @param type 'invoice' | 'refund'
344
+ * @param customerId customer id
345
+ * @param records records list
346
+ * @param now current timestamp
347
+ * @returns number of new tasks created
348
+ */
349
+ const createBatchTasks = (
350
+ type: 'invoice' | 'refund',
351
+ customerId: string,
352
+ records: { id: string; created_at?: Date }[],
353
+ now: number
354
+ ): { tasksCreated: number; totalRecords: number } => {
355
+ if (records.length === 0) {
356
+ return { tasksCreated: 0, totalRecords: 0 };
357
+ }
358
+
359
+ const recordIds = records.map((record) => record.id);
360
+ const recordLength = recordIds.length;
361
+ const batchSize = 30;
362
+ const chunks = Math.ceil(recordLength / batchSize);
363
+ let tasksCreated = 0;
364
+
365
+ for (let i = 0; i < chunks; i++) {
366
+ const start = i * batchSize;
367
+ const end = Math.min(start + batchSize, recordLength);
368
+ const chunkRecordIds = recordIds.slice(start, end);
369
+ const processTime = new Date(records?.[start]?.created_at || now).getTime();
370
+ const chunkId = `space-${customerId}-${type}-chunk-${i}-${processTime}`;
371
+
372
+ spaceQueue.push({
373
+ id: chunkId,
374
+ job: {
375
+ type: type === 'invoice' ? 'customerInvoiceChunk' : 'customerRefundChunk',
376
+ data: {
377
+ customerId,
378
+ chunkIndex: i,
379
+ totalChunks: chunks,
380
+ ids: chunkRecordIds,
381
+ processTime,
382
+ },
383
+ },
384
+ delay: 60 * (i + 1), // add 1 minute delay for each chunk
385
+ });
386
+
387
+ tasksCreated++;
388
+ }
389
+
390
+ logger.info(`Created ${type} chunk tasks`, {
391
+ customerId,
392
+ chunks,
393
+ totalRecords: recordLength,
394
+ });
395
+
396
+ return {
397
+ tasksCreated,
398
+ totalRecords: recordLength,
399
+ };
400
+ };
401
+
402
+ const syncCustomerBillingToSpace = async (customerId: string) => {
403
+ try {
404
+ const now = Date.now();
405
+ const customer = await Customer.findByPkOrDid(customerId);
406
+ if (!customer) {
407
+ logger.info('Customer not found:', { customerId });
408
+ return;
409
+ }
410
+
411
+ try {
412
+ await getEndpointAndSpaceDid(customer.did);
413
+ } catch (error) {
414
+ logger.info('Customer space endpoint not available:', {
415
+ customerId,
416
+ did: customer.did,
417
+ error: (error as Error).message,
418
+ });
419
+ return;
420
+ }
421
+
422
+ const validMethods = await PaymentMethod.findAll({
423
+ where: {
424
+ type: { [Op.in]: CHARGE_SUPPORTED_CHAIN_TYPES },
425
+ },
426
+ attributes: ['id'],
427
+ });
428
+
429
+ if (!validMethods.length) {
430
+ logger.info('No valid payment methods found', { customerId });
431
+ return;
432
+ }
433
+
434
+ const validMethodIds = validMethods.map((method) => method.id);
435
+
436
+ let invoiceProcessTime = 0;
437
+ let refundProcessTime = 0;
438
+ try {
439
+ // get the latest process time from the queue
440
+ const [invoiceProcessQueue, refundProcessQueue] = await Promise.all([
441
+ Job.findOne({
442
+ where: {
443
+ queue: 'did-space',
444
+ id: { [Op.like]: `space-${customerId}-invoice-chunk-%` },
445
+ cancelled: false,
446
+ },
447
+ order: [['created_at', 'DESC']],
448
+ }),
449
+ Job.findOne({
450
+ where: {
451
+ queue: 'did-space',
452
+ id: { [Op.like]: `space-${customerId}-refund-chunk-%` },
453
+ cancelled: false,
454
+ },
455
+ order: [['created_at', 'DESC']],
456
+ }),
457
+ ]);
458
+
459
+ invoiceProcessTime = invoiceProcessQueue?.job?.data?.processTime ?? 0;
460
+ refundProcessTime = refundProcessQueue?.job?.data?.processTime ?? 0;
461
+ logger.info('Invoice and refund process time', {
462
+ customerId,
463
+ invoiceProcessTime,
464
+ refundProcessTime,
465
+ });
466
+ } catch (error) {
467
+ logger.error('Failed to get invoice and refund process time', { customerId, error });
468
+ }
469
+
470
+ let [invoices, refunds] = await Promise.all([
471
+ Invoice.findAll({
472
+ where: {
473
+ status: 'paid',
474
+ 'metadata.did_space_uploaded': { [Op.not]: true },
475
+ customer_id: customer.id,
476
+ default_payment_method_id: { [Op.in]: validMethodIds },
477
+ // only process invoices after the latest process time
478
+ created_at: { [Op.gte]: invoiceProcessTime },
479
+ },
480
+ attributes: ['id', 'metadata', 'payment_intent_id', 'created_at'],
481
+ order: [['created_at', 'DESC']],
482
+ }),
483
+ Refund.findAll({
484
+ where: {
485
+ status: 'succeeded',
486
+ 'metadata.did_space_uploaded': { [Op.not]: true },
487
+ customer_id: customer.id,
488
+ payment_method_id: { [Op.in]: validMethodIds },
489
+ [Op.and]: [literal('payment_details IS NOT NULL')],
490
+ // only process refunds after the latest process time
491
+ created_at: { [Op.gte]: refundProcessTime },
492
+ },
493
+ attributes: ['id', 'metadata', 'payment_details', 'created_at'],
494
+ order: [['created_at', 'DESC']],
495
+ }),
496
+ ]);
497
+
498
+ invoices = invoices.filter((x) => x.metadata?.payment_details || x.payment_intent_id);
499
+ refunds = refunds.filter((x) => x.payment_details);
500
+
501
+ const invoicesLength = invoices.length;
502
+ const refundsLength = refunds.length;
503
+ logger.info('Found records to upload:', {
504
+ customerId,
505
+ did: customer.did,
506
+ invoiceCount: invoicesLength,
507
+ refundCount: refundsLength,
508
+ });
509
+
510
+ if (invoicesLength === 0 && refundsLength === 0) {
511
+ logger.info('No records to process', { customerId });
512
+ return;
513
+ }
514
+
515
+ const invoiceChunks = createBatchTasks('invoice', customerId, invoices, now);
516
+
517
+ const refundChunks = createBatchTasks('refund', customerId, refunds, now);
518
+
519
+ logger.info('Completed creating all chunk tasks', {
520
+ customerId,
521
+ invoiceChunks: invoiceChunks.tasksCreated,
522
+ refundChunks: refundChunks.tasksCreated,
523
+ totalRecords: invoicesLength + refundsLength,
524
+ });
525
+ } catch (error) {
526
+ logger.error('Failed to create chunk tasks:', {
527
+ customerId,
528
+ error: error instanceof Error ? error.message : String(error),
529
+ });
530
+ }
531
+ };
532
+
533
+ const handleCustomerInvoiceChunk = async (data: SpaceUploadData['customerInvoiceChunk']) => {
534
+ const { customerId, chunkIndex, totalChunks, ids: invoiceIds } = data;
535
+
536
+ logger.info('Processing invoice chunk', {
537
+ customerId,
538
+ chunkIndex: chunkIndex + 1,
539
+ totalChunks,
540
+ invoiceCount: invoiceIds.length,
541
+ });
542
+
543
+ try {
544
+ await Promise.all(
545
+ invoiceIds.map((invoiceId) =>
546
+ handleInvoicePaid(invoiceId).catch((error) => {
547
+ logger.error('Failed to process invoice:', {
548
+ customerId,
549
+ invoiceId,
550
+ error: error.message,
551
+ });
552
+ })
553
+ )
554
+ );
555
+
556
+ logger.info('Completed invoice chunk processing', {
557
+ customerId,
558
+ chunkIndex: chunkIndex + 1,
559
+ totalChunks,
560
+ processedInvoices: invoiceIds.length,
561
+ });
562
+ } catch (error) {
563
+ logger.error('Invoice chunk processing failed:', {
564
+ customerId,
565
+ chunkIndex,
566
+ error: error instanceof Error ? error.message : String(error),
567
+ });
568
+ }
569
+ };
570
+
571
+ const handleCustomerRefundChunk = async (data: SpaceUploadData['customerRefundChunk']) => {
572
+ const { customerId, chunkIndex, totalChunks, ids: refundIds } = data;
573
+
574
+ logger.info('Processing refund chunk', {
575
+ customerId,
576
+ chunkIndex: chunkIndex + 1,
577
+ totalChunks,
578
+ refundCount: refundIds.length,
579
+ });
580
+
581
+ try {
582
+ await Promise.all(
583
+ refundIds.map((refundId) =>
584
+ handleRefundPaid(refundId).catch((error) => {
585
+ logger.error('Failed to process refund:', {
586
+ customerId,
587
+ refundId,
588
+ error: error.message,
589
+ });
590
+ })
591
+ )
592
+ );
593
+
594
+ logger.info('Completed refund chunk processing', {
595
+ customerId,
596
+ chunkIndex: chunkIndex + 1,
597
+ totalChunks,
598
+ processedRefunds: refundIds.length,
599
+ });
600
+ } catch (error) {
601
+ logger.error('Refund chunk processing failed:', {
602
+ customerId,
603
+ chunkIndex,
604
+ error: error instanceof Error ? error.message : String(error),
605
+ });
606
+ }
607
+ };
608
+
609
+ const handlers = {
610
+ invoice: (data: SpaceUploadData['invoice']) => handleInvoicePaid(data.id),
611
+ refund: (data: SpaceUploadData['refund']) => handleRefundPaid(data.id),
612
+ customer: (data: SpaceUploadData['customer']) => syncCustomerBillingToSpace(data.id),
613
+ customerInvoiceChunk: (data: SpaceUploadData['customerInvoiceChunk']) => handleCustomerInvoiceChunk(data),
614
+ customerRefundChunk: (data: SpaceUploadData['customerRefundChunk']) => handleCustomerRefundChunk(data),
615
+ };
616
+
617
+ export const handleSpaceUpload = async (job: SpaceUploadJob) => {
618
+ logger.info('Starting to handle space upload', job);
619
+ const handler = handlers[job.type];
620
+ if (!handler) {
621
+ logger.error('No handler found for job type', { job });
622
+ return;
623
+ }
624
+ // @ts-ignore
625
+ await handler(job.data);
626
+ };
627
+
628
+ export const spaceQueue = createQueue<SpaceUploadJob>({
629
+ name: 'did-space',
630
+ onJob: handleSpaceUpload,
631
+ options: {
632
+ concurrency: 5,
633
+ maxRetries: 3,
634
+ enableScheduledJob: true,
635
+ },
636
+ });
637
+
638
+ spaceQueue.on('failed', ({ id, job, error }) => {
639
+ logger.error('Space upload job failed', { id, job, error });
640
+ });
641
+
642
+ export const startUploadBillingInfoListener = () => {
643
+ events.on('invoice.paid', (invoice) => {
644
+ spaceQueue.push({
645
+ id: `space-${invoice.id}`,
646
+ job: {
647
+ type: 'invoice',
648
+ data: { id: invoice.id },
649
+ },
650
+ });
651
+ });
652
+
653
+ events.on('refund.succeeded', (refund) => {
654
+ spaceQueue.push({
655
+ id: `space-${refund.id}`,
656
+ job: { type: 'refund', data: { id: refund.id } },
657
+ });
658
+ });
659
+
660
+ logger.info('Space upload listeners started');
661
+ };
@@ -133,7 +133,7 @@ const doHandleSubscriptionInvoice = async ({
133
133
  const usageReportStart = usageStart || start - offset;
134
134
  const usageReportEnd = usageEnd || end - offset;
135
135
 
136
- if (subscription.status !== 'trialing') {
136
+ if (subscription.status !== 'trialing' && reason !== 'recover') {
137
137
  // check if usage report is empty
138
138
  const usageReportEmpty = await checkUsageReportEmpty(subscription, usageReportStart, usageReportEnd);
139
139
  if (usageReportEmpty) {