payment-kit 1.15.4 → 1.15.5

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 (30) hide show
  1. package/api/src/index.ts +3 -0
  2. package/api/src/integrations/blocklet/user.ts +30 -0
  3. package/api/src/libs/invoice.ts +41 -0
  4. package/api/src/libs/notification/template/customer-reward-succeeded.ts +41 -20
  5. package/api/src/libs/notification/template/subscription-renew-failed.ts +19 -1
  6. package/api/src/libs/notification/template/subscription-succeeded.ts +94 -9
  7. package/api/src/libs/notification/template/subscription-trial-start.ts +92 -18
  8. package/api/src/libs/notification/template/subscription-trial-will-end.ts +45 -15
  9. package/api/src/libs/notification/template/subscription-will-renew.ts +28 -11
  10. package/api/src/libs/util.ts +18 -1
  11. package/api/src/locales/en.ts +12 -3
  12. package/api/src/locales/zh.ts +12 -3
  13. package/api/src/queues/payment.ts +3 -1
  14. package/api/src/routes/donations.ts +1 -1
  15. package/api/src/routes/subscriptions.ts +30 -5
  16. package/api/src/routes/usage-records.ts +13 -4
  17. package/api/src/store/migrations/20240910-customer-sync.ts +21 -0
  18. package/api/src/store/models/customer.ts +5 -0
  19. package/blocklet.yml +1 -1
  20. package/package.json +9 -9
  21. package/scripts/sdk.js +25 -2
  22. package/src/components/payment-link/before-pay.tsx +41 -29
  23. package/src/components/pricing-table/product-settings.tsx +37 -25
  24. package/src/pages/admin/index.tsx +0 -1
  25. package/src/pages/admin/payments/intents/detail.tsx +14 -1
  26. package/src/pages/admin/payments/payouts/detail.tsx +6 -1
  27. package/src/pages/admin/products/pricing-tables/create.tsx +3 -0
  28. package/src/pages/checkout/pricing-table.tsx +26 -7
  29. package/src/pages/customer/index.tsx +3 -3
  30. package/src/pages/customer/invoice/past-due.tsx +14 -2
package/api/src/index.ts CHANGED
@@ -37,6 +37,7 @@ import setupHandlers from './routes/connect/setup';
37
37
  import subscribeHandlers from './routes/connect/subscribe';
38
38
  import { initialize } from './store/models';
39
39
  import { sequelize } from './store/sequelize';
40
+ import { initUserHandler } from './integrations/blocklet/user';
40
41
 
41
42
  dotenv.config();
42
43
 
@@ -131,4 +132,6 @@ export const server = app.listen(port, (err?: any) => {
131
132
  initEventBroadcast();
132
133
 
133
134
  initResourceHandler();
135
+
136
+ initUserHandler();
134
137
  });
@@ -0,0 +1,30 @@
1
+ import { Customer } from '@api/store/models';
2
+ import notification from '@blocklet/sdk/service/notification';
3
+
4
+ import logger from '../../libs/logger';
5
+
6
+ const handleUserUpdate = async ({ user }: { user: any }) => {
7
+ logger.info('user Updated', {
8
+ did: user.did,
9
+ updated_at: user.updatedAt,
10
+ });
11
+ const customer = await Customer.findOne({
12
+ where: {
13
+ did: user.did,
14
+ },
15
+ });
16
+ if (customer) {
17
+ const now = Math.floor(Date.now() / 1000);
18
+ await customer.update({
19
+ name: user.fullName,
20
+ email: user.email,
21
+ phone: user.phone,
22
+ last_sync_at: now,
23
+ });
24
+ logger.info(`customer info updated: ${customer.did}`);
25
+ }
26
+ };
27
+
28
+ export function initUserHandler() {
29
+ notification.on('user.updated', handleUserUpdate);
30
+ }
@@ -2,7 +2,10 @@ import { component } from '@blocklet/sdk';
2
2
  import type { LiteralUnion } from 'type-fest';
