payment-kit 1.18.25 → 1.18.26

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 (31) hide show
  1. package/api/src/libs/notification/template/aggregated-subscription-renewed.ts +165 -0
  2. package/api/src/libs/notification/template/one-time-payment-succeeded.ts +2 -5
  3. package/api/src/libs/notification/template/subscription-canceled.ts +2 -3
  4. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +7 -4
  5. package/api/src/libs/notification/template/subscription-renew-failed.ts +3 -5
  6. package/api/src/libs/notification/template/subscription-renewed.ts +2 -2
  7. package/api/src/libs/notification/template/subscription-stake-slash-succeeded.ts +2 -3
  8. package/api/src/libs/notification/template/subscription-succeeded.ts +2 -2
  9. package/api/src/libs/notification/template/subscription-upgraded.ts +5 -5
  10. package/api/src/libs/notification/template/subscription-will-renew.ts +2 -2
  11. package/api/src/libs/queue/index.ts +6 -0
  12. package/api/src/libs/queue/store.ts +13 -1
  13. package/api/src/libs/util.ts +22 -1
  14. package/api/src/locales/en.ts +5 -0
  15. package/api/src/locales/zh.ts +5 -0
  16. package/api/src/queues/notification.ts +353 -11
  17. package/api/src/routes/customers.ts +61 -0
  18. package/api/src/routes/subscriptions.ts +1 -1
  19. package/api/src/store/migrations/20250328-notification-preference.ts +29 -0
  20. package/api/src/store/models/customer.ts +15 -1
  21. package/api/src/store/models/types.ts +17 -1
  22. package/blocklet.yml +1 -1
  23. package/package.json +19 -19
  24. package/src/components/customer/form.tsx +21 -2
  25. package/src/components/customer/notification-preference.tsx +428 -0
  26. package/src/components/layout/user.tsx +1 -1
  27. package/src/locales/en.tsx +30 -0
  28. package/src/locales/zh.tsx +30 -0
  29. package/src/pages/customer/index.tsx +26 -22
  30. package/src/pages/customer/recharge/account.tsx +7 -7
  31. package/src/pages/customer/subscription/embed.tsx +1 -0
