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.
Files changed (35) hide show
  1. package/api/src/crons/base.ts +8 -5
  2. package/api/src/crons/subscription-will-canceled.ts +5 -1
  3. package/api/src/crons/subscription-will-renew.ts +0 -1
  4. package/api/src/index.ts +3 -0
  5. package/api/src/libs/env.ts +1 -0
  6. package/api/src/libs/invoice.ts +6 -5
  7. package/api/src/libs/notification/template/one-time-payment-succeeded.ts +10 -4
  8. package/api/src/libs/notification/template/subscription-canceled.ts +10 -6
  9. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +9 -3
  10. package/api/src/libs/notification/template/subscription-renew-failed.ts +13 -6
  11. package/api/src/libs/notification/template/subscription-renewed.ts +11 -4
  12. package/api/src/libs/notification/template/subscription-succeeded.ts +10 -4
  13. package/api/src/libs/notification/template/subscription-trial-start.ts +2 -2
  14. package/api/src/libs/notification/template/subscription-trial-will-end.ts +1 -1
  15. package/api/src/libs/notification/template/subscription-upgraded.ts +10 -4
  16. package/api/src/libs/notification/template/subscription-will-canceled.ts +2 -2
  17. package/api/src/libs/notification/template/subscription-will-renew.ts +23 -2
  18. package/api/src/libs/payment.ts +7 -6
  19. package/api/src/libs/subscription.ts +5 -5
  20. package/api/src/libs/util.ts +13 -6
  21. package/api/src/libs/ws.ts +25 -0
  22. package/api/src/locales/en.ts +26 -29
  23. package/api/src/locales/zh.ts +24 -24
  24. package/api/src/queues/notification.ts +9 -0
  25. package/api/src/queues/payment.ts +2 -2
  26. package/api/src/queues/usage-record.ts +11 -2
  27. package/api/src/routes/usage-records.ts +8 -1
  28. package/api/src/store/models/subscription.ts +2 -0
  29. package/blocklet.yml +1 -1
  30. package/package.json +35 -35
  31. package/src/components/invoice/table.tsx +103 -62
  32. package/src/components/subscription/items/usage-records.tsx +64 -13
  33. package/src/components/subscription/portal/list.tsx +4 -3
  34. package/src/pages/customer/index.tsx +24 -3
  35. package/src/pages/customer/invoice/detail.tsx +28 -31
@@ -1,5 +1,5 @@
1
1
  import dayjs from 'dayjs';
2
- import { clone } from 'lodash';
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 label: string = new Date().toISOString();
30
+ const { name } = this.constructor;
31
31
 
32
- logger.info(`${label}: ${this.constructor.name}.run start`);
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(`${label}: ${this.constructor.name}.run end`);
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: !!diff.required,
44
+ required: true,
41
45
  },
42
46
  },
43
47
  delay: dayjs(subscription.current_period_end * 1000)
@@ -19,7 +19,6 @@ export class SubscriptionWillRenewSchedule extends BaseSubscriptionScheduleNotif
19
19
  current_period_end: {
20
20
  [Op.lt]: this.end / 1000,
21
21
  },
22
- trial_start: 0,
23
22
  status: 'active',
24
23
  },
25
24
  attributes: ['id', 'current_period_start', 'current_period_end'],
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
  });
@@ -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,
@@ -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}?locale=${locale}&action=${action}&${querystring.stringify(
21
- getConnectQueryParam({ userDid })
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 = txHash && getExplorerLink(chainHost, txHash as string, 'tx');
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: 'viewSubscription',
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 !== 'payment_disputed') {
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
- subscription.cancelation_details
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 = txHash && getExplorerLink(chainHost, txHash as string, 'tx');
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 = hasNft && txHash ? getExplorerLink(chainHost, txHash, 'tx') : 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: `${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: 'viewInvoice',
278
- title: translate('notification.subscriptionRenewFailed.renewNow', locale),
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 = hasNft && txHash ? getExplorerLink(chainHost, txHash, 'tx') : 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: 'viewSubscription',
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 = txHash && getExplorerLink(chainHost, txHash as string, 'tx');
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: 'viewSubscription',
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: 'viewSubscription',
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 = txHash && getExplorerLink(chainHost, txHash, 'tx');
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: 'viewSubscription',
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: 'viewInvoice',
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
  },
@@ -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
- const paymentCurrency = await PaymentCurrency.findByPk(paymentIntent.currency_id);
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}?locale=${locale}&${querystring.stringify(
33
- getConnectQueryParam({ userDid })
34
- )}`
31
+ withQuery(`customer/subscription/${subscriptionId}`, {
32
+ locale,
33
+ ...getConnectQueryParam({ userDid }),
34
+ })
35
35
  );
36
36
  }
37
37
 
@@ -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
- chainHost: string | undefined,
177
- did: string | undefined,
178
- type: LiteralUnion<'asset' | 'account' | 'tx' | 'token' | 'factory' | ' bridge', string>
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
+ }