payment-kit 1.13.239 → 1.13.240
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/crons/base.ts +8 -5
- package/api/src/crons/subscription-will-canceled.ts +5 -1
- package/api/src/crons/subscription-will-renew.ts +0 -1
- package/api/src/libs/env.ts +1 -0
- package/api/src/libs/invoice.ts +6 -5
- package/api/src/libs/notification/template/one-time-payment-succeeded.ts +10 -4
- package/api/src/libs/notification/template/subscription-canceled.ts +10 -6
- package/api/src/libs/notification/template/subscription-refund-succeeded.ts +9 -3
- package/api/src/libs/notification/template/subscription-renew-failed.ts +13 -6
- package/api/src/libs/notification/template/subscription-renewed.ts +11 -4
- package/api/src/libs/notification/template/subscription-succeeded.ts +10 -4
- package/api/src/libs/notification/template/subscription-trial-start.ts +2 -2
- package/api/src/libs/notification/template/subscription-trial-will-end.ts +1 -1
- package/api/src/libs/notification/template/subscription-upgraded.ts +10 -4
- package/api/src/libs/notification/template/subscription-will-canceled.ts +2 -2
- package/api/src/libs/notification/template/subscription-will-renew.ts +23 -2
- package/api/src/libs/payment.ts +7 -6
- package/api/src/libs/subscription.ts +5 -5
- package/api/src/libs/util.ts +13 -6
- package/api/src/locales/en.ts +26 -29
- package/api/src/locales/zh.ts +24 -24
- package/api/src/queues/notification.ts +9 -0
- package/api/src/queues/payment.ts +2 -2
- package/api/src/store/models/subscription.ts +2 -0
- package/blocklet.yml +1 -1
- package/package.json +34 -34
package/api/src/crons/base.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import dayjs from 'dayjs';
|
|
2
|
-
import
|
|
2
|
+
import clone from 'lodash/clone';
|
|
3
3
|
import pAll from 'p-all';
|
|
4
4
|
|
|
5
5
|
import { notificationCronConcurrency } from '../libs/env';
|
|
@@ -27,19 +27,21 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
|
|
|
27
27
|
abstract getSubscriptions(): Promise<Subscription[]>;
|
|
28
28
|
|
|
29
29
|
async run() {
|
|
30
|
-
const
|
|
30
|
+
const { name } = this.constructor;
|
|
31
31
|
|
|
32
|
-
logger.info(`${
|
|
32
|
+
logger.info(`${name}.run start`, { start: dayjs(this.start).toISOString(), end: dayjs(this.end).toISOString() });
|
|
33
33
|
|
|
34
34
|
const subscriptions = await this.getSubscriptions();
|
|
35
|
+
logger.info(`${name}.run.${subscriptions.length}`, subscriptions.length);
|
|
36
|
+
|
|
35
37
|
const subscriptionForWillRenew = this.getSubscriptionsForWillRenew(subscriptions);
|
|
36
38
|
|
|
37
39
|
await this.addTaskToQueue(subscriptionForWillRenew);
|
|
38
40
|
|
|
39
|
-
logger.info(`${
|
|
41
|
+
logger.info(`${name}.run end`);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
static DIFFS: Diff[] = [
|
|
44
|
+
static readonly DIFFS: Diff[] = [
|
|
43
45
|
{
|
|
44
46
|
value: 1,
|
|
45
47
|
unit: 'M',
|
|
@@ -164,6 +166,7 @@ export abstract class BaseSubscriptionScheduleNotification<Options extends any>
|
|
|
164
166
|
return;
|
|
165
167
|
}
|
|
166
168
|
|
|
169
|
+
logger.info(`BaseSubscriptionScheduleNotification.addTaskToQueue.${this.eventType}`, x);
|
|
167
170
|
notificationQueue.push(x);
|
|
168
171
|
};
|
|
169
172
|
}),
|
|
@@ -18,6 +18,10 @@ export class SubscriptionWillCanceledSchedule extends BaseSubscriptionScheduleNo
|
|
|
18
18
|
raw: true,
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
subscriptions.forEach((subscription) => {
|
|
22
|
+
subscription.current_period_end = subscription.cancel_at!;
|
|
23
|
+
});
|
|
24
|
+
|
|
21
25
|
return subscriptions;
|
|
22
26
|
}
|
|
23
27
|
|
|
@@ -37,7 +41,7 @@ export class SubscriptionWillCanceledSchedule extends BaseSubscriptionScheduleNo
|
|
|
37
41
|
subscriptionId: subscription.id,
|
|
38
42
|
willCancelValue: diff.value,
|
|
39
43
|
willCancelUnit: diff.unit,
|
|
40
|
-
required:
|
|
44
|
+
required: true,
|
|
41
45
|
},
|
|
42
46
|
},
|
|
43
47
|
delay: dayjs(subscription.current_period_end * 1000)
|
package/api/src/libs/env.ts
CHANGED
|
@@ -9,6 +9,7 @@ export const stripeInvoiceCronTime: string = process.env.STRIPE_INVOICE_CRON_TIM
|
|
|
9
9
|
export const stripePaymentCronTime: string = process.env.STRIPE_PAYMENT_CRON_TIME || '0 */20 * * * *'; // 默认每 20min 执行一次
|
|
10
10
|
export const stripeSubscriptionCronTime: string = process.env.STRIPE_SUBSCRIPTION_CRON_TIME || '0 10 */8 * * *'; // 默认每 8小时 执行一次
|
|
11
11
|
export const revokeStakeCronTime: string = process.env.REVOKE_STAKE_CRON_TIME || '0 */5 * * * *'; // 默认每 5 min 行一次
|
|
12
|
+
export const daysUntilCancel: string | undefined = process.env.DAYS_UNTIL_CANCEL;
|
|
12
13
|
|
|
13
14
|
export default {
|
|
14
15
|
...env,
|
package/api/src/libs/invoice.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import querystring from 'querystring';
|
|
2
|
-
|
|
3
1
|
import { component } from '@blocklet/sdk';
|
|
4
2
|
import type { LiteralUnion } from 'type-fest';
|
|
3
|
+
import { withQuery } from 'ufo';
|
|
5
4
|
|
|
6
5
|
import { getConnectQueryParam } from './util';
|
|
7
6
|
|
|
@@ -17,8 +16,10 @@ export function getCustomerInvoicePageUrl({
|
|
|
17
16
|
action?: LiteralUnion<'pay', string>;
|
|
18
17
|
}) {
|
|
19
18
|
return component.getUrl(
|
|
20
|
-
`customer/invoice/${invoiceId}
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
withQuery(`customer/invoice/${invoiceId}`, {
|
|
20
|
+
locale,
|
|
21
|
+
action,
|
|
22
|
+
...getConnectQueryParam({ userDid }),
|
|
23
|
+
})
|
|
23
24
|
);
|
|
24
25
|
}
|
|
@@ -104,7 +104,13 @@ export class OneTimePaymentSucceededEmailTemplate
|
|
|
104
104
|
|
|
105
105
|
// @ts-expect-error
|
|
106
106
|
const txHash: string | undefined = paymentIntent?.payment_details?.[paymentMethod.type]?.tx_hash;
|
|
107
|
-
const viewTxHashLink: string | undefined =
|
|
107
|
+
const viewTxHashLink: string | undefined =
|
|
108
|
+
txHash &&
|
|
109
|
+
getExplorerLink({
|
|
110
|
+
type: 'tx',
|
|
111
|
+
did: txHash,
|
|
112
|
+
chainHost,
|
|
113
|
+
});
|
|
108
114
|
|
|
109
115
|
return {
|
|
110
116
|
locale,
|
|
@@ -223,17 +229,17 @@ export class OneTimePaymentSucceededEmailTemplate
|
|
|
223
229
|
// @ts-ignore
|
|
224
230
|
actions: [
|
|
225
231
|
viewSubscriptionLink && {
|
|
226
|
-
name: 'viewSubscription',
|
|
232
|
+
name: translate('notification.common.viewSubscription', locale),
|
|
227
233
|
title: translate('notification.common.viewSubscription', locale),
|
|
228
234
|
link: viewSubscriptionLink,
|
|
229
235
|
},
|
|
230
236
|
viewInvoiceLink && {
|
|
231
|
-
name: '
|
|
237
|
+
name: translate('notification.common.viewInvoice', locale),
|
|
232
238
|
title: translate('notification.common.viewInvoice', locale),
|
|
233
239
|
link: viewInvoiceLink,
|
|
234
240
|
},
|
|
235
241
|
viewTxHashLink && {
|
|
236
|
-
name: 'viewTxHash',
|
|
242
|
+
name: translate('notification.common.viewTxHash', locale),
|
|
237
243
|
title: translate('notification.common.viewTxHash', locale),
|
|
238
244
|
link: viewTxHashLink as string,
|
|
239
245
|
},
|
|
@@ -49,13 +49,17 @@ export class SubscriptionCanceledEmailTemplate implements BaseEmailTemplate<Subs
|
|
|
49
49
|
if (subscription.status !== 'canceled') {
|
|
50
50
|
throw new Error(`Subscription(${this.options.subscriptionId}) status(${subscription.status}) must be canceled`);
|
|
51
51
|
}
|
|
52
|
-
if (subscription.cancelation_details?.reason
|
|
53
|
-
//
|
|
52
|
+
if (['payment_failed', 'payment_disputed'].includes(subscription.cancelation_details?.reason as string) === false) {
|
|
53
|
+
// 只有没钱导致订阅被取消了,或者管理员取消了,才会发送通知
|
|
54
54
|
logger.error(
|
|
55
|
-
`Subscription(${this.options.subscriptionId}) cancelation reason must be payment_disputed`,
|
|
56
|
-
|
|
55
|
+
`Subscription(${this.options.subscriptionId}) cancelation reason must be payment_disputed or payment_failed`,
|
|
56
|
+
{
|
|
57
|
+
cancelation_details: subscription.cancelation_details,
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Subscription(${this.options.subscriptionId}) cancelation reason must be payment_disputed or payment_failed`
|
|
57
62
|
);
|
|
58
|
-
throw new Error(`Subscription(${this.options.subscriptionId}) cancelation reason must be payment_disputed`);
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
const customer = await Customer.findByPk(subscription.customer_id);
|
|
@@ -229,7 +233,7 @@ export class SubscriptionCanceledEmailTemplate implements BaseEmailTemplate<Subs
|
|
|
229
233
|
// @ts-ignore
|
|
230
234
|
actions: [
|
|
231
235
|
viewSubscriptionLink && {
|
|
232
|
-
name: 'viewSubscription',
|
|
236
|
+
name: translate('notification.common.viewSubscription', locale),
|
|
233
237
|
title: translate('notification.common.viewSubscription', locale),
|
|
234
238
|
link: viewSubscriptionLink,
|
|
235
239
|
},
|
|
@@ -115,7 +115,13 @@ export class SubscriptionRefundSucceededEmailTemplate
|
|
|
115
115
|
|
|
116
116
|
// @ts-expect-error
|
|
117
117
|
const txHash: string | undefined = refund?.payment_details?.[paymentMethod.type]?.tx_hash;
|
|
118
|
-
const viewTxHashLink: string | undefined =
|
|
118
|
+
const viewTxHashLink: string | undefined =
|
|
119
|
+
txHash &&
|
|
120
|
+
getExplorerLink({
|
|
121
|
+
type: 'tx',
|
|
122
|
+
did: txHash,
|
|
123
|
+
chainHost,
|
|
124
|
+
});
|
|
119
125
|
|
|
120
126
|
return {
|
|
121
127
|
locale,
|
|
@@ -269,12 +275,12 @@ export class SubscriptionRefundSucceededEmailTemplate
|
|
|
269
275
|
// @ts-ignore
|
|
270
276
|
actions: [
|
|
271
277
|
{
|
|
272
|
-
name: 'viewSubscription',
|
|
278
|
+
name: translate('notification.common.viewSubscription', locale),
|
|
273
279
|
title: translate('notification.common.viewSubscription', locale),
|
|
274
280
|
link: viewSubscriptionLink,
|
|
275
281
|
},
|
|
276
282
|
viewTxHashLink && {
|
|
277
|
-
name: 'viewTxHash',
|
|
283
|
+
name: translate('notification.common.viewTxHash', locale),
|
|
278
284
|
title: translate('notification.common.viewTxHash', locale),
|
|
279
285
|
link: viewTxHashLink as string,
|
|
280
286
|
},
|
|
@@ -136,7 +136,14 @@ export class SubscriptionRenewFailedEmailTemplate
|
|
|
136
136
|
});
|
|
137
137
|
const txHash: string | undefined =
|
|
138
138
|
paymentIntent?.payment_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']?.tx_hash;
|
|
139
|
-
const viewTxHashLink: string | undefined =
|
|
139
|
+
const viewTxHashLink: string | undefined =
|
|
140
|
+
hasNft && txHash
|
|
141
|
+
? getExplorerLink({
|
|
142
|
+
type: 'tx',
|
|
143
|
+
did: txHash,
|
|
144
|
+
chainHost,
|
|
145
|
+
})
|
|
146
|
+
: undefined;
|
|
140
147
|
|
|
141
148
|
return {
|
|
142
149
|
locale,
|
|
@@ -181,7 +188,7 @@ export class SubscriptionRenewFailedEmailTemplate
|
|
|
181
188
|
body: `${translate('notification.subscriptionRenewFailed.body', locale, {
|
|
182
189
|
at,
|
|
183
190
|
productName,
|
|
184
|
-
reason:
|
|
191
|
+
reason: `<span style="color: red;">${reason}</span>`,
|
|
185
192
|
})}`,
|
|
186
193
|
// @ts-expect-error
|
|
187
194
|
attachments: [
|
|
@@ -269,17 +276,17 @@ export class SubscriptionRenewFailedEmailTemplate
|
|
|
269
276
|
// @ts-ignore
|
|
270
277
|
actions: [
|
|
271
278
|
{
|
|
272
|
-
name: 'viewSubscription',
|
|
279
|
+
name: translate('notification.common.viewSubscription', locale),
|
|
273
280
|
title: translate('notification.common.viewSubscription', locale),
|
|
274
281
|
link: viewSubscriptionLink,
|
|
275
282
|
},
|
|
276
283
|
{
|
|
277
|
-
name: '
|
|
278
|
-
title: translate('notification.
|
|
284
|
+
name: translate('notification.common.renewNow', locale),
|
|
285
|
+
title: translate('notification.common.renewNow', locale),
|
|
279
286
|
link: viewInvoiceLink,
|
|
280
287
|
},
|
|
281
288
|
viewTxHashLink && {
|
|
282
|
-
name: 'viewTxHash',
|
|
289
|
+
name: translate('notification.common.viewTxHash', locale),
|
|
283
290
|
title: translate('notification.common.viewTxHash', locale),
|
|
284
291
|
link: viewTxHashLink as string,
|
|
285
292
|
},
|
|
@@ -125,7 +125,14 @@ export class SubscriptionRenewedEmailTemplate implements BaseEmailTemplate<Subsc
|
|
|
125
125
|
});
|
|
126
126
|
const txHash: string | undefined =
|
|
127
127
|
paymentIntent?.payment_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']?.tx_hash;
|
|
128
|
-
const viewTxHashLink: string | undefined =
|
|
128
|
+
const viewTxHashLink: string | undefined =
|
|
129
|
+
hasNft && txHash
|
|
130
|
+
? getExplorerLink({
|
|
131
|
+
type: 'tx',
|
|
132
|
+
did: txHash,
|
|
133
|
+
chainHost,
|
|
134
|
+
})
|
|
135
|
+
: undefined;
|
|
129
136
|
|
|
130
137
|
return {
|
|
131
138
|
locale,
|
|
@@ -261,17 +268,17 @@ export class SubscriptionRenewedEmailTemplate implements BaseEmailTemplate<Subsc
|
|
|
261
268
|
// @ts-ignore
|
|
262
269
|
actions: [
|
|
263
270
|
{
|
|
264
|
-
name: 'viewSubscription',
|
|
271
|
+
name: translate('notification.common.viewSubscription', locale),
|
|
265
272
|
title: translate('notification.common.viewSubscription', locale),
|
|
266
273
|
link: viewSubscriptionLink,
|
|
267
274
|
},
|
|
268
275
|
{
|
|
269
|
-
name: '
|
|
276
|
+
name: translate('notification.common.viewInvoice', locale),
|
|
270
277
|
title: translate('notification.common.viewInvoice', locale),
|
|
271
278
|
link: viewInvoiceLink,
|
|
272
279
|
},
|
|
273
280
|
viewTxHashLink && {
|
|
274
|
-
name: 'viewTxHash',
|
|
281
|
+
name: translate('notification.common.viewTxHash', locale),
|
|
275
282
|
title: translate('notification.common.viewTxHash', locale),
|
|
276
283
|
link: viewTxHashLink as string,
|
|
277
284
|
},
|
|
@@ -145,7 +145,13 @@ export class SubscriptionSucceededEmailTemplate
|
|
|
145
145
|
const paymentIntent = await PaymentIntent.findByPk(invoice.payment_intent_id);
|
|
146
146
|
const txHash: string | undefined =
|
|
147
147
|
paymentIntent?.payment_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']?.tx_hash;
|
|
148
|
-
const viewTxHashLink: string | undefined =
|
|
148
|
+
const viewTxHashLink: string | undefined =
|
|
149
|
+
txHash &&
|
|
150
|
+
getExplorerLink({
|
|
151
|
+
type: 'tx',
|
|
152
|
+
did: txHash,
|
|
153
|
+
chainHost,
|
|
154
|
+
});
|
|
149
155
|
|
|
150
156
|
return {
|
|
151
157
|
locale,
|
|
@@ -270,17 +276,17 @@ export class SubscriptionSucceededEmailTemplate
|
|
|
270
276
|
// @ts-ignore
|
|
271
277
|
actions: [
|
|
272
278
|
{
|
|
273
|
-
name: 'viewSubscription',
|
|
279
|
+
name: translate('notification.common.viewSubscription', locale),
|
|
274
280
|
title: translate('notification.common.viewSubscription', locale),
|
|
275
281
|
link: viewSubscriptionLink,
|
|
276
282
|
},
|
|
277
283
|
{
|
|
278
|
-
name: '
|
|
284
|
+
name: translate('notification.common.viewInvoice', locale),
|
|
279
285
|
title: translate('notification.common.viewInvoice', locale),
|
|
280
286
|
link: viewInvoiceLink,
|
|
281
287
|
},
|
|
282
288
|
viewTxHashLink && {
|
|
283
|
-
name: 'viewTxHash',
|
|
289
|
+
name: translate('notification.common.viewTxHash', locale),
|
|
284
290
|
title: translate('notification.common.viewTxHash', locale),
|
|
285
291
|
link: viewTxHashLink as string,
|
|
286
292
|
},
|
|
@@ -262,12 +262,12 @@ export class SubscriptionTrialStartEmailTemplate
|
|
|
262
262
|
// @ts-ignore
|
|
263
263
|
actions: [
|
|
264
264
|
{
|
|
265
|
-
name: 'viewSubscription',
|
|
265
|
+
name: translate('notification.common.viewSubscription', locale),
|
|
266
266
|
title: translate('notification.common.viewSubscription', locale),
|
|
267
267
|
link: viewSubscriptionLink,
|
|
268
268
|
},
|
|
269
269
|
{
|
|
270
|
-
name: '
|
|
270
|
+
name: translate('notification.common.viewInvoice', locale),
|
|
271
271
|
title: translate('notification.common.viewInvoice', locale),
|
|
272
272
|
link: viewInvoiceLink,
|
|
273
273
|
},
|
|
@@ -248,7 +248,7 @@ export class SubscriptionTrialWilEndEmailTemplate
|
|
|
248
248
|
// @ts-ignore
|
|
249
249
|
actions: [
|
|
250
250
|
{
|
|
251
|
-
name: 'viewSubscription',
|
|
251
|
+
name: translate('notification.common.viewSubscription', locale),
|
|
252
252
|
title: translate('notification.common.viewSubscription', locale),
|
|
253
253
|
link: viewSubscriptionLink,
|
|
254
254
|
},
|
|
@@ -114,7 +114,13 @@ export class SubscriptionUpgradedEmailTemplate implements BaseEmailTemplate<Subs
|
|
|
114
114
|
|
|
115
115
|
// @ts-expect-error
|
|
116
116
|
const txHash: string | undefined = paymentIntent?.payment_details?.[paymentMethod.type]?.tx_hash;
|
|
117
|
-
const viewTxHashLink: string | undefined =
|
|
117
|
+
const viewTxHashLink: string | undefined =
|
|
118
|
+
txHash &&
|
|
119
|
+
getExplorerLink({
|
|
120
|
+
type: 'tx',
|
|
121
|
+
did: txHash,
|
|
122
|
+
chainHost,
|
|
123
|
+
});
|
|
118
124
|
|
|
119
125
|
return {
|
|
120
126
|
locale,
|
|
@@ -239,17 +245,17 @@ export class SubscriptionUpgradedEmailTemplate implements BaseEmailTemplate<Subs
|
|
|
239
245
|
// @ts-ignore
|
|
240
246
|
actions: [
|
|
241
247
|
{
|
|
242
|
-
name: 'viewSubscription',
|
|
248
|
+
name: translate('notification.common.viewSubscription', locale),
|
|
243
249
|
title: translate('notification.common.viewSubscription', locale),
|
|
244
250
|
link: viewSubscriptionLink,
|
|
245
251
|
},
|
|
246
252
|
{
|
|
247
|
-
name: '
|
|
253
|
+
name: translate('notification.common.viewInvoice', locale),
|
|
248
254
|
title: translate('notification.common.viewInvoice', locale),
|
|
249
255
|
link: viewInvoiceLink,
|
|
250
256
|
},
|
|
251
257
|
viewTxHashLink && {
|
|
252
|
-
name: 'viewTxHash',
|
|
258
|
+
name: translate('notification.common.viewTxHash', locale),
|
|
253
259
|
title: translate('notification.common.viewTxHash', locale),
|
|
254
260
|
link: viewTxHashLink,
|
|
255
261
|
},
|
|
@@ -208,12 +208,12 @@ export class SubscriptionWillCanceledEmailTemplate
|
|
|
208
208
|
// @ts-ignore
|
|
209
209
|
actions: [
|
|
210
210
|
viewSubscriptionLink && {
|
|
211
|
-
name: 'viewSubscription',
|
|
211
|
+
name: translate('notification.common.viewSubscription', locale),
|
|
212
212
|
title: translate('notification.common.viewSubscription', locale),
|
|
213
213
|
link: viewSubscriptionLink,
|
|
214
214
|
},
|
|
215
215
|
viewInvoiceLink && {
|
|
216
|
-
name: '
|
|
216
|
+
name: translate('notification.common.renewNow', locale),
|
|
217
217
|
title: translate('notification.common.renewNow', locale),
|
|
218
218
|
link: viewInvoiceLink,
|
|
219
219
|
},
|
|
@@ -7,12 +7,13 @@ import prettyMsI18n from 'pretty-ms-i18n';
|
|
|
7
7
|
|
|
8
8
|
import { getUserLocale } from '../../../integrations/blocklet/notification';
|
|
9
9
|
import { translate } from '../../../locales';
|
|
10
|
-
import { Customer, Invoice, Subscription } from '../../../store/models';
|
|
10
|
+
import { Customer, Invoice, PaymentMethod, Subscription } from '../../../store/models';
|
|
11
11
|
import { PaymentCurrency } from '../../../store/models/payment-currency';
|
|
12
12
|
import { PaymentDetail, getPaymentDetail } from '../../payment';
|
|
13
13
|
import { getMainProductName } from '../../product';
|
|
14
14
|
import { getCustomerSubscriptionPageUrl } from '../../subscription';
|
|
15
15
|
import { formatTime, getPrettyMsI18nLocale } from '../../time';
|
|
16
|
+
import { getExplorerLink } from '../../util';
|
|
16
17
|
import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
|
|
17
18
|
|
|
18
19
|
export interface SubscriptionWillRenewEmailTemplateOptions {
|
|
@@ -36,6 +37,7 @@ interface SubscriptionWillRenewEmailTemplateContext {
|
|
|
36
37
|
duration: string;
|
|
37
38
|
|
|
38
39
|
viewSubscriptionLink: string;
|
|
40
|
+
immediateRechargeLink: string;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
export class SubscriptionWillRenewEmailTemplate
|
|
@@ -97,6 +99,17 @@ export class SubscriptionWillRenewEmailTemplate
|
|
|
97
99
|
locale,
|
|
98
100
|
userDid,
|
|
99
101
|
});
|
|
102
|
+
const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(paymentCurrency.payment_method_id);
|
|
103
|
+
// @ts-ignore
|
|
104
|
+
const chainHost: string | undefined = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
|
|
105
|
+
const immediateRechargeLink: string = getExplorerLink({
|
|
106
|
+
type: 'account',
|
|
107
|
+
did: userDid,
|
|
108
|
+
chainHost,
|
|
109
|
+
queryParams: {
|
|
110
|
+
action: 'recharge',
|
|
111
|
+
},
|
|
112
|
+
})!;
|
|
100
113
|
|
|
101
114
|
return {
|
|
102
115
|
locale,
|
|
@@ -112,6 +125,7 @@ export class SubscriptionWillRenewEmailTemplate
|
|
|
112
125
|
duration,
|
|
113
126
|
|
|
114
127
|
viewSubscriptionLink,
|
|
128
|
+
immediateRechargeLink,
|
|
115
129
|
};
|
|
116
130
|
}
|
|
117
131
|
|
|
@@ -171,6 +185,7 @@ export class SubscriptionWillRenewEmailTemplate
|
|
|
171
185
|
paymentInfo,
|
|
172
186
|
|
|
173
187
|
viewSubscriptionLink,
|
|
188
|
+
immediateRechargeLink,
|
|
174
189
|
} = await this.getContext();
|
|
175
190
|
|
|
176
191
|
const canPay: boolean = paymentDetail.balance >= paymentDetail.price;
|
|
@@ -189,6 +204,7 @@ export class SubscriptionWillRenewEmailTemplate
|
|
|
189
204
|
at,
|
|
190
205
|
productName,
|
|
191
206
|
willRenewDuration,
|
|
207
|
+
balance: `${paymentDetail.balance} ${paymentDetail.symbol}`,
|
|
192
208
|
})}`
|
|
193
209
|
: `${translate('notification.subscriptionWillRenew.unableToPayBody', locale, {
|
|
194
210
|
at,
|
|
@@ -252,8 +268,13 @@ export class SubscriptionWillRenewEmailTemplate
|
|
|
252
268
|
].filter(Boolean),
|
|
253
269
|
// @ts-ignore
|
|
254
270
|
actions: [
|
|
271
|
+
!canPay && {
|
|
272
|
+
name: translate('notification.common.immediateRecharge', locale),
|
|
273
|
+
title: translate('notification.common.immediateRecharge', locale),
|
|
274
|
+
link: immediateRechargeLink,
|
|
275
|
+
},
|
|
255
276
|
{
|
|
256
|
-
name: 'viewSubscription',
|
|
277
|
+
name: translate('notification.common.viewSubscription', locale),
|
|
257
278
|
title: translate('notification.common.viewSubscription', locale),
|
|
258
279
|
link: viewSubscriptionLink,
|
|
259
280
|
},
|
package/api/src/libs/payment.ts
CHANGED
|
@@ -181,6 +181,12 @@ export async function getPaymentDetail(userDid: string, invoice: Invoice): Promi
|
|
|
181
181
|
symbol: '',
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
+
const paymentCurrency = await PaymentCurrency.findByPk(invoice.currency_id);
|
|
185
|
+
if (!paymentCurrency) {
|
|
186
|
+
return defaultResult;
|
|
187
|
+
}
|
|
188
|
+
Object.assign(defaultResult, { symbol: paymentCurrency.symbol });
|
|
189
|
+
|
|
184
190
|
const paymentIntent = await PaymentIntent.findByPk(invoice.payment_intent_id);
|
|
185
191
|
if (!paymentIntent) {
|
|
186
192
|
return defaultResult;
|
|
@@ -191,12 +197,7 @@ export async function getPaymentDetail(userDid: string, invoice: Invoice): Promi
|
|
|
191
197
|
return defaultResult;
|
|
192
198
|
}
|
|
193
199
|
|
|
194
|
-
|
|
195
|
-
if (!paymentCurrency) {
|
|
196
|
-
return defaultResult;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (paymentMethod.type === 'arcblock') {
|
|
200
|
+
if (['arcblock', 'ethereum'].includes(paymentMethod.type)) {
|
|
200
201
|
// balance enough token for payment?
|
|
201
202
|
const result = await isDelegationSufficientForPayment({
|
|
202
203
|
paymentMethod,
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import querystring from 'querystring';
|
|
2
|
-
|
|
3
1
|
import component from '@blocklet/sdk/lib/component';
|
|
4
2
|
import { BN } from '@ocap/util';
|
|
5
3
|
import type { LiteralUnion } from 'type-fest';
|
|
4
|
+
import { withQuery } from 'ufo';
|
|
6
5
|
|
|
7
6
|
import {
|
|
8
7
|
Customer,
|
|
@@ -29,9 +28,10 @@ export function getCustomerSubscriptionPageUrl({
|
|
|
29
28
|
userDid: string;
|
|
30
29
|
}) {
|
|
31
30
|
return component.getUrl(
|
|
32
|
-
`customer/subscription/${subscriptionId}
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
withQuery(`customer/subscription/${subscriptionId}`, {
|
|
32
|
+
locale,
|
|
33
|
+
...getConnectQueryParam({ userDid }),
|
|
34
|
+
})
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
|
package/api/src/libs/util.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { getUrl } from '@blocklet/sdk/lib/component';
|
|
|
4
4
|
import env from '@blocklet/sdk/lib/env';
|
|
5
5
|
import { customAlphabet } from 'nanoid';
|
|
6
6
|
import type { LiteralUnion } from 'type-fest';
|
|
7
|
+
import { withQuery } from 'ufo';
|
|
7
8
|
|
|
8
9
|
import dayjs from './dayjs';
|
|
9
10
|
|
|
@@ -172,11 +173,17 @@ export function getDataObjectFromQuery(
|
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
// @FIXME: 这个应该封装在某个通用类库里面 @jianchao @wangshijun
|
|
175
|
-
export function getExplorerLink(
|
|
176
|
-
|
|
177
|
-
did
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
export function getExplorerLink({
|
|
177
|
+
type,
|
|
178
|
+
did,
|
|
179
|
+
chainHost = undefined,
|
|
180
|
+
queryParams = {},
|
|
181
|
+
}: {
|
|
182
|
+
chainHost: string | undefined;
|
|
183
|
+
did: string | undefined;
|
|
184
|
+
type: LiteralUnion<'asset' | 'account' | 'tx' | 'token' | 'factory' | ' bridge', string>;
|
|
185
|
+
queryParams?: Record<string, string>;
|
|
186
|
+
}) {
|
|
180
187
|
if (!chainHost) return undefined;
|
|
181
188
|
try {
|
|
182
189
|
const chainUrl = new URL(chainHost);
|
|
@@ -203,7 +210,7 @@ export function getExplorerLink(
|
|
|
203
210
|
chainUrl.pathname = '/';
|
|
204
211
|
}
|
|
205
212
|
|
|
206
|
-
return chainUrl.href;
|
|
213
|
+
return withQuery(chainUrl.href, queryParams);
|
|
207
214
|
} catch {
|
|
208
215
|
return undefined;
|
|
209
216
|
}
|
package/api/src/locales/en.ts
CHANGED
|
@@ -24,7 +24,8 @@ export default flat({
|
|
|
24
24
|
minute: 'minute',
|
|
25
25
|
minutes: 'minutes',
|
|
26
26
|
cancellationReason: 'Cancellation reason',
|
|
27
|
-
renewNow: '
|
|
27
|
+
renewNow: 'Pay now',
|
|
28
|
+
immediateRecharge: 'Immediate recharge',
|
|
28
29
|
},
|
|
29
30
|
|
|
30
31
|
sendTo: 'Sent to',
|
|
@@ -40,9 +41,9 @@ export default flat({
|
|
|
40
41
|
|
|
41
42
|
subscriptionTrialWillEnd: {
|
|
42
43
|
title: 'The {productName} trial will end soon',
|
|
43
|
-
body: 'Your {productName}
|
|
44
|
+
body: 'Your trial for {productName} will end in {willRenewDuration}. Please ensure your account balance is sufficient for automatic billing after the trial ends. Thank you for your support and trust!',
|
|
44
45
|
unableToPayBody:
|
|
45
|
-
'Your {productName}
|
|
46
|
+
'Your trial for {productName} will end in {willRenewDuration}. <span style="color: red;">Your current balance is {balance}, which is less than {price}</span>, Please ensure your balance is sufficient for automatic billing. Thank you for your support and trust!',
|
|
46
47
|
},
|
|
47
48
|
|
|
48
49
|
subscriptionSucceed: {
|
|
@@ -61,39 +62,35 @@ export default flat({
|
|
|
61
62
|
},
|
|
62
63
|
|
|
63
64
|
subscriptionWillRenew: {
|
|
64
|
-
title: '{productName} will be
|
|
65
|
-
body: 'Your
|
|
65
|
+
title: '{productName} will be deducted automatically',
|
|
66
|
+
body: 'Your {productName} subscription has {willRenewDuration} until the next deduction. Your current balance is {balance}, please ensure that your account balance is sufficient so that the deduction is automatically completed after the maturity ({at}). I wish you all the best! ',
|
|
66
67
|
unableToPayBody:
|
|
67
|
-
'Your
|
|
68
|
-
renewAmount: '
|
|
68
|
+
'Your {productName} subscription has {willRenewDuration} until the next deduction. <span style="color: red;" > Your current balance is {balance}, less than {price}</span>, please ensure that your account balance is sufficient so that the deduction will be completed automatically after the expiration ({at}). I wish you all the best! ',
|
|
69
|
+
renewAmount: 'deduction amount ',
|
|
69
70
|
},
|
|
70
71
|
|
|
71
72
|
subscriptionRenewed: {
|
|
72
|
-
title: '{productName}
|
|
73
|
-
body: '
|
|
74
|
-
noExpenseIncurred: 'No
|
|
73
|
+
title: '{productName} payment successful',
|
|
74
|
+
body: 'Payment for your subscription {productName} is successfully collected on {at}. Thank you for your continued support and trust, I wish you a happy use! ',
|
|
75
|
+
noExpenseIncurred: 'No expenses incurred during the service ',
|
|
75
76
|
},
|
|
76
77
|
|
|
77
78
|
subscriptionRenewFailed: {
|
|
78
|
-
title: '
|
|
79
|
-
body:
|
|
80
|
-
renewNow: 'Renew now',
|
|
79
|
+
title: '{productName} charge failed',
|
|
80
|
+
body: 'We are sorry to inform you that your {productName} failed to be charged on {at}. The reason for the failure is {reason}. If you have any questions, please contact us in time. Thank you!',
|
|
81
81
|
reason: {
|
|
82
|
-
noDidWallet:
|
|
83
|
-
|
|
84
|
-
noDelegation: 'No delegation found from your DID Wallet, please renew it after completing the authorization',
|
|
82
|
+
noDidWallet: 'You have not bound DID Wallet, please bind DID Wallet to ensure sufficient balance',
|
|
83
|
+
noDelegation: 'Your DID Wallet has not been authorized, please update authorization',
|
|
85
84
|
noTransferPermission:
|
|
86
|
-
'
|
|
85
|
+
'Your DID Wallet has not granted transfer permission to the application, please update authorization',
|
|
87
86
|
noTokenPermission:
|
|
88
|
-
'
|
|
89
|
-
noTransferTo:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
noSupported: 'Token renewal is not supported, please check your subscription',
|
|
96
|
-
txSendFailed: 'Failed to send transaction when try to collect payment.',
|
|
87
|
+
'Your DID Wallet has not granted token transfer permission to the application, please update authorization',
|
|
88
|
+
noTransferTo: 'Your DID Wallet has not granted the application charge permission, please update authorization',
|
|
89
|
+
noEnoughAllowance: 'The deduction amount exceeds the single transfer limit, please update authorization',
|
|
90
|
+
noToken: 'Your account has no tokens, please recharge tokens',
|
|
91
|
+
noEnoughToken: 'Your account token balance is {balance}, insufficient for {price}, please recharge tokens',
|
|
92
|
+
noSupported: 'It is not supported to charge with tokens, please check your package',
|
|
93
|
+
txSendFailed: 'Failed to send charge transaction',
|
|
97
94
|
},
|
|
98
95
|
},
|
|
99
96
|
|
|
@@ -103,9 +100,9 @@ export default flat({
|
|
|
103
100
|
},
|
|
104
101
|
|
|
105
102
|
subscriptWillCanceled: {
|
|
106
|
-
title: '{productName} subscription is about to be automatically
|
|
107
|
-
body: '
|
|
108
|
-
renewAmount: '
|
|
103
|
+
title: '{productName} subscription is about to be automatically cancelled ',
|
|
104
|
+
body: 'Your subscription {productName} will be automatically unsubscribed by the system after {at} (after {willCancelDuration}) due to a long period of failure to automatically complete the deduction. Please handle the problem of charge deduction manually in time, so as not to affect the use. If you have any questions, please feel free to contact us. ',
|
|
105
|
+
renewAmount: 'deduction amount ',
|
|
109
106
|
},
|
|
110
107
|
|
|
111
108
|
subscriptionCanceled: {
|
package/api/src/locales/zh.ts
CHANGED
|
@@ -24,7 +24,8 @@ export default flat({
|
|
|
24
24
|
minute: '分钟',
|
|
25
25
|
minutes: '分钟',
|
|
26
26
|
cancellationReason: '取消原因',
|
|
27
|
-
renewNow: '
|
|
27
|
+
renewNow: '立即付款',
|
|
28
|
+
immediateRecharge: '立即充值',
|
|
28
29
|
},
|
|
29
30
|
|
|
30
31
|
sendTo: '发送给',
|
|
@@ -40,9 +41,9 @@ export default flat({
|
|
|
40
41
|
|
|
41
42
|
subscriptionTrialWillEnd: {
|
|
42
43
|
title: '{productName} 试用期即将结束',
|
|
43
|
-
body: '您订阅的 {productName} 试用资格将在 {willRenewDuration}
|
|
44
|
+
body: '您订阅的 {productName} 试用资格将在 {willRenewDuration} 后结束。请确保您的账户余额充足,以便在试用期结束后自动完成扣费。感谢您的支持与信任!',
|
|
44
45
|
unableToPayBody:
|
|
45
|
-
'您订阅的 {productName} 试用资格将在 {willRenewDuration} 后结束。<span style="color: red;">您的当前余额为 {balance},不足 {price}</span
|
|
46
|
+
'您订阅的 {productName} 试用资格将在 {willRenewDuration} 后结束。<span style="color: red;">您的当前余额为 {balance},不足 {price}</span>,请确保余额充足以便自动完成扣费。感谢您的支持与信任!',
|
|
46
47
|
},
|
|
47
48
|
|
|
48
49
|
subscriptionSucceed: {
|
|
@@ -61,34 +62,33 @@ export default flat({
|
|
|
61
62
|
},
|
|
62
63
|
|
|
63
64
|
subscriptionWillRenew: {
|
|
64
|
-
title: '{productName}
|
|
65
|
-
body: '您订阅的 {productName}
|
|
65
|
+
title: '{productName} 即将自动完成扣费',
|
|
66
|
+
body: '您订阅的 {productName} 距离下一次扣费还有 {willRenewDuration}。您的当前余额为 {balance},请确保您的账户余额充足,以便在到期后({at})自动完成扣费。祝您一切顺利!',
|
|
66
67
|
unableToPayBody:
|
|
67
|
-
'您订阅的 {productName}
|
|
68
|
-
renewAmount: '
|
|
68
|
+
'您订阅的 {productName} 距离下一次扣费还有 {willRenewDuration}。<span style="color: red;">您的当前余额为 {balance},不足 {price}</span>,请确保您的账户余额充足,以便在到期后({at})自动完成扣费。祝您一切顺利!',
|
|
69
|
+
renewAmount: '扣费金额',
|
|
69
70
|
},
|
|
70
71
|
|
|
71
72
|
subscriptionRenewed: {
|
|
72
|
-
title: '{productName}
|
|
73
|
-
body: '您的 {productName} 已于 {at}
|
|
73
|
+
title: '{productName} 扣费成功',
|
|
74
|
+
body: '您的 {productName} 已于 {at} 扣费成功。感谢您的持续支持与信任,祝您使用愉快!',
|
|
74
75
|
noExpenseIncurred: '服务期间未产生费用',
|
|
75
76
|
},
|
|
76
77
|
|
|
77
78
|
subscriptionRenewFailed: {
|
|
78
|
-
title: '{productName}
|
|
79
|
-
body: '很抱歉地通知您,您的 {productName}
|
|
80
|
-
renewNow: '立即续费',
|
|
79
|
+
title: '{productName} 扣费失败',
|
|
80
|
+
body: '很抱歉地通知您,您的 {productName} 于 {at} 扣费失败。失败原因为 {reason}。如有任何疑问,请及时联系我们。谢谢!',
|
|
81
81
|
reason: {
|
|
82
|
-
noDidWallet: '您尚未绑定 DID Wallet,请绑定 DID Wallet
|
|
83
|
-
noDelegation: '您的 DID Wallet
|
|
84
|
-
noTransferPermission: '您的 DID Wallet
|
|
85
|
-
noTokenPermission: '您的 DID Wallet
|
|
86
|
-
noTransferTo: '您的 DID Wallet
|
|
87
|
-
noEnoughAllowance: '
|
|
88
|
-
noToken: '
|
|
89
|
-
noEnoughToken: '您的账户代币余额为 {balance},不足 {price}
|
|
90
|
-
noSupported: '
|
|
91
|
-
txSendFailed: '
|
|
82
|
+
noDidWallet: '您尚未绑定 DID Wallet,请绑定 DID Wallet,确保余额充足',
|
|
83
|
+
noDelegation: '您的 DID Wallet 尚未授权,请更新授权',
|
|
84
|
+
noTransferPermission: '您的 DID Wallet 未授予应用转账权限,请更新授权',
|
|
85
|
+
noTokenPermission: '您的 DID Wallet 未授予应用对应通证的转账权限,请更新授权',
|
|
86
|
+
noTransferTo: '您的 DID Wallet 未授予应用扣费权限,请更新授权',
|
|
87
|
+
noEnoughAllowance: '扣款金额超出单笔转账限额,请更新授权',
|
|
88
|
+
noToken: '您的账户没有任何代币,请充值代币',
|
|
89
|
+
noEnoughToken: '您的账户代币余额为 {balance},不足 {price},请充值代币',
|
|
90
|
+
noSupported: '不支持使用代币扣费,请检查您的套餐',
|
|
91
|
+
txSendFailed: '扣费交易发送失败',
|
|
92
92
|
},
|
|
93
93
|
},
|
|
94
94
|
|
|
@@ -99,8 +99,8 @@ export default flat({
|
|
|
99
99
|
|
|
100
100
|
subscriptWillCanceled: {
|
|
101
101
|
title: '{productName} 订阅即将自动取消',
|
|
102
|
-
body: '
|
|
103
|
-
renewAmount: '
|
|
102
|
+
body: '由于长时间未能自动完成扣费,您订阅的 {productName} 将于 {at} ({willCancelDuration}后) 被系统自动取消订阅。请您及时手动处理扣费问题,以免影响使用。如有任何疑问,请随时与我们联系。',
|
|
103
|
+
renewAmount: '扣费金额',
|
|
104
104
|
},
|
|
105
105
|
|
|
106
106
|
subscriptionCanceled: {
|
|
@@ -105,6 +105,7 @@ async function handleNotificationJob(job: NotificationQueueJob): Promise<void> {
|
|
|
105
105
|
try {
|
|
106
106
|
const template = getNotificationTemplate(job);
|
|
107
107
|
await new Notification(template).send();
|
|
108
|
+
logger.info('handleNotificationJob.success', { job });
|
|
108
109
|
} catch (error) {
|
|
109
110
|
logger.error('handleNotificationJob.error.$job', job);
|
|
110
111
|
logger.error('handleNotificationJob.error', error);
|
|
@@ -133,6 +134,7 @@ export async function startNotificationQueue() {
|
|
|
133
134
|
// 试用期开始
|
|
134
135
|
events.on('customer.subscription.trial_start', (subscription: Subscription) => {
|
|
135
136
|
notificationQueue.push({
|
|
137
|
+
id: `customer.subscription.trial_start.${subscription.id}`,
|
|
136
138
|
job: {
|
|
137
139
|
type: 'customer.subscription.trial_start',
|
|
138
140
|
options: {
|
|
@@ -146,6 +148,7 @@ export async function startNotificationQueue() {
|
|
|
146
148
|
if (!subscription.trial_start) {
|
|
147
149
|
// 没有试用期的 subscription 通知
|
|
148
150
|
notificationQueue.push({
|
|
151
|
+
id: `customer.subscription.started.${subscription.id}`,
|
|
149
152
|
job: {
|
|
150
153
|
type: 'customer.subscription.started',
|
|
151
154
|
options: {
|
|
@@ -160,6 +163,7 @@ export async function startNotificationQueue() {
|
|
|
160
163
|
events.on('checkout.session.completed', (checkoutSession: CheckoutSession) => {
|
|
161
164
|
if (checkoutSession.mode === 'payment') {
|
|
162
165
|
notificationQueue.push({
|
|
166
|
+
id: `checkout.session.completed.${checkoutSession.id}`,
|
|
163
167
|
job: {
|
|
164
168
|
type: 'checkout.session.completed',
|
|
165
169
|
options: {
|
|
@@ -172,6 +176,7 @@ export async function startNotificationQueue() {
|
|
|
172
176
|
|
|
173
177
|
events.on('customer.subscription.renewed', (subscription: Subscription) => {
|
|
174
178
|
notificationQueue.push({
|
|
179
|
+
id: `customer.subscription.renewed.${subscription.id}.${subscription.latest_invoice_id}`,
|
|
175
180
|
job: {
|
|
176
181
|
type: 'customer.subscription.renewed',
|
|
177
182
|
options: {
|
|
@@ -186,6 +191,7 @@ export async function startNotificationQueue() {
|
|
|
186
191
|
const invoice = await Invoice.findByPk(subscription.latest_invoice_id);
|
|
187
192
|
if (invoice && subscription.metadata.renew_failed_reason) {
|
|
188
193
|
notificationQueue.push({
|
|
194
|
+
id: `customer.subscription.renew_failed.${subscription.id}.${invoice.id}`,
|
|
189
195
|
job: {
|
|
190
196
|
type: 'customer.subscription.renew_failed',
|
|
191
197
|
options: {
|
|
@@ -199,6 +205,7 @@ export async function startNotificationQueue() {
|
|
|
199
205
|
|
|
200
206
|
events.on('customer.subscription.upgraded', (subscription: Subscription) => {
|
|
201
207
|
notificationQueue.push({
|
|
208
|
+
id: `customer.subscription.upgraded.${subscription.id}.${subscription.latest_invoice_id}`,
|
|
202
209
|
job: {
|
|
203
210
|
type: 'customer.subscription.upgraded',
|
|
204
211
|
options: {
|
|
@@ -210,6 +217,7 @@ export async function startNotificationQueue() {
|
|
|
210
217
|
|
|
211
218
|
events.on('refund.succeeded', (refund: Refund) => {
|
|
212
219
|
notificationQueue.push({
|
|
220
|
+
id: `refund.succeeded.${refund.subscription_id}.${refund.id}`,
|
|
213
221
|
job: {
|
|
214
222
|
type: 'refund.succeeded',
|
|
215
223
|
options: {
|
|
@@ -221,6 +229,7 @@ export async function startNotificationQueue() {
|
|
|
221
229
|
|
|
222
230
|
events.on('customer.subscription.deleted', (subscription: Subscription) => {
|
|
223
231
|
notificationQueue.push({
|
|
232
|
+
id: `customer.subscription.deleted.${subscription.id}`,
|
|
224
233
|
job: {
|
|
225
234
|
type: 'customer.subscription.deleted',
|
|
226
235
|
options: {
|
|
@@ -554,9 +554,9 @@ export const handlePayment = async (job: PaymentJob) => {
|
|
|
554
554
|
await paymentIntent.update(updates.payment);
|
|
555
555
|
await invoice.update(updates.invoice);
|
|
556
556
|
|
|
557
|
-
//
|
|
557
|
+
// 只有在 第一次重试 或者 重试次数超过阈值 的时候才发送邮件,不然邮件频率太高了
|
|
558
558
|
const minRetryMail = updates.minRetryMail || MIN_RETRY_MAIL;
|
|
559
|
-
if (attemptCount >= minRetryMail && invoice.billing_reason === 'subscription_cycle') {
|
|
559
|
+
if ((attemptCount === 1 || attemptCount >= minRetryMail) && invoice.billing_reason === 'subscription_cycle') {
|
|
560
560
|
const subscription = await Subscription.findByPk(invoice.subscription_id);
|
|
561
561
|
if (subscription) {
|
|
562
562
|
await subscription.update({
|
|
@@ -176,10 +176,12 @@ export class Subscription extends Model<InferAttributes<Subscription>, InferCrea
|
|
|
176
176
|
type: DataTypes.ENUM('active', 'canceled', 'incomplete', 'incomplete_expired', 'past_due', 'trialing', 'paused'),
|
|
177
177
|
allowNull: false,
|
|
178
178
|
},
|
|
179
|
+
// 将来在什么时候这个订阅会被取消
|
|
179
180
|
cancel_at: {
|
|
180
181
|
type: DataTypes.INTEGER,
|
|
181
182
|
allowNull: true,
|
|
182
183
|
},
|
|
184
|
+
// 什么时候开始说要去取消订阅的
|
|
183
185
|
canceled_at: {
|
|
184
186
|
type: DataTypes.INTEGER,
|
|
185
187
|
allowNull: true,
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.240",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "cross-env COMPONENT_STORE_URL=https://test.store.blocklet.dev blocklet dev --open",
|
|
6
6
|
"eject": "vite eject",
|
|
@@ -43,30 +43,30 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@abtnode/cron": "1.16.26",
|
|
46
|
-
"@arcblock/did": "^1.18.
|
|
46
|
+
"@arcblock/did": "^1.18.116",
|
|
47
47
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
48
|
-
"@arcblock/did-connect": "^2.9.
|
|
49
|
-
"@arcblock/did-util": "^1.18.
|
|
50
|
-
"@arcblock/jwt": "^1.18.
|
|
51
|
-
"@arcblock/ux": "^2.9.
|
|
52
|
-
"@arcblock/validator": "^1.18.
|
|
48
|
+
"@arcblock/did-connect": "^2.9.77",
|
|
49
|
+
"@arcblock/did-util": "^1.18.116",
|
|
50
|
+
"@arcblock/jwt": "^1.18.116",
|
|
51
|
+
"@arcblock/ux": "^2.9.77",
|
|
52
|
+
"@arcblock/validator": "^1.18.116",
|
|
53
53
|
"@blocklet/logger": "1.16.26",
|
|
54
|
-
"@blocklet/payment-react": "1.13.
|
|
54
|
+
"@blocklet/payment-react": "1.13.240",
|
|
55
55
|
"@blocklet/sdk": "1.16.26",
|
|
56
|
-
"@blocklet/ui-react": "^2.9.
|
|
56
|
+
"@blocklet/ui-react": "^2.9.77",
|
|
57
57
|
"@blocklet/uploader": "^0.0.78",
|
|
58
|
-
"@mui/icons-material": "^5.15.
|
|
58
|
+
"@mui/icons-material": "^5.15.16",
|
|
59
59
|
"@mui/lab": "^5.0.0-alpha.170",
|
|
60
|
-
"@mui/material": "^5.15.
|
|
61
|
-
"@mui/styles": "^5.15.
|
|
60
|
+
"@mui/material": "^5.15.16",
|
|
61
|
+
"@mui/styles": "^5.15.16",
|
|
62
62
|
"@mui/system": "^5.15.15",
|
|
63
|
-
"@ocap/asset": "^1.18.
|
|
64
|
-
"@ocap/client": "^1.18.
|
|
65
|
-
"@ocap/mcrypto": "^1.18.
|
|
66
|
-
"@ocap/util": "^1.18.
|
|
67
|
-
"@ocap/wallet": "^1.18.
|
|
68
|
-
"@react-pdf/renderer": "^3.4.
|
|
69
|
-
"@stripe/react-stripe-js": "^2.7.
|
|
63
|
+
"@ocap/asset": "^1.18.116",
|
|
64
|
+
"@ocap/client": "^1.18.116",
|
|
65
|
+
"@ocap/mcrypto": "^1.18.116",
|
|
66
|
+
"@ocap/util": "^1.18.116",
|
|
67
|
+
"@ocap/wallet": "^1.18.116",
|
|
68
|
+
"@react-pdf/renderer": "^3.4.4",
|
|
69
|
+
"@stripe/react-stripe-js": "^2.7.1",
|
|
70
70
|
"@stripe/stripe-js": "^2.4.0",
|
|
71
71
|
"ahooks": "^3.7.11",
|
|
72
72
|
"axios": "^0.27.2",
|
|
@@ -76,9 +76,9 @@
|
|
|
76
76
|
"copy-to-clipboard": "^3.3.3",
|
|
77
77
|
"cors": "^2.8.5",
|
|
78
78
|
"date-fns": "^3.6.0",
|
|
79
|
-
"dayjs": "^1.11.
|
|
79
|
+
"dayjs": "^1.11.11",
|
|
80
80
|
"dotenv-flow": "^3.3.0",
|
|
81
|
-
"ethers": "^6.12.
|
|
81
|
+
"ethers": "^6.12.1",
|
|
82
82
|
"express": "^4.19.2",
|
|
83
83
|
"express-async-errors": "^3.1.1",
|
|
84
84
|
"express-history-api-fallback": "^2.2.1",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"flat": "^5.0.2",
|
|
87
87
|
"google-libphonenumber": "^3.2.34",
|
|
88
88
|
"iframe-resizer-react": "^1.1.0",
|
|
89
|
-
"joi": "^17.
|
|
89
|
+
"joi": "^17.13.1",
|
|
90
90
|
"json-stable-stringify": "^1.1.1",
|
|
91
91
|
"lodash": "^4.17.21",
|
|
92
92
|
"morgan": "^1.10.0",
|
|
@@ -95,13 +95,13 @@
|
|
|
95
95
|
"p-all": "3.0.0",
|
|
96
96
|
"p-wait-for": "3",
|
|
97
97
|
"pretty-ms-i18n": "^1.0.3",
|
|
98
|
-
"react": "^18.
|
|
99
|
-
"react-dom": "^18.
|
|
98
|
+
"react": "^18.3.1",
|
|
99
|
+
"react-dom": "^18.3.1",
|
|
100
100
|
"react-error-boundary": "^4.0.13",
|
|
101
|
-
"react-hook-form": "^7.51.
|
|
101
|
+
"react-hook-form": "^7.51.4",
|
|
102
102
|
"react-international-phone": "^3.1.2",
|
|
103
|
-
"react-router-dom": "^6.
|
|
104
|
-
"recharts": "^2.12.
|
|
103
|
+
"react-router-dom": "^6.23.0",
|
|
104
|
+
"recharts": "^2.12.7",
|
|
105
105
|
"rimraf": "^3.0.2",
|
|
106
106
|
"sequelize": "^6.37.3",
|
|
107
107
|
"sql-where-parser": "^2.2.1",
|
|
@@ -116,14 +116,14 @@
|
|
|
116
116
|
"devDependencies": {
|
|
117
117
|
"@abtnode/types": "1.16.26",
|
|
118
118
|
"@arcblock/eslint-config-ts": "^0.3.0",
|
|
119
|
-
"@blocklet/payment-types": "1.13.
|
|
119
|
+
"@blocklet/payment-types": "1.13.240",
|
|
120
120
|
"@types/cookie-parser": "^1.4.7",
|
|
121
121
|
"@types/cors": "^2.8.17",
|
|
122
122
|
"@types/dotenv-flow": "^3.3.3",
|
|
123
123
|
"@types/express": "^4.17.21",
|
|
124
|
-
"@types/node": "^18.19.
|
|
125
|
-
"@types/react": "^18.
|
|
126
|
-
"@types/react-dom": "^18.
|
|
124
|
+
"@types/node": "^18.19.32",
|
|
125
|
+
"@types/react": "^18.3.1",
|
|
126
|
+
"@types/react-dom": "^18.3.0",
|
|
127
127
|
"@vitejs/plugin-react": "^4.2.1",
|
|
128
128
|
"bumpp": "^8.2.1",
|
|
129
129
|
"cross-env": "^7.0.3",
|
|
@@ -137,9 +137,9 @@
|
|
|
137
137
|
"prettier-plugin-import-sort": "^0.0.7",
|
|
138
138
|
"ts-jest": "^29.1.2",
|
|
139
139
|
"ts-node": "^10.9.2",
|
|
140
|
-
"type-fest": "^4.
|
|
140
|
+
"type-fest": "^4.18.2",
|
|
141
141
|
"typescript": "^4.9.5",
|
|
142
|
-
"vite": "^5.2.
|
|
142
|
+
"vite": "^5.2.11",
|
|
143
143
|
"vite-plugin-blocklet": "^0.7.9",
|
|
144
144
|
"vite-plugin-node-polyfills": "^0.21.0",
|
|
145
145
|
"vite-plugin-svgr": "^4.2.0",
|
|
@@ -155,5 +155,5 @@
|
|
|
155
155
|
"parser": "typescript"
|
|
156
156
|
}
|
|
157
157
|
},
|
|
158
|
-
"gitHead": "
|
|
158
|
+
"gitHead": "02b0f1dc4e711c889dd88f7ace2b56062c5d2f0e"
|
|
159
159
|
}
|