payment-kit 1.13.79 → 1.13.80

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.
@@ -20,6 +20,7 @@ export class SubscriptionTrailWillEndSchedule extends BaseSubscriptionScheduleNo
20
20
  trail_start: {
21
21
  [Op.gt]: 0,
22
22
  },
23
+ status: 'trailing',
23
24
  },
24
25
  attributes: ['id', 'current_period_start', 'current_period_end'],
25
26
  raw: true,
@@ -18,6 +18,7 @@ export class SubscriptionWillRenewSchedule extends BaseSubscriptionScheduleNotif
18
18
  [Op.lt]: this.end / 1000,
19
19
  },
20
20
  trail_start: 0,
21
+ status: 'active',
21
22
  },
22
23
  attributes: ['id', 'current_period_start', 'current_period_end'],
23
24
  raw: true,
@@ -56,6 +56,9 @@ export class SubscriptionRenewedEmailTemplate implements BaseEmailTemplate<Subsc
56
56
  if (!subscription) {
57
57
  throw new Error(`Subscription not found: ${this.options.subscriptionId}`);
58
58
  }
59
+ if (subscription.status !== 'active') {
60
+ throw new Error(`Subscription not active: ${this.options.subscriptionId}`);
61
+ }
59
62
 
60
63
  const customer = await Customer.findByPk(subscription.customer_id);