@@ -0,0 +1,165 @@
1
+ import { BN } from '@ocap/util';
2
+ import { translate } from '../../../locales';
3
+ import { BaseEmailTemplate, BaseEmailTemplateType } from './base';
4
+ import { Subscription, Invoice, Customer, PaymentCurrency, PaymentMethod } from '../../../store/models';
5
+ import { getCustomerSubscriptionPageUrl } from '../../subscription';
6
+ import { getUserLocale } from '../../../integrations/blocklet/notification';
7
+ import { formatTime } from '../../time';
8
+ import { formatCurrencyInfo } from '../../util';
9
+
10
+ export interface AggregatedSubscriptionRenewedEmailTemplateOptions {
11
+ customer_id: string;
12
+ items: Array<{
13
+ event_id: string;
14
+ occurred_at: number;
15
+ data: {
16
+ subscriptionId: string;
17
+ invoiceId: string;
18
+ };
19
+ }>;
20
+ time_range: {
21
+ start: number;
22
+ end: number;
23
+ };
24
+ }
25
+
26
+ interface SubscriptionWithAmount {
27
+ subscription: Subscription;
28
+ amounts: Record<string, string>;
29
+ }
30
+
31
+ interface Context {
32
+ locale: string;
33
+ startTime: string;
34
+ endTime: string;
35
+ totalAmountStr: string;
36
+ subscriptionData: SubscriptionWithAmount[];
37
+ userDid: string;
38
+ }
39
+
40
+ export class AggregatedSubscriptionRenewedEmailTemplate implements BaseEmailTemplate {
41
+ options: AggregatedSubscriptionRenewedEmailTemplateOptions;
42
+
43
+ constructor(options: AggregatedSubscriptionRenewedEmailTemplateOptions) {
44
+ this.options = options;
45
+ }
46
+
47
+ async getContext(): Promise<Context> {
48
+ const { items, customer_id: customerId, time_range: timeRange } = this.options;
49
+
50
+ const customer = await Customer.findByPk(customerId);
51
+ if (!customer) {
52
+ throw new Error(`Customer not found: ${customerId}`);
53
+ }
54
+ const locale = await getUserLocale(customer.did);
55
+
56
+ const subscriptions = await Subscription.findAll({
57
+ where: { id: [...new Set(items.map((item) => item.data.subscriptionId))] },
58
+ });
59
+
60
+ const invoices = await Invoice.findAll({
61
+ where: { id: [...new Set(items.map((item) => item.data.invoiceId))] },
62
+ });
63
+
64
+ const currencyIds = [...new Set(invoices.map((invoice) => invoice.currency_id))];
65
+ const paymentCurrencies = (await PaymentCurrency.findAll({
66
+ where: { id: currencyIds },
67
+ include: [{ model: PaymentMethod, as: 'payment_method' }],
68
+ })) as (PaymentCurrency & { payment_method: PaymentMethod })[];
69
+ const currencyMap = paymentCurrencies.reduce(
70
+ (acc, curr) => {
71
+ acc[curr.id] = curr;
72
+ return acc;
73
+ },
74
+ {} as Record<string, PaymentCurrency & { payment_method: PaymentMethod }>
75
+ );
76
+
77
+ const { totalAmounts, subscriptionAmounts } = invoices.reduce(
78
+ (acc, invoice) => {
79
+ const currency = invoice.currency_id;
80
+ const amount = new BN(invoice.amount_paid || '0');
81
+
82
+ acc.totalAmounts[currency] = (acc.totalAmounts[currency] || new BN('0')).add(amount);
83
+
84
+ if (invoice.subscription_id) {
85
+ if (!acc.subscriptionAmounts[invoice.subscription_id]) {
86
+ acc.subscriptionAmounts[invoice.subscription_id] = {};
87
+ }
88
+ const subAmounts = acc.subscriptionAmounts[invoice.subscription_id] || {};
89
+ subAmounts[currency] = (subAmounts[currency] || new BN('0')).add(amount);
90
+ acc.subscriptionAmounts[invoice.subscription_id] = subAmounts;
91
+ }
92
+ return acc;
93
+ },
94
+ {
95
+ totalAmounts: {} as Record<string, BN>,
96
+ subscriptionAmounts: {} as Record<string, Record<string, BN>>,
97
+ }
98
+ );
99
+
100
+ const totalAmountStr = Object.entries(totalAmounts)
101
+ .map(([currencyId, amount]) => {
102
+ const currency = currencyMap[currencyId];
103
+ if (!currency) {
104
+ return undefined;
105
+ }
106
+ return formatCurrencyInfo(amount.toString(), currency, currency.payment_method);
107
+ })
108
+ .filter((amountStr) => amountStr !== undefined)
109
+ .join('、');
110
+
111
+ const subscriptionData = subscriptions.map((subscription) => ({
112
+ subscription,
113
+ amounts: Object.entries(subscriptionAmounts[subscription.id] || {}).reduce(
114
+ (acc, [currencyId, amount]) => {
115
+ const currency = currencyMap[currencyId];
116
+ if (!currency) {
117
+ return acc;
118
+ }
119
+ acc[currencyId] = formatCurrencyInfo(amount.toString(), currency, currency.payment_method);
120
+ return acc;
121
+ },
122
+ {} as Record<string, string>
123
+ ),
124
+ }));
125
+
126
+ return {
127
+ locale,
128
+ startTime: formatTime(timeRange.start * 1000),
129
+ endTime: formatTime(timeRange.end * 1000),
130
+ totalAmountStr,
131
+ subscriptionData,
132
+ userDid: customer.did,
133
+ };
134
+ }
135
+
136
+ async getTemplate(): Promise<BaseEmailTemplateType> {
137
+ const { locale, startTime, endTime, totalAmountStr, subscriptionData, userDid } = await this.getContext();
138
+
139
+ const subscriptionList = subscriptionData
140
+ .map(({ subscription, amounts }) => {
141
+ const amountStr = Object.values(amounts).join('、');
142
+ const description = subscription.description || subscription.id;
143
+ const link = getCustomerSubscriptionPageUrl({
144
+ subscriptionId: subscription.id,
145
+ locale,
146
+ userDid,
147
+ });
148
+ return `<${description} - ${amountStr}(link:${link})>`;
149
+ })
150
+ .join('\n');
151
+
152
+ return {
153
+ title: translate('notification.aggregatedSubscriptionRenewed.title', locale, {
154
+ count: subscriptionData.length,
155
+ }),
156
+ body: translate('notification.aggregatedSubscriptionRenewed.body', locale, {
157
+ startTime,
158
+ endTime,
159
+ count: subscriptionData.length,
160
+ totalAmount: totalAmountStr,
161
+ subscriptionList,
162
+ }),
163
+ };
164
+ }
165
+ }
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/brace-style */
2
2
  /* eslint-disable @typescript-eslint/indent */
