payment-kit 1.13.248 → 1.13.249
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/customer-reward-succeeded.ts +297 -0
- package/api/src/libs/notification/template/one-time-payment-succeeded.ts +16 -1
- package/api/src/libs/notification/template/subscription-will-renew.ts +21 -2
- package/api/src/locales/en.ts +15 -5
- package/api/src/locales/zh.ts +13 -3
- package/api/src/queues/notification.ts +25 -3
- package/blocklet.yml +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/brace-style */
|
|
2
|
+
/* eslint-disable @typescript-eslint/indent */
|
|
3
|
+
import { fromUnitToToken } from '@ocap/util';
|
|
4
|
+
import isEmpty from 'lodash/isEmpty';
|
|
5
|
+
import pWaitFor from 'p-wait-for';
|
|
6
|
+
import type { LiteralUnion } from 'type-fest';
|
|
7
|
+
|
|
8
|
+
import { getUserLocale } from '../../../integrations/blocklet/notification';
|
|
9
|
+
import { translate } from '../../../locales';
|
|
10
|
+
import {
|
|
11
|
+
CheckoutSession,
|
|
12
|
+
Customer,
|
|
13
|
+
DonationSettings,
|
|
14
|
+
NftMintItem,
|
|
15
|
+
PaymentBeneficiary,
|
|
16
|
+
PaymentIntent,
|
|
17
|
+
PaymentLink,
|
|
18
|
+
PaymentMethod,
|
|
19
|
+
} from '../../../store/models';
|
|
20
|
+
import { PaymentCurrency } from '../../../store/models/payment-currency';
|
|
21
|
+
import { getCustomerInvoicePageUrl } from '../../invoice';
|
|
22
|
+
import logger from '../../logger';
|
|
23
|
+
import { formatTime } from '../../time';
|
|
24
|
+
import { getExplorerLink } from '../../util';
|
|
25
|
+
import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
|
|
26
|
+
|
|
27
|
+
export interface CustomerRewardSucceededEmailTemplateOptions {
|
|
28
|
+
checkoutSessionId: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface CustomerRewardSucceededEmailTemplateContext {
|
|
32
|
+
locale: string;
|
|
33
|
+
at: string;
|
|
34
|
+
|
|
35
|
+
nftMintItem: NftMintItem | undefined;
|
|
36
|
+
chainHost: string | undefined;
|
|
37
|
+
userDid: string;
|
|
38
|
+
paymentInfo: string;
|
|
39
|
+
rewardDetail: string;
|
|
40
|
+
donationSettings: DonationSettings;
|
|
41
|
+
|
|
42
|
+
viewInvoiceLink: string;
|
|
43
|
+
viewTxHashLink: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @see https://github.com/blocklet/payment-kit/issues/236#issue-2007698930
|
|
48
|
+
* @description
|
|
49
|
+
* @export
|
|
50
|
+
* @class CustomerRewardSucceededEmailTemplate
|
|
51
|
+
* @implements {BaseEmailTemplate<CustomerRewardSucceededEmailTemplateContext>}
|
|
52
|
+
*/
|
|
53
|
+
export class CustomerRewardSucceededEmailTemplate
|
|
54
|
+
implements BaseEmailTemplate<CustomerRewardSucceededEmailTemplateContext>
|
|
55
|
+
{
|
|
56
|
+
options: CustomerRewardSucceededEmailTemplateOptions;
|
|
57
|
+
|
|
58
|
+
constructor(options: CustomerRewardSucceededEmailTemplateOptions) {
|
|
59
|
+
this.options = options;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async getContext(): Promise<CustomerRewardSucceededEmailTemplateContext> {
|
|
63
|
+
const cs: CheckoutSession | null = await CheckoutSession.findByPk(this.options.checkoutSessionId);
|
|
64
|
+
if (!cs) {
|
|
65
|
+
throw new Error(`CheckoutSession(${this.options.checkoutSessionId}) not found`);
|
|
66
|
+
}
|
|
67
|
+
if (cs.mode !== 'payment') {
|
|
68
|
+
throw new Error(`CheckoutSession(${this.options.checkoutSessionId}) mode must be payment`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const customer = await Customer.findByPk(cs.customer_id);
|
|
72
|
+
if (!customer) {
|
|
73
|
+
throw new Error(`Customer not found: ${cs.customer_id}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await pWaitFor(
|
|
77
|
+
async () => {
|
|
78
|
+
const checkoutSession = await CheckoutSession.findOne({
|
|
79
|
+
where: {
|
|
80
|
+
id: cs.id,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return Boolean(['disabled', 'minted', 'sent', 'error'].includes(checkoutSession?.nft_mint_status as string));
|
|
85
|
+
},
|
|
86
|
+
{ timeout: 1000 * 10, interval: 1000 }
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const checkoutSession = (await CheckoutSession.findByPk(cs.id)) as CheckoutSession;
|
|
90
|
+
if (!checkoutSession.payment_link_id) {
|
|
91
|
+
throw new Error(`Payment link cannot be found for checkoutSession: ${cs.id}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const paymentLink = await PaymentLink.findByPk(checkoutSession.payment_link_id);
|
|
95
|
+
if (!paymentLink) {
|
|
96
|
+
throw new Error(`Payment link cannot be found for payment_link_id(${checkoutSession.payment_link_id})`);
|
|
97
|
+
}
|
|
98
|
+
if (paymentLink.submit_type !== 'donate') {
|
|
99
|
+
throw new Error(`Payment link submit_type(${paymentLink.submit_type}) must be donate`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const paymentCurrency = (await PaymentCurrency.findOne({
|
|
103
|
+
where: {
|
|
104
|
+
id: checkoutSession.currency_id,
|
|
105
|
+
},
|
|
106
|
+
})) as PaymentCurrency;
|
|
107
|
+
|
|
108
|
+
const userDid: string = customer.did;
|
|
109
|
+
const locale = await getUserLocale(userDid);
|
|
110
|
+
const at: string = formatTime(checkoutSession.created_at);
|
|
111
|
+
|
|
112
|
+
const paymentInfo: string = `${fromUnitToToken(checkoutSession?.amount_total, paymentCurrency.decimal)} ${
|
|
113
|
+
paymentCurrency.symbol
|
|
114
|
+
}`;
|
|
115
|
+
const paymentIntent = await PaymentIntent.findByPk(checkoutSession!.payment_intent_id);
|
|
116
|
+
if (!paymentIntent) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Payment intent cannot be found for checkoutSession.payment_intent_id${checkoutSession!.payment_intent_id}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
const rewardDetail: string = this.getRewardDetail({
|
|
122
|
+
paymentIntent,
|
|
123
|
+
paymentCurrency,
|
|
124
|
+
locale,
|
|
125
|
+
});
|
|
126
|
+
const donationSettings: DonationSettings = paymentLink.donation_settings as DonationSettings;
|
|
127
|
+
|
|
128
|
+
const hasNft: boolean = checkoutSession?.nft_mint_status === 'minted';
|
|
129
|
+
const nftMintItem: NftMintItem | undefined = hasNft
|
|
130
|
+
? // @ts-expect-error
|
|
131
|
+
checkoutSession?.nft_mint_details?.[paymentMethod.type]
|
|
132
|
+
: undefined;
|
|
133
|
+
|
|
134
|
+
const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(paymentIntent!.payment_method_id);
|
|
135
|
+
// @ts-expect-error
|
|
136
|
+
const chainHost: string | undefined = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
|
|
137
|
+
const viewInvoiceLink = getCustomerInvoicePageUrl({
|
|
138
|
+
invoiceId: checkoutSession.invoice_id!,
|
|
139
|
+
userDid,
|
|
140
|
+
locale,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// @ts-expect-error
|
|
144
|
+
const txHash: string | undefined = paymentIntent?.payment_details?.[paymentMethod.type]?.tx_hash;
|
|
145
|
+
const viewTxHashLink: string =
|
|
146
|
+
(txHash &&
|
|
147
|
+
getExplorerLink({
|
|
148
|
+
type: 'tx',
|
|
149
|
+
did: txHash,
|
|
150
|
+
chainHost,
|
|
151
|
+
})) ||
|
|
152
|
+
'';
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
locale,
|
|
156
|
+
at,
|
|
157
|
+
|
|
158
|
+
userDid,
|
|
159
|
+
nftMintItem,
|
|
160
|
+
chainHost,
|
|
161
|
+
paymentInfo,
|
|
162
|
+
rewardDetail,
|
|
163
|
+
donationSettings,
|
|
164
|
+
|
|
165
|
+
viewInvoiceLink,
|
|
166
|
+
viewTxHashLink,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
getRewardDetail({
|
|
170
|
+
paymentIntent,
|
|
171
|
+
paymentCurrency,
|
|
172
|
+
locale,
|
|
173
|
+
}: {
|
|
174
|
+
paymentIntent: PaymentIntent;
|
|
175
|
+
paymentCurrency: PaymentCurrency;
|
|
176
|
+
locale: LiteralUnion<'zh' | 'en', string>;
|
|
177
|
+
}): string {
|
|
178
|
+
if (isEmpty(paymentIntent.beneficiaries)) {
|
|
179
|
+
logger.warn('Payment intent not available', { paymentIntentId: paymentIntent.id });
|
|
180
|
+
return '';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const rewardDetail: string = paymentIntent
|
|
184
|
+
.beneficiaries!.map((x: PaymentBeneficiary) => {
|
|
185
|
+
return translate('notification.customerRewardSucceeded.received', locale, {
|
|
186
|
+
address: `${x.address}`,
|
|
187
|
+
amount: `${fromUnitToToken(x.share, paymentCurrency.decimal)} ${paymentCurrency.symbol}`,
|
|
188
|
+
});
|
|
189
|
+
})
|
|
190
|
+
.join('\r\n');
|
|
191
|
+
|
|
192
|
+
return rewardDetail;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async getTemplate(): Promise<BaseEmailTemplateType> {
|
|
196
|
+
const {
|
|
197
|
+
locale,
|
|
198
|
+
at,
|
|
199
|
+
nftMintItem,
|
|
200
|
+
chainHost,
|
|
201
|
+
userDid,
|
|
202
|
+
paymentInfo,
|
|
203
|
+
rewardDetail,
|
|
204
|
+
donationSettings,
|
|
205
|
+
|
|
206
|
+
viewInvoiceLink,
|
|
207
|
+
viewTxHashLink,
|
|
208
|
+
} = await this.getContext();
|
|
209
|
+
|
|
210
|
+
const template: BaseEmailTemplateType = {
|
|
211
|
+
title: `${translate('notification.customerRewardSucceeded.title', locale, {
|
|
212
|
+
amount: paymentInfo,
|
|
213
|
+
})}`,
|
|
214
|
+
body: `${translate('notification.customerRewardSucceeded.body', locale, {
|
|
215
|
+
at,
|
|
216
|
+
amount: paymentInfo,
|
|
217
|
+
subject: `<${donationSettings.title}(link:${donationSettings.reference})>`,
|
|
218
|
+
})}`,
|
|
219
|
+
// @ts-expect-error
|
|
220
|
+
attachments: [
|
|
221
|
+
nftMintItem &&
|
|
222
|
+
chainHost && {
|
|
223
|
+
type: 'asset',
|
|
224
|
+
data: {
|
|
225
|
+
chainHost,
|
|
226
|
+
did: nftMintItem.address,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
type: 'section',
|
|
231
|
+
fields: [
|
|
232
|
+
{
|
|
233
|
+
type: 'text',
|
|
234
|
+
data: {
|
|
235
|
+
type: 'plain',
|
|
236
|
+
color: '#9397A1',
|
|
237
|
+
text: translate('notification.common.account', locale),
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
type: 'text',
|
|
242
|
+
data: {
|
|
243
|
+
type: 'plain',
|
|
244
|
+
text: userDid,
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
type: 'text',
|
|
249
|
+
data: {
|
|
250
|
+
type: 'plain',
|
|
251
|
+
color: '#9397A1',
|
|
252
|
+
text: translate('notification.common.rewardAmount', locale),
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
type: 'text',
|
|
257
|
+
data: {
|
|
258
|
+
type: 'plain',
|
|
259
|
+
text: `${paymentInfo}`,
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
type: 'text',
|
|
264
|
+
data: {
|
|
265
|
+
type: 'plain',
|
|
266
|
+
color: '#9397A1',
|
|
267
|
+
text: translate('notification.common.rewardDetail', locale),
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
type: 'text',
|
|
272
|
+
data: {
|
|
273
|
+
type: 'plain',
|
|
274
|
+
text: `${rewardDetail}`,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
].filter(Boolean),
|
|
278
|
+
},
|
|
279
|
+
].filter(Boolean),
|
|
280
|
+
// @ts-ignore
|
|
281
|
+
actions: [
|
|
282
|
+
viewInvoiceLink && {
|
|
283
|
+
name: translate('notification.common.viewInvoice', locale),
|
|
284
|
+
title: translate('notification.common.viewInvoice', locale),
|
|
285
|
+
link: viewInvoiceLink,
|
|
286
|
+
},
|
|
287
|
+
viewTxHashLink && {
|
|
288
|
+
name: translate('notification.common.viewTxHash', locale),
|
|
289
|
+
title: translate('notification.common.viewTxHash', locale),
|
|
290
|
+
link: viewTxHashLink,
|
|
291
|
+
},
|
|
292
|
+
].filter(Boolean),
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
return template;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -5,7 +5,14 @@ import pWaitFor from 'p-wait-for';
|
|
|
5
5
|
|
|
6
6
|
import { getUserLocale } from '../../../integrations/blocklet/notification';
|
|
7
7
|
import { translate } from '../../../locales';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
CheckoutSession,
|
|
10
|
+
Customer,
|
|
11
|
+
NftMintItem,
|
|
12
|
+
PaymentIntent,
|
|
13
|
+
PaymentLink,
|
|
14
|
+
PaymentMethod,
|
|
15
|
+
} from '../../../store/models';
|
|
9
16
|
import { PaymentCurrency } from '../../../store/models/payment-currency';
|
|
10
17
|
import { getMainProductNameByCheckoutSession } from '../../product';
|
|
11
18
|
import { formatTime } from '../../time';
|
|
@@ -75,6 +82,14 @@ export class OneTimePaymentSucceededEmailTemplate
|
|
|
75
82
|
);
|
|
76
83
|
|
|
77
84
|
const checkoutSession = (await CheckoutSession.findByPk(cs.id)) as CheckoutSession;
|
|
85
|
+
const paymentLink = await PaymentLink.findByPk(checkoutSession.payment_link_id);
|
|
86
|
+
if (!paymentLink) {
|
|
87
|
+
throw new Error(`Payment link cannot be found for payment_link_id(${checkoutSession.payment_link_id})`);
|
|
88
|
+
}
|
|
89
|
+
if (paymentLink.submit_type !== 'auto') {
|
|
90
|
+
throw new Error(`Payment link submit_type(${paymentLink.submit_type}) must be auto`);
|
|
91
|
+
}
|
|
92
|
+
|
|
78
93
|
const paymentCurrency = (await PaymentCurrency.findOne({
|
|
79
94
|
where: {
|
|
80
95
|
id: checkoutSession.currency_id,
|
|
@@ -11,7 +11,7 @@ import { Customer, Invoice, PaymentMethod, Subscription } from '../../../store/m
|
|
|
11
11
|
import { PaymentCurrency } from '../../../store/models/payment-currency';
|
|
12
12
|
import { PaymentDetail, getPaymentDetail } from '../../payment';
|
|
13
13
|
import { getMainProductName } from '../../product';
|
|
14
|
-
import { getCustomerSubscriptionPageUrl } from '../../subscription';
|
|
14
|
+
import { getCustomerSubscriptionPageUrl, getUpcomingInvoiceAmount } from '../../subscription';
|
|
15
15
|
import { formatTime, getPrettyMsI18nLocale } from '../../time';
|
|
16
16
|
import { getExplorerLink } from '../../util';
|
|
17
17
|
import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
|
|
@@ -84,7 +84,11 @@ export class SubscriptionWillRenewEmailTemplate
|
|
|
84
84
|
locale === 'en' ? this.getWillRenewDuration(locale) : this.getWillRenewDuration(locale).split(' ').join('');
|
|
85
85
|
|
|
86
86
|
const paymentDetail: PaymentDetail = await getPaymentDetail(userDid, invoice);
|
|
87
|
-
const
|
|
87
|
+
const upcomingInvoiceAmount = await getUpcomingInvoiceAmount(subscription.id);
|
|
88
|
+
const paymentInfo: string = `${fromUnitToToken(
|
|
89
|
+
+upcomingInvoiceAmount.amount,
|
|
90
|
+
upcomingInvoiceAmount.currency?.decimal
|
|
91
|
+
)} ${paymentCurrency.symbol}`;
|
|
88
92
|
const currentPeriodStart: string = formatTime(invoice.period_start * 1000);
|
|
89
93
|
const currentPeriodEnd: string = formatTime(invoice.period_end * 1000);
|
|
90
94
|
const duration: string = prettyMsI18n(
|
|
@@ -248,6 +252,21 @@ export class SubscriptionWillRenewEmailTemplate
|
|
|
248
252
|
text: productName,
|
|
249
253
|
},
|
|
250
254
|
},
|
|
255
|
+
{
|
|
256
|
+
type: 'text',
|
|
257
|
+
data: {
|
|
258
|
+
type: 'plain',
|
|
259
|
+
color: '#9397A1',
|
|
260
|
+
text: translate('notification.common.currentBalance', locale),
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
type: 'text',
|
|
265
|
+
data: {
|
|
266
|
+
type: 'plain',
|
|
267
|
+
text: `${paymentDetail.balance} ${paymentDetail.symbol}`,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
251
270
|
{
|
|
252
271
|
type: 'text',
|
|
253
272
|
data: {
|
package/api/src/locales/en.ts
CHANGED
|
@@ -26,6 +26,10 @@ export default flat({
|
|
|
26
26
|
cancellationReason: 'Cancellation reason',
|
|
27
27
|
renewNow: 'Pay now',
|
|
28
28
|
immediateRecharge: 'Immediate recharge',
|
|
29
|
+
amount: 'Amount',
|
|
30
|
+
rewardAmount: 'Reward amount',
|
|
31
|
+
rewardDetail: 'Reward detail',
|
|
32
|
+
currentBalance: 'Current balance',
|
|
29
33
|
},
|
|
30
34
|
|
|
31
35
|
sendTo: 'Sent to',
|
|
@@ -62,16 +66,16 @@ export default flat({
|
|
|
62
66
|
},
|
|
63
67
|
|
|
64
68
|
subscriptionWillRenew: {
|
|
65
|
-
title: '{productName}
|
|
66
|
-
body: 'Your
|
|
69
|
+
title: '{productName} automatic payment reminder',
|
|
70
|
+
body: 'Your subscription to {productName} is scheduled for automatic payment on {at}({willRenewDuration} later). If you have any questions or need assistance, please feel free to contact us.',
|
|
67
71
|
unableToPayBody:
|
|
68
|
-
'Your {productName}
|
|
69
|
-
renewAmount: '
|
|
72
|
+
'Your subscription to {productName} is scheduled for automatic payment on {at}({willRenewDuration} later). <span style="color: red;">Your current balance is {balance}, which is less than {price}, please ensure that your account has enough balance to avoid payment failure.</span> If you have any questions or need assistance, please feel free to contact us.',
|
|
73
|
+
renewAmount: 'Payment Amount',
|
|
70
74
|
},
|
|
71
75
|
|
|
72
76
|
subscriptionRenewed: {
|
|
73
77
|
title: '{productName} payment successful',
|
|
74
|
-
body: 'Payment for your subscription {productName} is successfully collected on {at}.
|
|
78
|
+
body: 'Payment for your subscription {productName} is successfully collected on {at}. Thanks for your continued support and trust, we wish you a pleasant journey when using this service!',
|
|
75
79
|
noExpenseIncurred: 'No expenses incurred during the service ',
|
|
76
80
|
},
|
|
77
81
|
|
|
@@ -99,6 +103,12 @@ export default flat({
|
|
|
99
103
|
body: 'Your subscription to {productName} has been successfully refunded on {at}, with a refund amount of {refundInfo}. If you have any questions, please feel free to contact us.',
|
|
100
104
|
},
|
|
101
105
|
|
|
106
|
+
customerRewardSucceeded: {
|
|
107
|
+
title: 'Thanks for your reward of {amount}',
|
|
108
|
+
body: 'Thanks for your reward on {at} for {subject}, the amount of reward is {amount}. Your support is our driving force, thanks for your generous support!',
|
|
109
|
+
received: '{address} has received {amount}',
|
|
110
|
+
},
|
|
111
|
+
|
|
102
112
|
subscriptWillCanceled: {
|
|
103
113
|
title: '{productName} subscription is about to be automatically cancelled ',
|
|
104
114
|
body: 'Your subscription {productName} will be automatically unsubscribed by the system after {at} (after {willCancelDuration}) due to a long period of failure to automatically complete the deduction. Please handle the problem of charge deduction manually in time, so as not to affect the use. If you have any questions, please feel free to contact us. ',
|
package/api/src/locales/zh.ts
CHANGED
|
@@ -26,6 +26,10 @@ export default flat({
|
|
|
26
26
|
cancellationReason: '取消原因',
|
|
27
27
|
renewNow: '立即付款',
|
|
28
28
|
immediateRecharge: '立即充值',
|
|
29
|
+
amount: '金额',
|
|
30
|
+
rewardAmount: '打赏金额',
|
|
31
|
+
rewardDetail: '打赏详情',
|
|
32
|
+
currentBalance: '当前余额',
|
|
29
33
|
},
|
|
30
34
|
|
|
31
35
|
sendTo: '发送给',
|
|
@@ -62,10 +66,10 @@ export default flat({
|
|
|
62
66
|
},
|
|
63
67
|
|
|
64
68
|
subscriptionWillRenew: {
|
|
65
|
-
title: '{productName}
|
|
66
|
-
body: '您订阅的 {productName}
|
|
69
|
+
title: '{productName} 自动扣费提醒',
|
|
70
|
+
body: '您订阅的 {productName} 将在 {at}({willRenewDuration}后) 发起自动扣费。若有任何疑问或需要帮助,请随时与我们联系。',
|
|
67
71
|
unableToPayBody:
|
|
68
|
-
'您订阅的 {productName}
|
|
72
|
+
'您订阅的 {productName} 将在 {at}({willRenewDuration}后) 发起自动扣费。<span style="color: red;">您的当前余额为 {balance},不足 {price}</span>,请确保您的账户余额充足,避免扣费失败。若有任何疑问或需要帮助,请随时与我们联系。',
|
|
69
73
|
renewAmount: '扣费金额',
|
|
70
74
|
},
|
|
71
75
|
|
|
@@ -97,6 +101,12 @@ export default flat({
|
|
|
97
101
|
body: '您订阅的 {productName} 在 {at} 已退款成功,退款金额为 {refundInfo}。如有任何疑问,请随时与我们联系。',
|
|
98
102
|
},
|
|
99
103
|
|
|
104
|
+
customerRewardSucceeded: {
|
|
105
|
+
title: '感谢您打赏的 {amount}',
|
|
106
|
+
body: '感谢您于 {at} 在 {subject} 下的打赏,打赏金额为 {amount}。您的支持是我们前行的动力,谢谢您的大力支持!',
|
|
107
|
+
received: '{address} 收到了 {amount}',
|
|
108
|
+
},
|
|
109
|
+
|
|
100
110
|
subscriptWillCanceled: {
|
|
101
111
|
title: '{productName} 订阅即将自动取消',
|
|
102
112
|
body: '由于长时间未能自动完成扣费,您订阅的 {productName} 将于 {at} ({willCancelDuration}后) 被系统自动取消订阅。请您及时手动处理扣费问题,以免影响使用。如有任何疑问,请随时与我们联系。',
|
|
@@ -3,6 +3,10 @@ import { events } from '../libs/event';
|
|
|
3
3
|
import logger from '../libs/logger';
|
|
4
4
|
import { Notification } from '../libs/notification';
|
|
5
5
|
import type { BaseEmailTemplate } from '../libs/notification/template/base';
|
|
6
|
+
import {
|
|
7
|
+
CustomerRewardSucceededEmailTemplate,
|
|
8
|
+
CustomerRewardSucceededEmailTemplateOptions,
|
|
9
|
+
} from '../libs/notification/template/customer-reward-succeeded';
|
|
6
10
|
import {
|
|
7
11
|
OneTimePaymentSucceededEmailTemplate,
|
|
8
12
|
OneTimePaymentSucceededEmailTemplateOptions,
|
|
@@ -48,7 +52,7 @@ import {
|
|
|
48
52
|
SubscriptionWillRenewEmailTemplateOptions,
|
|
49
53
|
} from '../libs/notification/template/subscription-will-renew';
|
|
50
54
|
import createQueue from '../libs/queue';
|
|
51
|
-
import { CheckoutSession, EventType, Invoice, Refund, Subscription } from '../store/models';
|
|
55
|
+
import { CheckoutSession, EventType, Invoice, PaymentLink, Refund, Subscription } from '../store/models';
|
|
52
56
|
|
|
53
57
|
export type NotificationQueueJobOptions = any;
|
|
54
58
|
|
|
@@ -56,7 +60,8 @@ export type NotificationQueueJobType =
|
|
|
56
60
|
| EventType
|
|
57
61
|
| 'customer.subscription.will_renew'
|
|
58
62
|
| 'customer.subscription.trial_will_end'
|
|
59
|
-
| 'customer.subscription.will_canceled'
|
|
63
|
+
| 'customer.subscription.will_canceled'
|
|
64
|
+
| 'customer.reward.succeeded';
|
|
60
65
|
|
|
61
66
|
export type NotificationQueueJob = {
|
|
62
67
|
type: NotificationQueueJobType;
|
|
@@ -97,6 +102,9 @@ function getNotificationTemplate(job: NotificationQueueJob): BaseEmailTemplate {
|
|
|
97
102
|
if (job.type === 'refund.succeeded') {
|
|
98
103
|
return new SubscriptionRefundSucceededEmailTemplate(job.options as SubscriptionRefundSucceededEmailTemplateOptions);
|
|
99
104
|
}
|
|
105
|
+
if (job.type === 'customer.reward.succeeded') {
|
|
106
|
+
return new CustomerRewardSucceededEmailTemplate(job.options as CustomerRewardSucceededEmailTemplateOptions);
|
|
107
|
+
}
|
|
100
108
|
|
|
101
109
|
throw new Error(`Unknown job type: ${job.type}`);
|
|
102
110
|
}
|
|
@@ -160,8 +168,22 @@ export async function startNotificationQueue() {
|
|
|
160
168
|
});
|
|
161
169
|
|
|
162
170
|
// 一次性购买成功
|
|
163
|
-
events.on('checkout.session.completed', (checkoutSession: CheckoutSession) => {
|
|
171
|
+
events.on('checkout.session.completed', async (checkoutSession: CheckoutSession) => {
|
|
164
172
|
if (checkoutSession.mode === 'payment') {
|
|
173
|
+
const paymentLink = await PaymentLink.findByPk(checkoutSession.payment_link_id);
|
|
174
|
+
if (paymentLink?.submit_type === 'donate') {
|
|
175
|
+
notificationQueue.push({
|
|
176
|
+
id: `customer.reward.succeeded.${checkoutSession.id}`,
|
|
177
|
+
job: {
|
|
178
|
+
type: 'customer.reward.succeeded',
|
|
179
|
+
options: {
|
|
180
|
+
checkoutSessionId: checkoutSession.id,
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
165
187
|
notificationQueue.push({
|
|
166
188
|
id: `checkout.session.completed.${checkoutSession.id}`,
|
|
167
189
|
job: {
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.249",
|
|
4
4
|
"scripts": {
|
|
5
|
-
"dev": "
|
|
5
|
+
"dev": "blocklet dev --open",
|
|
6
6
|
"eject": "vite eject",
|
|
7
7
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
8
8
|
"lint:fix": "npm run lint -- --fix",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@arcblock/ux": "^2.9.77",
|
|
52
52
|
"@arcblock/validator": "^1.18.116",
|
|
53
53
|
"@blocklet/logger": "1.16.26",
|
|
54
|
-
"@blocklet/payment-react": "1.13.
|
|
54
|
+
"@blocklet/payment-react": "1.13.249",
|
|
55
55
|
"@blocklet/sdk": "1.16.26",
|
|
56
56
|
"@blocklet/ui-react": "^2.9.77",
|
|
57
57
|
"@blocklet/uploader": "^0.1.6",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"devDependencies": {
|
|
117
117
|
"@abtnode/types": "1.16.26",
|
|
118
118
|
"@arcblock/eslint-config-ts": "^0.3.0",
|
|
119
|
-
"@blocklet/payment-types": "1.13.
|
|
119
|
+
"@blocklet/payment-types": "1.13.249",
|
|
120
120
|
"@types/cookie-parser": "^1.4.7",
|
|
121
121
|
"@types/cors": "^2.8.17",
|
|
122
122
|
"@types/dotenv-flow": "^3.3.3",
|
|
@@ -155,5 +155,5 @@
|
|
|
155
155
|
"parser": "typescript"
|
|
156
156
|
}
|
|
157
157
|
},
|
|
158
|
-
"gitHead": "
|
|
158
|
+
"gitHead": "8d7617c2a9cb318f4e161163f44de29abf99c150"
|
|
159
159
|
}
|