61
64
  if (!customer) {
@@ -57,6 +57,9 @@ export class SubscriptionSucceededEmailTemplate
57
57
  if (!subscription) {
58
58
  throw new Error(`Subscription not found: ${this.options.subscriptionId}`);
59
59
  }
60
+ if (subscription.status !== 'active') {
61
+ throw new Error(`Subscription not active: ${this.options.subscriptionId}`);
62
+ }
60
63
 
61
64
  const customer = await Customer.findByPk(subscription.customer_id);
62
65
  if (!customer) {
@@ -49,6 +49,9 @@ export class SubscriptionTrailStartEmailTemplate
49
49
  if (!subscription) {
50
50
  throw new Error(`Subscription not found: ${this.options.subscriptionId}`);
51
51
  }
52
+ if (subscription.status !== 'trialing') {
53
+ throw new Error(`Subscription not trialing: ${this.options.subscriptionId}`);
54
+ }
52
55
 
53
56
  const customer = await Customer.findByPk(subscription.customer_id);
54
57
  if (!customer) {
@@ -51,6 +51,9 @@ export class SubscriptionTrailWilEndEmailTemplate
51
51
  if (!subscription) {
52
52
  throw new Error(`Subscription not found: ${this.options.subscriptionId}`);
53
53
  }
54
+ if (subscription.status !== 'trialing') {
55
+ throw new Error(`Subscription not trialing: ${this.options.subscriptionId}`);
56
+ }
54
57
 
55
58
  const customer = await Customer.findByPk(subscription.customer_id);
56
59
  if (!customer) {
@@ -51,6 +51,9 @@ export class SubscriptionWillRenewEmailTemplate
51
51
  if (!subscription) {
52
52
  throw new Error(`Subscription not found: ${this.options.subscriptionId}`);
53
53
  }
54
+ if (subscription.status !== 'active') {
55
+ throw new Error(`Subscription not active: ${this.options.subscriptionId}`);
56
+ }
54
57
 
55
58
  const customer = await Customer.findByPk(subscription.customer_id);
56
59
  if (!customer) {
@@ -19,7 +19,6 @@ import {
19
19
  } from '../store/models';
20
20
  import type { TPaymentCurrency } from '../store/models/payment-currency';
21
21
  import { blocklet, wallet } from './auth';
22
- import logger from './logger';
23
22
  import { OCAP_PAYMENT_TX_TYPE } from './util';
24
23
 
25
24
  export interface SufficientForPaymentResult {
@@ -162,19 +161,16 @@ export async function getPaymentDetail(userDid: string, invoice: Invoice): Promi
162
161
 
163
162
  const paymentIntent = await PaymentIntent.findByPk(invoice.payment_intent_id);
164
163
  if (!paymentIntent) {
165
- logger.error('getPayment.error', { reason: 'NO_PAYMENT_INTENT' });
166
164
  return defaultResult;
167
165
  }
168
166
 
169
167
  const paymentMethod = await PaymentMethod.findByPk(paymentIntent?.payment_method_id);
170
168
  if (!paymentMethod) {
171
- logger.error('getPayment.error', { reason: 'NO_PAYMENT_METHOD' });
172
169
  return defaultResult;
173
170
  }
174
171
 
175
172
  const paymentCurrency = await PaymentCurrency.findByPk(paymentIntent.currency_id);
176
173
  if (!paymentCurrency) {
177
- logger.error('getPayment.error', { reason: 'NO_PAYMENT_CURRENCY' });
178
174
  return defaultResult;
179
175
  }
180
176
 
@@ -189,7 +185,6 @@ export async function getPaymentDetail(userDid: string, invoice: Invoice): Promi
189
185
 
190
186
  // Do not have enough permission
191
187
  if (!result.token) {
192
- logger.error('getPayment.error', { reason: result.reason });
193
188
  return defaultResult;
194
189
  }
195
190
 
@@ -56,7 +56,7 @@ export const eventQueue = createQueue<EventJob>({
56
56
  name: 'event',
57
57
  onJob: handleEvent,
58
58
  options: {
59
- concurrency: 1,
59
+ concurrency: 5,
60
60
  maxRetries: 3,
61
61
  },
62
62
  });
@@ -140,7 +140,7 @@ export const invoiceQueue = createQueue<InvoiceJob>({
140
140
  name: 'invoice',
141
141
  onJob: handleInvoice,
142
142
  options: {
143
- concurrency: 1,
143
+ concurrency: 2,
144
144
  maxRetries: 3,
145
145
  },
146
146
  });
@@ -103,7 +103,12 @@ type Updates = {
103
103
  };
104
104
  };
105
105
 
106
- export const handlePaymentFailed = async (paymentIntent: PaymentIntent, invoice: Invoice, error: PaymentError) => {
106
+ export const handlePaymentFailed = async (
107
+ paymentIntent: PaymentIntent,
108
+ invoice: Invoice,
109
+ error: PaymentError,
110
+ checkSubscriptionStatus = true
111
+ ) => {
107
112
  const now = dayjs().unix();
108
113
  const attemptCount = invoice.attempt_count + 1;
109
114
 
@@ -135,7 +140,7 @@ export const handlePaymentFailed = async (paymentIntent: PaymentIntent, invoice:
135
140
  return updates.retry;
136
141
  }
137
142
 
138
- if (subscription.status !== 'active') {
143
+ if (checkSubscriptionStatus && subscription.status !== 'active') {
139
144
  return updates.retry;
140
145
  }
141
146
 
@@ -249,6 +254,29 @@ export const handlePayment = async (job: PaymentJob) => {
249
254
  }
250
255
 
251
256
  const invoice = await Invoice.findByPk(paymentIntent.invoice_id);
257
+
258
+ // check max retry before doing any hard work
259
+ if (invoice && invoice.attempt_count > MAX_RETRY_COUNT) {
260
+ logger.info(`PaymentIntent capture aborted since max retry exceeded: ${paymentIntent.id}`);
261
+ const updates = await handlePaymentFailed(
262
+ paymentIntent,
263
+ invoice,
264
+ {
265
+ type: 'card_error',
266
+ code: 'max_retry_exceeded',
267
+ message: 'max_retry_exceeded',
268
+ },
269
+ false
270
+ );
271
+ await paymentIntent.update(updates.payment);
272
+ await invoice.update(updates.invoice);
273
+ if (!updates.invoice.next_payment_attempt) {
274
+ logger.info(`PaymentIntent job deleted since max retry exceeded: ${paymentIntent.id}`);
275
+ await paymentQueue.delete(paymentIntent.id);
276
+ }
277
+ return;
278
+ }
279
+
252
280
  const paymentSettings = invoice?.payment_settings || job.paymentSettings;
253
281
  if (!paymentSettings) {
254
282
  await paymentIntent.update({ status: 'requires_action' });
@@ -334,7 +362,7 @@ export const handlePayment = async (job: PaymentJob) => {
334
362
  // @ts-ignore
335
363
  const txHash = await client.sendTransferV2Tx({ tx: signed, wallet, delegator: payer }, getGasPayerExtra(buffer));
336
364
 
337
- logger.info(`PaymentIntent capture done: ${paymentIntent.id} with tx ${txHash}`);
365
+ logger.info('PaymentIntent capture done', { id: paymentIntent.id, txHash });
338
366
 
339
367
  await paymentIntent.update({
340
368
  status: 'succeeded',
@@ -396,6 +424,9 @@ export const handlePayment = async (job: PaymentJob) => {
396
424
  runAt: retryAt,
397
425
  });
398
426
  logger.error('PaymentIntent capture retry scheduled', { id: paymentIntent.id, retryAt });
427
+ } else {
428
+ logger.info('PaymentIntent job deleted since no retry', { id: paymentIntent.id });
429
+ paymentQueue.delete(paymentIntent.id);
399
430
  }
400
431
  }
401
432
  }
@@ -406,7 +437,7 @@ export const paymentQueue = createQueue<PaymentJob>({
406
437
  onJob: handlePayment,
407
438
  options: {
408
439
  concurrency: 1,
409
- maxRetries: 3,
440
+ maxRetries: 0,
410
441
  enableScheduledJob: true,
411
442
  },
412
443
  });
@@ -81,6 +81,11 @@ export const handleSubscription = async (job: SubscriptionJob) => {
81
81
  return;
82
82
  }
83
83
 
84
+ if (subscription.isActive() === false) {
85
+ logger.warn(`Subscription not active: ${job.subscriptionId}, so new invoice is skipped`);
86
+ return;
87
+ }
88
+
84
89
  // Do we still have the customer
85
90
  const customer = await Customer.findByPk(subscription.customer_id);
86
91
  if (!customer) {
@@ -215,8 +220,8 @@ export const subscriptionQueue = createQueue<SubscriptionJob>({
215
220
  name: 'subscription',
216
221
  onJob: handleSubscription,
217
222
  options: {
218
- concurrency: 1,
219
- maxRetries: 5,
223
+ concurrency: 5,
224
+ maxRetries: 3,
220
225
  enableScheduledJob: true,
221
226
  },
222
227
  });
@@ -111,8 +111,8 @@ export const webhookQueue = createQueue<WebhookJob>({
111
111
  name: 'webhook',
112
112
  onJob: handleWebhook,
113
113
  options: {
114
- concurrency: 2,
115
- maxRetries: 3,
114
+ concurrency: 5,
115
+ maxRetries: 0,
116
116
  enableScheduledJob: true,
117
117
  },
118
118
  });
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.13.79
17
+ version: 1.13.80
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.13.79",
3
+ "version": "1.13.80",
4
4
  "scripts": {
5
5
  "dev": "COMPONENT_STORE_URL=https://test.store.blocklet.dev blocklet dev",
6
6
  "eject": "vite eject",
@@ -109,7 +109,7 @@
109
109
  "@abtnode/types": "^1.16.20",
110
110
  "@arcblock/eslint-config": "^0.2.4",
111
111
  "@arcblock/eslint-config-ts": "^0.2.4",
112
- "@did-pay/types": "1.13.79",
112
+ "@did-pay/types": "1.13.80",
113
113
  "@types/cookie-parser": "^1.4.6",
114
114
  "@types/cors": "^2.8.17",
115
115
  "@types/dotenv-flow": "^3.3.3",
@@ -148,5 +148,5 @@
148
148
  "parser": "typescript"
149
149
  }
150
150
  },
151
- "gitHead": "2e35efe8ba1977c9547af7eeafeb29aeee257a90"
151
+ "gitHead": "7ffaad28cb871681cac2b7472307380bee4b0c39"
152
152
  }
@@ -111,20 +111,34 @@ export default function SubscriptionDetail(props: { id: string }) {
111
111
  value={formatTime(data.start_date ? data.start_date * 1000 : data.created_at)}
112
112
  divider
113
113
  />
114
- {!data.cancel_at && (
114
+ {data.status === 'active' && !data.cancel_at && (
115
115
  <InfoMetric
116
116
  label={t('admin.subscription.nextInvoice')}
117
117
  value={formatTime(data.current_period_end * 1000)}
118
118
  divider
119
119
  />
120
120
  )}
121
- {data.cancel_at && (
121
+ {['active', 'trailing'].includes(data.status) && data.cancel_at && (
122
122
  <InfoMetric
123
123
  label={t('admin.subscription.cancel.schedule')}
124
124
  value={formatTime(data.cancel_at * 1000)}
125
125
  divider
126
126
  />
127
127
  )}
128
+ {data.status !== 'canceled' && data.cancel_at_period_end && (
129
+ <InfoMetric
130
+ label={t('admin.subscription.cancel.schedule')}
131
+ value={formatTime(data.current_period_end * 1000)}
132
+ divider
133
+ />
134
+ )}
135
+ {data.status === 'canceled' && data.canceled_at && (
136
+ <InfoMetric
137
+ label={t('admin.subscription.cancel.done')}
138
+ value={formatTime(data.canceled_at * 1000)}
139
+ divider
140
+ />
141
+ )}
128
142
  </Stack>
129
143
  </Box>
130
144
  </Box>
@@ -84,6 +84,13 @@ export default function CustomerSubscription() {
84
84
  divider
85
85
  />
86
86
  )}
87
+ {data.status !== 'canceled' && data.cancel_at_period_end && (
88
+ <InfoMetric
89
+ label={t('admin.subscription.cancel.schedule')}
90
+ value={formatTime(data.current_period_end * 1000)}
91
+ divider
92
+ />
93
+ )}
87
94
  {data.status === 'canceled' && data.canceled_at && (
88
95
  <InfoMetric
89
96
  label={t('admin.subscription.cancel.done')}