3
3
  import { withQuery } from 'ufo';
4
4
 
5
+ import { Invoice, InvoiceItem, PaymentCurrency, Price, Product } from '@api/store/models';
6
+ import { fromUnitToToken } from '@ocap/util';
5
7
  import { getConnectQueryParam } from './util';
8
+ import { expandLineItems } from './session';
6
9
 
7
10
  export function getCustomerInvoicePageUrl({
8
11
  invoiceId,
@@ -23,3 +26,41 @@ export function getCustomerInvoicePageUrl({
23
26
  })
24
27
  );
25
28
  }
29
+
30
+ export async function getOneTimeProductInfo(invoiceId: string, paymentCurrency: PaymentCurrency) {
31
+ try {
32
+ const doc = await Invoice.findOne({
33
+ where: { id: invoiceId },
34
+ include: [{ model: InvoiceItem, as: 'lines' }],
35
+ });
36
+ if (!doc) {
37
+ throw new Error(`Invoice not found in ${invoiceId}`);
38
+ }
39
+ const json = doc.toJSON();
40
+ const products = (await Product.findAll()).map((x) => x.toJSON());
41
+ const prices = (await Price.findAll()).map((x) => x.toJSON());
42
+ // @ts-ignore
43
+ expandLineItems(json.lines, products, prices);
44
+ const oneTimePaymentInfo: Array<{
45
+ productName: string;
46
+ paymentInfo: string;
47
+ quantity: number;
48
+ }> = [];
49
+ // @ts-ignore
50
+ (json.lines || []).forEach((x) => {
51
+ const { recurring, product } = x.price;
52
+ if (!recurring) {
53
+ // it's one-time product
54
+ oneTimePaymentInfo.push({
55
+ productName: product?.name,
56
+ paymentInfo: `${fromUnitToToken(x.amount, paymentCurrency.decimal)} ${paymentCurrency.symbol}`,
57
+ quantity: x.quantity,
58
+ });
59
+ }
60
+ });
61
+ return oneTimePaymentInfo;
62
+ } catch (err) {
63
+ console.error(err);
64
+ return [];
65
+ }
66
+ }
@@ -5,6 +5,7 @@ import isEmpty from 'lodash/isEmpty';
5
5
  import pWaitFor from 'p-wait-for';
6
6
  import type { LiteralUnion } from 'type-fest';
7
7
 
8
+ import { joinURL } from 'ufo';
8
9
  import { getUserLocale } from '../../../integrations/blocklet/notification';
9
10
  import { translate } from '../../../locales';
10
11
  import {
@@ -21,7 +22,7 @@ import { PaymentCurrency } from '../../../store/models/payment-currency';
21
22
  import { getCustomerInvoicePageUrl } from '../../invoice';
22
23
  import logger from '../../logger';
23
24
  import { formatTime } from '../../time';
24
- import { getExplorerLink } from '../../util';
25
+ import { getCustomerProfileUrl, getExplorerLink } from '../../util';
25
26
  import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
26
27
  import { blocklet } from '../../auth';
27
28
 
@@ -37,7 +38,14 @@ interface CustomerRewardSucceededEmailTemplateContext {
37
38
  chainHost: string | undefined;
38
39
  userDid: string;
39
40
  paymentInfo: string;
40
- rewardDetail: string;
41
+ rewardDetail:
42
+ | {
43
+ url: string;
44
+ title: string;
45
+ appDID: string;
46
+ logo?: string;
47
+ }[]
48
+ | null;
41
49
  donationSettings: DonationSettings;
42
50
 
43
51
  viewInvoiceLink: string;
@@ -117,7 +125,7 @@ export class CustomerRewardSucceededEmailTemplate
117
125
  `Payment intent cannot be found for checkoutSession.payment_intent_id${checkoutSession!.payment_intent_id}`
118
126
  );
119
127
  }
