payment-kit 1.13.239 → 1.13.241
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/index.ts +3 -0
- 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/libs/ws.ts +25 -0
- 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/queues/usage-record.ts +11 -2
- package/api/src/routes/usage-records.ts +8 -1
- package/api/src/store/models/subscription.ts +2 -0
- package/blocklet.yml +1 -1
- package/package.json +35 -35
- package/src/components/invoice/table.tsx +103 -62
- package/src/components/subscription/items/usage-records.tsx +64 -13
- package/src/components/subscription/portal/list.tsx +4 -3
- package/src/pages/customer/index.tsx +24 -3
- package/src/pages/customer/invoice/detail.tsx +28 -31
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/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { ensureWebhookRegistered } from './integrations/stripe/setup';
|
|
|
16
16
|
import { handlers } from './libs/auth';
|
|
17
17
|
import logger, { accessLogStream } from './libs/logger';
|
|
18
18
|
import { ensureI18n } from './libs/middleware';
|
|
19
|
+
import { initEventBroadcast } from './libs/ws';
|
|
19
20
|
import { startCheckoutSessionQueue } from './queues/checkout-session';
|
|
20
21
|
import { startEventQueue } from './queues/event';
|
|
21
22
|
import { startInvoiceQueue } from './queues/invoice';
|
|
@@ -123,5 +124,7 @@ export const server = app.listen(port, (err?: any) => {
|
|
|
123
124
|
|
|
124
125
|
crons.init();
|
|
125
126
|
|
|
127
|
+
initEventBroadcast();
|
|
128
|
+
|
|
126
129
|
initResourceHandler();
|
|
127
130
|
});
|
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
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { sendToRelay } from '@blocklet/sdk/service/notification';
|
|
2
|
+
|
|
3
|
+
import type { CheckoutSession, Invoice, PaymentIntent } from '../store/models';
|
|
4
|
+
import { events } from './event';
|
|
5
|
+
|
|
6
|
+
export function broadcast(channel: string, eventName: string, data: any) {
|
|
7
|
+
sendToRelay(channel, eventName, data).catch((err: any) => {
|
|
8
|
+
console.error(`Failed to broadcast info: ${channel}.${eventName}`, err);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function initEventBroadcast() {
|
|
13
|
+
events.on('payment_intent.succeeded', (data: PaymentIntent) => {
|
|
14
|
+
broadcast('events', 'payment_intent.succeeded', data);
|
|
15
|
+
});
|
|
16
|
+
events.on('checkout.session.completed', (data: CheckoutSession) => {
|
|
17
|
+
broadcast('events', 'checkout.session.completed', data);
|
|
18
|
+
});
|
|
19
|
+
events.on('checkout.session.nft_minted', (data: CheckoutSession) => {
|
|
20
|
+
broadcast('events', 'checkout.session.nft_minted', data);
|
|
21
|
+
});
|
|
22
|
+
events.on('invoice.paid', (data: Invoice) => {
|
|
23
|
+
broadcast('events', 'invoice.paid', data);
|
|
24
|
+
});
|
|
25
|
+
}
|