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.
- package/api/src/libs/notification/template/aggregated-subscription-renewed.ts +165 -0
- package/api/src/libs/notification/template/one-time-payment-succeeded.ts +2 -5
- package/api/src/libs/notification/template/subscription-canceled.ts +2 -3
- package/api/src/libs/notification/template/subscription-refund-succeeded.ts +7 -4
- package/api/src/libs/notification/template/subscription-renew-failed.ts +3 -5
- package/api/src/libs/notification/template/subscription-renewed.ts +2 -2
- package/api/src/libs/notification/template/subscription-stake-slash-succeeded.ts +2 -3
- package/api/src/libs/notification/template/subscription-succeeded.ts +2 -2
- package/api/src/libs/notification/template/subscription-upgraded.ts +5 -5
- package/api/src/libs/notification/template/subscription-will-renew.ts +2 -2
- package/api/src/libs/queue/index.ts +6 -0
- package/api/src/libs/queue/store.ts +13 -1
- package/api/src/libs/util.ts +22 -1
- package/api/src/locales/en.ts +5 -0
- package/api/src/locales/zh.ts +5 -0
- package/api/src/queues/notification.ts +353 -11
- package/api/src/routes/customers.ts +61 -0
- package/api/src/routes/subscriptions.ts +1 -1
- package/api/src/store/migrations/20250328-notification-preference.ts +29 -0
- package/api/src/store/models/customer.ts +15 -1
- package/api/src/store/models/types.ts +17 -1
- package/blocklet.yml +1 -1
- package/package.json +19 -19
- package/src/components/customer/form.tsx +21 -2
- package/src/components/customer/notification-preference.tsx +428 -0
- package/src/components/layout/user.tsx +1 -1
- package/src/locales/en.tsx +30 -0
- package/src/locales/zh.tsx +30 -0
- package/src/pages/customer/index.tsx +26 -22
- package/src/pages/customer/recharge/account.tsx +7 -7
- 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
|
|
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 =
|
|
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 =
|
|
107
|
-
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
109
|
+
const paymentInfo: string = formatCurrencyInfo(
|
|
111
110
|
paymentIntent?.amount || invoice.amount_paid,
|
|
112
|
-
paymentCurrency
|
|
113
|
-
|
|
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 =
|
|
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) {
|
package/api/src/libs/util.ts
CHANGED
|
@@ -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
|
+
}
|
package/api/src/locales/en.ts
CHANGED
|
@@ -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
|
});
|
package/api/src/locales/zh.ts
CHANGED
|
@@ -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
|
});
|