120
- const rewardDetail: string = await this.getRewardDetail({
128
+ const rewardDetail = await this.getRewardDetail({
121
129
  paymentIntent,
122
130
  paymentCurrency,
123
131
  locale,
@@ -173,10 +181,18 @@ export class CustomerRewardSucceededEmailTemplate
173
181
  paymentIntent: PaymentIntent;
174
182
  paymentCurrency: PaymentCurrency;
175
183
  locale: LiteralUnion<'zh' | 'en', string>;
176
- }): Promise<string> {
184
+ }): Promise<
185
+ | {
186
+ url: string;
187
+ title: string;
188
+ appDID: string;
189
+ logo?: string;
190
+ }[]
191
+ | null
192
+ > {
177
193
  if (isEmpty(paymentIntent.beneficiaries)) {
178
194
  logger.warn('Payment intent not available', { paymentIntentId: paymentIntent.id });
179
- return '';
195
+ return null;
180
196
  }
181
197
 
182
198
  const promises = paymentIntent.beneficiaries!.map((x: PaymentBeneficiary) => {
@@ -185,16 +201,22 @@ export class CustomerRewardSucceededEmailTemplate
185
201
  const users = await Promise.all(promises);
186
202
  if (!users.length) {
187
203
  logger.warn('No users found for payment intent', { paymentIntentId: paymentIntent.id });
188
- return '';
204
+ return null;
189
205
  }
190
- const rewardDetail: string = paymentIntent
191
- .beneficiaries!.map((x: PaymentBeneficiary, index: number) => {
192
- return translate('notification.customerRewardSucceeded.received', locale, {
193
- address: `${users[index]?.user?.fullName || ''} ( ${x.address} )`,
206
+ const rewardDetail = paymentIntent.beneficiaries!.map((x: PaymentBeneficiary, index: number) => {
207
+ return {
208
+ url: getCustomerProfileUrl({ userDid: x.address, locale }),
209
+ title: translate('notification.customerRewardSucceeded.received', locale, {
210
+ address: users[index]?.user?.fullName || x.address,
194
211
  amount: `${fromUnitToToken(x.share, paymentCurrency.decimal)} ${paymentCurrency.symbol}`,
195
- });
196
- })
197
- .join('\r\n');
212
+ }),
213
+ logo:
214
+ process.env.BLOCKLET_APP_URL && users[index]?.user?.avatar
215
+ ? joinURL(process.env.BLOCKLET_APP_URL, users[index]?.user?.avatar as string)
216
+ : '',
217
+ appDID: x.address,
218
+ };
219
+ });
198
220
 
199
221
  return rewardDetail;
200
222
  }
@@ -274,15 +296,14 @@ export class CustomerRewardSucceededEmailTemplate
274
296
  text: translate('notification.common.rewardDetail', locale),
275
297
  },
276
298
  },
277
- {
278
- type: 'text',
279
- data: {
280
- type: 'plain',
281
- text: `${rewardDetail}`,
282
- },
283
- },
284
299
  ].filter(Boolean),
285
300
  },
301
+ ...(rewardDetail
302
+ ? rewardDetail.map((x) => ({
303
+ type: 'dapp',
304
+ data: x,
305
+ }))
306
+ : []),
286
307
  ].filter(Boolean),
287
308
  // @ts-ignore
288
309
  actions: [
@@ -78,6 +78,9 @@ export class SubscriptionRenewFailedEmailTemplate
78
78
  throw new Error(`Subscription not found: ${this.options.invoice.subscription_id}`);
79
79
  }
80
80
 
81
+ if (subscription.isImmutable()) {
82
+ throw new Error(`Cannot renew an immutable subscription: ${subscription.id}`);
83
+ }
81
84
  const customer = await Customer.findByPk(subscription.customer_id);