3
- import { fromUnitToToken } from '@ocap/util';
4
3
  import pWaitFor from 'p-wait-for';
5
4
 
6
5
  import { getUserLocale } from '../../../integrations/blocklet/notification';
@@ -9,7 +8,7 @@ import { CheckoutSession, Customer, NftMintItem, PaymentIntent, PaymentMethod }
9
8
  import { PaymentCurrency } from '../../../store/models/payment-currency';
10
9
  import { getMainProductNameByCheckoutSession } from '../../product';
11
10
  import { formatTime } from '../../time';
12
- import { getExplorerLink } from '../../util';
11
+ import { formatCurrencyInfo, getExplorerLink } from '../../util';
13
12
  import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
14
13
 
15
14
  export interface OneTimePaymentSucceededEmailTemplateOptions {
@@ -96,9 +95,7 @@ export class OneTimePaymentSucceededEmailTemplate
96
95
  const paymentIntent = await PaymentIntent.findByPk(checkoutSession!.payment_intent_id);
97
96
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(paymentIntent!.payment_method_id);
98
97
 
99
- const paymentInfo: string = `${fromUnitToToken(checkoutSession?.amount_total, paymentCurrency.decimal)} ${
100
- paymentCurrency.symbol
101
- }${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
98
+ const paymentInfo = formatCurrencyInfo(checkoutSession?.amount_total, paymentCurrency, paymentMethod);
102
99
 
103
100
  // @ts-expect-error
104
101
  const chainHost: string | undefined = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/brace-style */
2
2
  /* eslint-disable @typescript-eslint/indent */
3
- import { fromUnitToToken } from '@ocap/util';
4
3
  import prettyMsI18n from 'pretty-ms-i18n';
5
4
 
6
5
  import { Op } from 'sequelize';
@@ -13,7 +12,7 @@ import { getMainProductName } from '../../product';
13
12
  import { getCustomerSubscriptionPageUrl, getSubscriptionStakeCancellation } from '../../subscription';
14
13
  import { formatTime, getPrettyMsI18nLocale } from '../../time';
15
14
  import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
16
- import { getSubscriptionNotificationCustomActions } from '../../util';
15
+ import { formatCurrencyInfo, getSubscriptionNotificationCustomActions } from '../../util';
17
16
 
18
17
  export interface SubscriptionCanceledEmailTemplateOptions {
19
18
  subscriptionId: string;
@@ -87,7 +86,7 @@ export class SubscriptionCanceledEmailTemplate implements BaseEmailTemplate<Subs
87
86
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
88
87
 
89
88
  // @ts-ignore
90
- const paymentInfo: string = `${fromUnitToToken(invoice.total, invoice?.paymentCurrency?.decimal)} ${invoice?.paymentCurrency?.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
89
+ const paymentInfo: string = formatCurrencyInfo(invoice.total, invoice?.paymentCurrency, paymentMethod);
91
90
 
92
91
  const customerCancelRequest = subscription.cancelation_details?.reason === 'cancellation_requested';
93
92
  let cancellationReason = '';
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/brace-style */
2
2
  /* eslint-disable @typescript-eslint/indent */
3
- import { fromUnitToToken } from '@ocap/util';
4
3
  import prettyMsI18n from 'pretty-ms-i18n';
5
4
 
6
5
  import { getUserLocale } from '../../../integrations/blocklet/notification';
@@ -11,7 +10,7 @@ import { PaymentCurrency } from '../../../store/models/payment-currency';
11
10
  import { getMainProductName } from '../../product';
12
11
  import { getCustomerSubscriptionPageUrl } from '../../subscription';
13
12
  import { formatTime, getPrettyMsI18nLocale } from '../../time';
14
- import { getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
13
+ import { formatCurrencyInfo, getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
15
14
  import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
16
15
 
17
16
  export interface SubscriptionRefundSucceededEmailTemplateOptions {
@@ -103,8 +102,12 @@ export class SubscriptionRefundSucceededEmailTemplate
103
102
  refund.payment_method_id || invoice?.default_payment_method_id
104
103
  );
105
104
 
106
- const paymentInfo: string = `${fromUnitToToken(paymentIntent?.amount_received || '0', paymentCurrency.decimal)} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
107
- const refundInfo: string = `${fromUnitToToken(refund.amount, paymentCurrency.decimal)} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
105
+ const paymentInfo: string = formatCurrencyInfo(
106
+ paymentIntent?.amount_received || '0',
107
+ paymentCurrency,
108
+ paymentMethod
109
+ );
110
+ const refundInfo: string = formatCurrencyInfo(refund.amount, paymentCurrency, paymentMethod);
108
111
 
109
112
  // @ts-expect-error
110
113
  const chainHost: string | undefined = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/brace-style */
2
2
  /* eslint-disable @typescript-eslint/indent */
3
- import { fromUnitToToken, toDid } from '@ocap/util';
3
+ import { toDid } from '@ocap/util';
4
4
  import camelCase from 'lodash/camelCase';
5
5
  import prettyMsI18n from 'pretty-ms-i18n';
6
6
 
@@ -22,7 +22,7 @@ import { SufficientForPaymentResult, getPaymentDetail } from '../../payment';
22
22
  import { getMainProductName } from '../../product';
23
23
  import { getCustomerSubscriptionPageUrl } from '../../subscription';
24
24
  import { formatTime, getPrettyMsI18nLocale } from '../../time';
25
- import { getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
25
+ import { formatCurrencyInfo, getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
26
26
  import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
27
27
 
28
28
  export interface SubscriptionRenewFailedEmailTemplateOptions {
@@ -123,9 +123,7 @@ export class SubscriptionRenewFailedEmailTemplate
123
123
  );
124
124
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
125
125
 
126
- const paymentInfo: string = `${fromUnitToToken(invoice.amount_remaining, paymentCurrency.decimal)} ${
127
- paymentCurrency.symbol
128
- }${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
126
+ const paymentInfo: string = formatCurrencyInfo(invoice.amount_remaining, paymentCurrency, paymentMethod);
129
127
 
130
128
  const chainHost: string | undefined =
131
129
  paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.api_host;
@@ -20,7 +20,7 @@ import { getCustomerInvoicePageUrl } from '../../invoice';
20
20
  import { getMainProductName } from '../../product';
21
21
  import { getCustomerSubscriptionPageUrl } from '../../subscription';
22
22
  import { formatTime, getPrettyMsI18nLocale } from '../../time';
23
- import { getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
23
+ import { formatCurrencyInfo, getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
24
24
  import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
25
25
 
26
26
  export interface SubscriptionRenewedEmailTemplateOptions {
@@ -112,7 +112,7 @@ export class SubscriptionRenewedEmailTemplate implements BaseEmailTemplate<Subsc
112
112
  );
113
113
 
114
114
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
115
- const paymentInfo: string = `${fromUnitToToken(invoice.total, paymentCurrency.decimal)} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
115
+ const paymentInfo: string = formatCurrencyInfo(invoice.total, paymentCurrency, paymentMethod);
116
116
 
117
117
  const chainHost: string | undefined =
118
118
  paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.api_host;
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable prettier/prettier */
2
- import { fromUnitToToken } from '@ocap/util';
3
2
  import { getUserLocale } from '../../../integrations/blocklet/notification';
4
3
  import { translate } from '../../../locales';
5
4
  import { Customer, PaymentIntent, PaymentMethod, Subscription } from '../../../store/models';
@@ -8,7 +7,7 @@ import { PaymentCurrency } from '../../../store/models/payment-currency';
8
7
  import { getMainProductName } from '../../product';
9
8
  import { getCustomerSubscriptionPageUrl } from '../../subscription';
10
9
  import { formatTime } from '../../time';
11
- import { getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
10
+ import { formatCurrencyInfo, getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
12
11
  import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
13
12
 
14
13
  export interface SubscriptionStakeSlashSucceededEmailTemplateOptions {
@@ -84,7 +83,7 @@ export class SubscriptionStakeSlashSucceededEmailTemplate
84
83
 
85
84
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(paymentIntent.payment_method_id);
86
85
 
87
- const slashInfo: string = `${fromUnitToToken(paymentIntent?.amount_received || '0', paymentCurrency.decimal)} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
86
+ const slashInfo: string = formatCurrencyInfo(paymentIntent?.amount_received || '0', paymentCurrency, paymentMethod);
88
87
 
89
88
  // @ts-expect-error
90
89
  const chainHost: string | undefined = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
@@ -21,7 +21,7 @@ import {
21
21
  import { getCustomerInvoicePageUrl, getOneTimeProductInfo } from '../../invoice';
22
22
  import { getMainProductName } from '../../product';
23
23
  import { formatTime, getPrettyMsI18nLocale } from '../../time';
24
- import { getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
24
+ import { formatCurrencyInfo, getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
25
25
  import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
26
26
 
27
27
  export interface SubscriptionSucceededEmailTemplateOptions {
@@ -134,7 +134,7 @@ export class SubscriptionSucceededEmailTemplate
134
134
  );
135
135
 
136
136
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
137
- const paymentInfo: string = `${paymentAmount} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
137
+ const paymentInfo: string = formatCurrencyInfo(paymentAmount, paymentCurrency, paymentMethod, true);
138
138
 
139
139
  // @FIXME: 获取 chainHost 困难的一批?
140
140
  const chainHost: string | undefined =
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/brace-style */
2
2
  /* eslint-disable @typescript-eslint/indent */
3
- import { fromUnitToToken } from '@ocap/util';
4
3
  import prettyMsI18n from 'pretty-ms-i18n';
5
4
 
6
5
  import { getUserLocale } from '../../../integrations/blocklet/notification';
@@ -20,7 +19,7 @@ import { getCustomerInvoicePageUrl } from '../../invoice';
20
19
  import { getMainProductName } from '../../product';
21
20
  import { getCustomerSubscriptionPageUrl } from '../../subscription';
22
21
  import { formatTime, getPrettyMsI18nLocale } from '../../time';
23
- import { getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
22
+ import { formatCurrencyInfo, getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
24
23
  import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
25
24
 
26
25
  export interface SubscriptionUpgradedEmailTemplateOptions {
@@ -107,10 +106,11 @@ export class SubscriptionUpgradedEmailTemplate implements BaseEmailTemplate<Subs
107
106
 
108
107
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
109
108
 
110
- const paymentInfo: string = `${fromUnitToToken(
109
+ const paymentInfo: string = formatCurrencyInfo(
111
110
  paymentIntent?.amount || invoice.amount_paid,
112
- paymentCurrency.decimal
113
- )} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
111
+ paymentCurrency,
112
+ paymentMethod
113
+ );
114
114
 
115
115
  // @ts-expect-error
116
116
  const chainHost: string | undefined = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
@@ -26,7 +26,7 @@ import {
26
26
  getPaymentAmountForCycleSubscription,
27
27
  } from '../../subscription';
28
28
  import { formatTime, getPrettyMsI18nLocale, getSimplifyDuration } from '../../time';
29
- import { getCustomerRechargeLink, getSubscriptionNotificationCustomActions } from '../../util';
29
+ import { formatCurrencyInfo, getCustomerRechargeLink, getSubscriptionNotificationCustomActions } from '../../util';
30
30
  import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
31
31
 
32
32
  export interface SubscriptionWillRenewEmailTemplateOptions {
@@ -117,7 +117,7 @@ export class SubscriptionWillRenewEmailTemplate
117
117
  const paidType: string = isPrePaid
118
118
  ? translate('notification.common.prepaid', locale)
119
119
  : translate('notification.common.postpaid', locale);
120
- const paymentInfo: string = `${paymentDetail?.price || '0'} ${paymentCurrency.symbol}${paymentMethod ? ` (${paymentMethod.name})` : ''}`;
120
+ const paymentInfo: string = formatCurrencyInfo(paymentDetail?.price || '0', paymentCurrency, paymentMethod, true);
121
121
  const currentPeriodStart: string = isPrePaid
122
122
  ? formatTime(invoice.period_end * 1000)
123
123
  : formatTime(invoice.period_start * 1000);
@@ -270,6 +270,11 @@ export default function createQueue<T = any>({ name, onJob, options = defaults }
270
270
  }
271
271
  };
272
272
 
273
+ const updateJob = async (id: string, updates: any) => {
274
+ const updatedJob = await store.updateJob(id, updates);
275
+ return updatedJob;
276
+ };
277
+
273
278
  // Populate the queue on startup
274
279
  process.nextTick(async () => {
275
280
  try {
@@ -335,6 +340,7 @@ export default function createQueue<T = any>({ name, onJob, options = defaults }
335
340
  get: getJob,
336
341
  delete: deleteJob,
337
342
  cancel,
343
+ update: updateJob,
338
344
  options: {
339
345
  concurrency,
340
346
  maxRetries,
@@ -1,4 +1,4 @@
1
- import { Op } from 'sequelize';
1
+ import { Op, type WhereOptions } from 'sequelize';
2
2
 
3
3
  import { Job, TJob } from '../../store/models/job';
4
4
  import CustomError from '../error';
@@ -26,6 +26,18 @@ export default function createQueueStore(queue: string) {
26
26
  transaction: null,
27
27
  });
28
28
  },
29
+ findJobs(predicate: WhereOptions<TJob>): Promise<TJob[]> {
30
+ return Job.findAll({
31
+ where: {
32
+ queue,
33
+ cancelled: false,
34
+ ...predicate,
35
+ },
36
+ order: [['created_at', 'ASC']],
37
+ transaction: null,
38
+ });
39
+ },
40
+
29
41
  async updateJob(id: string, updates: Partial<TJob>): Promise<TJob> {
30
42
  const job = await Job.findOne({ where: { queue, id }, transaction: null });
31
43
  if (!job) {
@@ -10,9 +10,10 @@ import { joinURL, withQuery, withTrailingSlash } from 'ufo';
10
10
 
11
11
  import axios from 'axios';
12
12
  import { ethers } from 'ethers';
13
+ import { fromUnitToToken } from '@ocap/util';
13
14
  import dayjs from './dayjs';
14
15
  import { blocklet, wallet } from './auth';
15
- import type { PaymentMethod, Subscription } from '../store/models';
16
+ import type { PaymentCurrency, PaymentMethod, Subscription } from '../store/models';
16
17
  import logger from './logger';
17
18
 
18
19
  export const OCAP_PAYMENT_TX_TYPE = 'fg:t:transfer_v2';
@@ -524,3 +525,23 @@ export function resolveAddressChainTypes(address: string): LiteralUnion<'ethereu
524
525
  }
525
526
  return ['arcblock'];
526
527
  }
528
+
529
+ export function formatCurrencyInfo(
530
+ amount: string | number,
531
+ paymentCurrency: PaymentCurrency,
532
+ paymentMethod?: PaymentMethod | null,
533
+ isToken?: boolean
534
+ ) {
535
+ let amountStr = '';
536
+ const defaultPaymentCurrency = {
537
+ symbol: '',
538
+ decimal: 18,
539
+ };
540
+
541
+ if (isToken) {
542
+ amountStr = `${amount || '0'} ${paymentCurrency.symbol ?? defaultPaymentCurrency.symbol}`;
543
+ } else {
544
+ amountStr = `${fromUnitToToken(amount || '0', paymentCurrency.decimal ?? defaultPaymentCurrency.decimal)} ${paymentCurrency.symbol ?? defaultPaymentCurrency.symbol}`;
545
+ }
546
+ return paymentMethod && paymentMethod.type !== 'arcblock' ? `${amountStr} (${paymentMethod.name})` : amountStr;
547
+ }
@@ -212,5 +212,10 @@ export default flat({
212
212
  title: 'Insufficient Credit for SubGuard™',
213
213
  body: 'Your subscription to {productName} has insufficient staked credit for SubGuard™. Please increase your stake to maintain the service or disable it if no longer needed.',
214
214
  },
215
+
216
+ aggregatedSubscriptionRenewed: {
217
+ title: '{count} Subscriptions Renewed',
218
+ body: 'During {startTime} - {endTime}, {count} subscriptions were successfully renewed, total amount: {totalAmount}.\n\nSubscription List:\n{subscriptionList}',
219
+ },
215
220
  },
216
221
  });
@@ -206,5 +206,10 @@ export default flat({
206
206
  title: '订阅守护服务额度不足',
207
207
  body: '您订阅的 {productName} 订阅守护服务额度不足,为了避免影响您的使用,请及时充值。如不再需要订阅守护服务,可关闭该功能。',
208
208
  },
209
+
210
+ aggregatedSubscriptionRenewed: {
211
+ title: '{count} 个订阅续费成功',
212
+ body: '在 {startTime} - {endTime} 期间,共有 {count} 个订阅成功续费,总金额:{totalAmount}。\n\n订阅清单:\n{subscriptionList}',
213
+ },
209
214
  },
210
215
  });