82
85
  if (!customer) {
83
86
  throw new Error(`Customer not found: ${subscription.customer_id}`);
@@ -188,7 +191,6 @@ export class SubscriptionRenewFailedEmailTemplate
188
191
  body: `${translate('notification.subscriptionRenewFailed.body', locale, {
189
192
  at,
190
193
  productName,
191
- reason: `<span style="color: red;">${reason}</span>`,
192
194
  })}`,
193
195
  // @ts-expect-error
194
196
  attachments: [
@@ -240,6 +242,22 @@ export class SubscriptionRenewFailedEmailTemplate
240
242
  text: paymentInfo,
241
243
  },
242
244
  },
245
+ {
246
+ type: 'text',
247
+ data: {
248
+ type: 'plain',
249
+ color: '#9397A1',
250
+ text: translate('notification.common.failReason', locale),
251
+ },
252
+ },
253
+ {
254
+ type: 'text',
255
+ data: {
256
+ type: 'plain',
257
+ color: '#FF0000',
258
+ text: reason,
259
+ },
260
+ },
243
261
  {
244
262
  type: 'text',
245
263
  data: {
@@ -1,9 +1,10 @@
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
  import prettyMsI18n from 'pretty-ms-i18n';
5
+ import isEmpty from 'lodash/isEmpty';
6
6
 
7
+ import { getPaymentAmountForCycleSubscription } from '@api/libs/payment';
7
8
  import { getUserLocale } from '../../../integrations/blocklet/notification';
8
9
  import { translate } from '../../../locales';
9
10
  import {
@@ -16,7 +17,7 @@ import {
16
17
  } from '../../../store/models';
17
18
  import { Invoice } from '../../../store/models/invoice';
18
19
  import { PaymentCurrency } from '../../../store/models/payment-currency';
19
- import { getCustomerInvoicePageUrl } from '../../invoice';
20
+ import { getCustomerInvoicePageUrl, getOneTimeProductInfo } from '../../invoice';
20
21
  import { getMainProductName } from '../../product';
21
22
  import { getCustomerSubscriptionPageUrl } from '../../subscription';
22
23
  import { formatTime, getPrettyMsI18nLocale } from '../../time';
@@ -27,6 +28,12 @@ export interface SubscriptionSucceededEmailTemplateOptions {
27
28
  subscriptionId: string;
28
29
  }
29
30
 
31
+ interface OneTimeProductInfo {
32
+ paymentInfo: string;
33
+ productName: string;
34
+ quantity: number;
35
+ }
36
+
30
37
  interface SubscriptionSucceededEmailTemplateContext {
31
38
  locale: string;
32
39
  productName: string;
@@ -41,6 +48,7 @@ interface SubscriptionSucceededEmailTemplateContext {
41
48
  viewSubscriptionLink: string;
42
49
  viewInvoiceLink: string;
43
50
  viewTxHashLink: string | undefined;
51
+ oneTimeProductInfo?: Array<OneTimeProductInfo>;
44
52
  }
45
53
 
46
54
  export class SubscriptionSucceededEmailTemplate
@@ -90,12 +98,7 @@ export class SubscriptionSucceededEmailTemplate
90
98
  { timeout: 1000 * 10, interval: 1000 }
91
99
  );
92
100
 
93
- const invoice = (await Invoice.findOne({
94
- where: {
95
- subscription_id: subscription.id,
96
- },
97
- order: [['created_at', 'ASC']],
98
- })) as Invoice;
101
+ const invoice = (await Invoice.findByPk(subscription.latest_invoice_id)) as Invoice;
99
102
  const paymentCurrency = (await PaymentCurrency.findOne({
100
103
  where: {
101
104
  id: subscription.currency_id,
@@ -113,7 +116,10 @@ export class SubscriptionSucceededEmailTemplate
113
116
  const productName = await getMainProductName(subscription.id);
114
117
  const at: string = formatTime(subscription.created_at);
115
118
 
116
- const paymentInfo: string = `${fromUnitToToken(invoice.total, paymentCurrency.decimal)} ${paymentCurrency.symbol}`;
119
+ const oneTimeProductInfo = await getOneTimeProductInfo(subscription.latest_invoice_id as string, paymentCurrency);
120
+ const paymentAmount = await getPaymentAmountForCycleSubscription(subscription, paymentCurrency);
121
+
122
+ const paymentInfo: string = `${paymentAmount} ${paymentCurrency.symbol}`;
117
123
  const hasNft: boolean = checkoutSession?.nft_mint_status === 'minted';
118
124
  const nftMintItem: NftMintItem | undefined = hasNft
119
125
  ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']
@@ -169,9 +175,76 @@ export class SubscriptionSucceededEmailTemplate
169
175
  viewSubscriptionLink,
170
176
  viewInvoiceLink,
171
177
  viewTxHashLink,
178
+ oneTimeProductInfo,
172
179
  };
173
180
  }
174
181
 
182
+ getOneTimeProductTemplate(oneTimeProductInfo: Array<OneTimeProductInfo>, locale: string = 'en') {
183
+ return [
184
+ {
185
+ type: 'divider',
186
+ },
187
+ {
188
+ type: 'text',
189
+ data: {
190
+ type: 'plain',
191
+ text: translate('notification.common.expandPayment', locale),
192
+ },
193
+ },
194
+ {
195
+ type: 'section',
196
+ fields: (oneTimeProductInfo || []).flatMap((x: any) => [
197
+ {
198
+ type: 'text',
199
+ data: {
200
+ type: 'plain',
201
+ color: '#9397A1',
202
+ text: translate('notification.common.product', locale),
203
+ },
204
+ },
205
+ {
206
+ type: 'text',
207
+ data: {
208
+ type: 'plain',
209
+ text: x?.productName,
210
+ },
211
+ },
212
+ {
213
+ type: 'text',
214
+ data: {
215
+ type: 'plain',
216
+ color: '#9397A1',
217
+ text: translate('notification.common.paymentAmount', locale),
218
+ },
219
+ },
220
+ {
221
+ type: 'text',
222
+ data: {
223
+ type: 'plain',
224
+ text: x?.paymentInfo,
225
+ },
226
+ },
227
+ {
228
+ type: 'text',
229
+ data: {
230
+ type: 'plain',
231
+ color: '#9397A1',
232
+ text: translate('notification.common.paymentQuantity', locale),
233
+ },
234
+ },
235
+ {
236
+ type: 'text',
237
+ data: {
238
+ type: 'plain',
239
+ text: translate('notification.common.qty', locale, {
240
+ count: x.quantity,
241
+ }),
242
+ },
243
+ },
244
+ ]),
245
+ },
246
+ ];
247
+ }
175
248
  async getTemplate(): Promise<BaseEmailTemplateType> {
176
249
  const {
177
250
  locale,
@@ -187,7 +260,9 @@ export class SubscriptionSucceededEmailTemplate
187
260
  viewSubscriptionLink,
188
261
  viewInvoiceLink,
189
262
  viewTxHashLink,
263
+ oneTimeProductInfo,
190
264
  } = await this.getContext();
265
+ const hasOneTimeProduct = !isEmpty(oneTimeProductInfo);
191
266
 
192
267
  const template: BaseEmailTemplateType = {
193
268
  title: `${translate('notification.subscriptionSucceed.title', locale, {
@@ -207,6 +282,13 @@ export class SubscriptionSucceededEmailTemplate
207
282
  did: nftMintItem.address,
208
283
  },
209
284
  },
285
+ hasOneTimeProduct && {
286
+ type: 'text',
287
+ data: {
288
+ type: 'plain',
289
+ text: translate('notification.common.subscribeProduct', locale),
290
+ },
291
+ },
210
292
  {
211
293
  type: 'section',
212
294
  fields: [
@@ -272,6 +354,9 @@ export class SubscriptionSucceededEmailTemplate
272
354
  },
273
355
  ].filter(Boolean),
274
356
  },
357
+ ...(hasOneTimeProduct
358
+ ? this.getOneTimeProductTemplate(oneTimeProductInfo as Array<OneTimeProductInfo>, locale)
359
+ : []),
275
360
  ].filter(Boolean),
276
361
  // @ts-ignore
277
362
  actions: [
@@ -4,11 +4,12 @@ import { fromUnitToToken, toDid } from '@ocap/util';
4
4
  import pWaitFor from 'p-wait-for';
5
5
  import prettyMsI18n from 'pretty-ms-i18n';
6
6
 
7
+ import isEmpty from 'lodash/isEmpty';
7
8
  import { getUserLocale } from '../../../integrations/blocklet/notification';
8
9
  import { translate } from '../../../locales';
9
10
  import { CheckoutSession, Customer, Invoice, NftMintItem, PaymentMethod, Subscription } from '../../../store/models';
10
11
  import { PaymentCurrency } from '../../../store/models/payment-currency';
11
- import { getCustomerInvoicePageUrl } from '../../invoice';
12
+ import { getCustomerInvoicePageUrl, getOneTimeProductInfo } from '../../invoice';
12
13
  import { getMainProductName } from '../../product';
13
14
  import { getCustomerSubscriptionPageUrl } from '../../subscription';
14
15
  import { formatTime, getPrettyMsI18nLocale } from '../../time';
@@ -18,6 +19,12 @@ export interface SubscriptionTrialStartEmailTemplateOptions {
18
19
  subscriptionId: string;
19
20
  }
20
21
 
22
+ interface OneTimeProductInfo {
23
+ paymentInfo: string;
24
+ productName: string;
25
+ quantity: number;
26
+ }
27
+
21
28
  interface SubscriptionTrialStartEmailTemplateContext {
22
29
  locale: string;
23
30
  productName: string;
@@ -33,6 +40,7 @@ interface SubscriptionTrialStartEmailTemplateContext {
33
40
 
34
41
  viewSubscriptionLink: string;
35
42
  viewInvoiceLink: string;
43
+ oneTimeProductInfo?: Array<OneTimeProductInfo>;
36
44
  }
37
45
 
38
46
  export class SubscriptionTrialStartEmailTemplate
@@ -81,6 +89,8 @@ export class SubscriptionTrialStartEmailTemplate
81
89
  },
82
90
  })) as PaymentCurrency;
83
91
 
92
+ const oneTimeProductInfo = await getOneTimeProductInfo(subscription.latest_invoice_id as string, paymentCurrency);
93
+
84
94
  const checkoutSession = await CheckoutSession.findOne({
85
95
  where: {
86
96
  subscription_id: subscription.id,
@@ -99,7 +109,7 @@ export class SubscriptionTrialStartEmailTemplate
99
109
  const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
100
110
  const chainHost: string | undefined =
101
111
  paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as 'arcblock' | 'ethereum']?.api_host;
102
- const paymentInfo: string = `${fromUnitToToken(invoice?.total || '0', paymentCurrency.decimal)} ${paymentCurrency.symbol}`;
112
+ const paymentInfo: string = `${fromUnitToToken('0', paymentCurrency.decimal)} ${paymentCurrency.symbol}`;
103
113
  const currentPeriodStart: string = formatTime((subscription.trial_start as number) * 1000);
104
114
  const currentPeriodEnd: string = formatTime((subscription.trial_end as number) * 1000);
105
115
  const duration: string = prettyMsI18n(
@@ -135,9 +145,77 @@ export class SubscriptionTrialStartEmailTemplate
135
145
 
136
146
  viewSubscriptionLink,
137
147
  viewInvoiceLink,
148
+ oneTimeProductInfo,
138
149
  };
139
150
  }
140
151
 
152
+ getOneTimeProductTemplate(oneTimeProductInfo: Array<OneTimeProductInfo>, locale: string = 'en') {
153
+ return [
154
+ {
155
+ type: 'divider',
156
+ },
157
+ {
158
+ type: 'text',
159
+ data: {
160
+ type: 'plain',
161
+ text: translate('notification.common.expandPayment', locale),
162
+ },
163
+ },
164
+ {
165
+ type: 'section',
166
+ fields: (oneTimeProductInfo || []).flatMap((x: any) => [
167
+ {
168
+ type: 'text',
169
+ data: {
170
+ type: 'plain',
171
+ color: '#9397A1',
172
+ text: translate('notification.common.product', locale),
173
+ },
174
+ },
175
+ {
176
+ type: 'text',
177
+ data: {
178
+ type: 'plain',
179
+ text: x?.productName,
180
+ },
181
+ },
182
+ {
183
+ type: 'text',
184
+ data: {
185
+ type: 'plain',
186
+ color: '#9397A1',
187
+ text: translate('notification.common.paymentAmount', locale),
188
+ },
189
+ },
190
+ {
191
+ type: 'text',
192
+ data: {
193
+ type: 'plain',
194
+ text: x?.paymentInfo,
195
+ },
196
+ },
197
+ {
198
+ type: 'text',
199
+ data: {
200
+ type: 'plain',
201
+ color: '#9397A1',
202
+ text: translate('notification.common.paymentQuantity', locale),
203
+ },
204
+ },
205
+ {
206
+ type: 'text',
207
+ data: {
208
+ type: 'plain',
209
+ text: translate('notification.common.qty', locale, {
210
+ count: x.quantity,
211
+ }),
212
+ },
213
+ },
214
+ ]),
215
+ },
216
+ ];
217
+ }
218
+
141
219
  async getTemplate(): Promise<BaseEmailTemplateType> {
142
220
  const {
143
221
  locale,
@@ -147,15 +225,16 @@ export class SubscriptionTrialStartEmailTemplate
147
225
  nftMintItem,
148
226
  chainHost,
149
227
  userDid,
150
- paymentInfo,
151
228
  currentPeriodStart,
152
229
  currentPeriodEnd,
153
230
  duration,
154
231
 
155
232
  viewSubscriptionLink,
156
233
  viewInvoiceLink,
234
+ oneTimeProductInfo,
157
235
  } = await this.getContext();
158
236
 
237
+ const hasOneTimeProduct = !isEmpty(oneTimeProductInfo);
159
238
  const template: BaseEmailTemplateType = {
160
239
  title: `${translate('notification.subscriptionTrialStart.title', locale, {
161
240
  productName,
@@ -175,6 +254,13 @@ export class SubscriptionTrialStartEmailTemplate
175
254
  did: nftMintItem.address,
176
255
  },
177
256
  },
257
+ hasOneTimeProduct && {
258
+ type: 'text',
259
+ data: {
260
+ type: 'plain',
261
+ text: translate('notification.common.trialProduct', locale),
262
+ },
263
+ },
178
264
  {
179
265
  type: 'section',
180
266
  fields: [
@@ -208,21 +294,6 @@ export class SubscriptionTrialStartEmailTemplate
208
294
  text: productName,
209
295
  },
210
296
  },
211
- {
212
- type: 'text',
213
- data: {
214
- type: 'plain',
215
- color: '#9397A1',
216
- text: translate('notification.common.paymentAmount', locale),
217
- },
218
- },
219
- {
220
- type: 'text',
221
- data: {
222
- type: 'plain',
223
- text: paymentInfo,
224
- },
225
- },
226
297
  {
227
298
  type: 'text',
228
299
  data: {
@@ -255,6 +326,9 @@ export class SubscriptionTrialStartEmailTemplate
255
326
  },
256
327
  ].filter(Boolean),
257
328
  },
329
+ ...(hasOneTimeProduct
330
+ ? this.getOneTimeProductTemplate(oneTimeProductInfo as Array<OneTimeProductInfo>, locale)
331
+ : []),
258
332
  ].filter(Boolean),
259
333
  // @ts-ignore
260
334
  actions: [