payment-kit 1.19.0 → 1.19.2

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 (139) hide show
  1. package/api/src/crons/index.ts +8 -0
  2. package/api/src/index.ts +4 -0
  3. package/api/src/libs/credit-grant.ts +146 -0
  4. package/api/src/libs/env.ts +1 -0
  5. package/api/src/libs/invoice.ts +4 -3
  6. package/api/src/libs/notification/template/base.ts +388 -2
  7. package/api/src/libs/notification/template/customer-credit-grant-granted.ts +149 -0
  8. package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +151 -0
  9. package/api/src/libs/notification/template/customer-credit-insufficient.ts +254 -0
  10. package/api/src/libs/notification/template/subscription-canceled.ts +193 -202
  11. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +215 -237
  12. package/api/src/libs/notification/template/subscription-renewed.ts +130 -200
  13. package/api/src/libs/notification/template/subscription-succeeded.ts +100 -202
  14. package/api/src/libs/notification/template/subscription-trial-start.ts +142 -188
  15. package/api/src/libs/notification/template/subscription-trial-will-end.ts +146 -174
  16. package/api/src/libs/notification/template/subscription-upgraded.ts +96 -192
  17. package/api/src/libs/notification/template/subscription-will-canceled.ts +94 -135
  18. package/api/src/libs/notification/template/subscription-will-renew.ts +220 -245
  19. package/api/src/libs/payment.ts +69 -0
  20. package/api/src/libs/queue/index.ts +3 -2
  21. package/api/src/libs/session.ts +8 -0
  22. package/api/src/libs/subscription.ts +74 -3
  23. package/api/src/libs/util.ts +3 -1
  24. package/api/src/libs/ws.ts +23 -1
  25. package/api/src/locales/en.ts +33 -0
  26. package/api/src/locales/zh.ts +31 -0
  27. package/api/src/queues/credit-consume.ts +728 -0
  28. package/api/src/queues/credit-grant.ts +572 -0
  29. package/api/src/queues/notification.ts +173 -128
  30. package/api/src/queues/payment.ts +210 -122
  31. package/api/src/queues/subscription.ts +179 -0
  32. package/api/src/routes/checkout-sessions.ts +157 -9
  33. package/api/src/routes/connect/shared.ts +3 -2
  34. package/api/src/routes/credit-grants.ts +241 -0
  35. package/api/src/routes/credit-transactions.ts +208 -0
  36. package/api/src/routes/customers.ts +34 -5
  37. package/api/src/routes/index.ts +8 -0
  38. package/api/src/routes/meter-events.ts +347 -0
  39. package/api/src/routes/meters.ts +219 -0
  40. package/api/src/routes/payment-currencies.ts +20 -2
  41. package/api/src/routes/payment-links.ts +1 -1
  42. package/api/src/routes/payment-methods.ts +14 -2
  43. package/api/src/routes/prices.ts +43 -0
  44. package/api/src/routes/pricing-table.ts +13 -7
  45. package/api/src/routes/products.ts +63 -4
  46. package/api/src/routes/settings.ts +1 -1
  47. package/api/src/routes/subscriptions.ts +4 -0
  48. package/api/src/routes/webhook-endpoints.ts +0 -3
  49. package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
  50. package/api/src/store/models/credit-grant.ts +486 -0
  51. package/api/src/store/models/credit-transaction.ts +268 -0
  52. package/api/src/store/models/customer.ts +8 -0
  53. package/api/src/store/models/index.ts +52 -1
  54. package/api/src/store/models/meter-event.ts +423 -0
  55. package/api/src/store/models/meter.ts +176 -0
  56. package/api/src/store/models/payment-currency.ts +66 -14
  57. package/api/src/store/models/price.ts +6 -0
  58. package/api/src/store/models/product.ts +2 -2
  59. package/api/src/store/models/subscription.ts +24 -0
  60. package/api/src/store/models/types.ts +28 -2
  61. package/api/tests/libs/subscription.spec.ts +53 -0
  62. package/blocklet.yml +9 -1
  63. package/package.json +4 -4
  64. package/scripts/sdk.js +233 -1
  65. package/src/app.tsx +10 -0
  66. package/src/components/collapse.tsx +11 -1
  67. package/src/components/conditional-section.tsx +87 -0
  68. package/src/components/customer/credit-grant-item-list.tsx +99 -0
  69. package/src/components/customer/credit-overview.tsx +246 -0
  70. package/src/components/customer/form.tsx +7 -3
  71. package/src/components/invoice/list.tsx +19 -1
  72. package/src/components/metadata/form.tsx +287 -91
  73. package/src/components/meter/actions.tsx +101 -0
  74. package/src/components/meter/add-usage-dialog.tsx +239 -0
  75. package/src/components/meter/events-list.tsx +657 -0
  76. package/src/components/meter/form.tsx +245 -0
  77. package/src/components/meter/products.tsx +264 -0
  78. package/src/components/meter/usage-guide.tsx +174 -0
  79. package/src/components/payment-currency/form.tsx +2 -0
  80. package/src/components/payment-intent/list.tsx +19 -1
  81. package/src/components/payment-link/item.tsx +2 -2
  82. package/src/components/payment-link/preview.tsx +1 -1
  83. package/src/components/payment-link/product-select.tsx +52 -12
  84. package/src/components/payment-method/arcblock.tsx +2 -0
  85. package/src/components/payment-method/base.tsx +2 -0
  86. package/src/components/payment-method/bitcoin.tsx +2 -0
  87. package/src/components/payment-method/ethereum.tsx +2 -0
  88. package/src/components/payment-method/stripe.tsx +2 -0
  89. package/src/components/payouts/list.tsx +19 -1
  90. package/src/components/payouts/portal/list.tsx +6 -11
  91. package/src/components/price/currency-select.tsx +56 -32
  92. package/src/components/price/form.tsx +912 -407
  93. package/src/components/pricing-table/preview.tsx +1 -1
  94. package/src/components/product/add-price.tsx +9 -7
  95. package/src/components/product/create.tsx +7 -4
  96. package/src/components/product/edit-price.tsx +21 -12
  97. package/src/components/product/features.tsx +17 -7
  98. package/src/components/product/form.tsx +100 -90
  99. package/src/components/refund/list.tsx +19 -1
  100. package/src/components/section/header.tsx +5 -18
  101. package/src/components/subscription/items/index.tsx +1 -1
  102. package/src/components/subscription/metrics.tsx +37 -5
  103. package/src/components/subscription/portal/actions.tsx +2 -1
  104. package/src/contexts/products.tsx +26 -9
  105. package/src/hooks/subscription.ts +34 -0
  106. package/src/libs/meter-utils.ts +196 -0
  107. package/src/libs/util.ts +4 -0
  108. package/src/locales/en.tsx +389 -5
  109. package/src/locales/zh.tsx +368 -1
  110. package/src/pages/admin/billing/index.tsx +61 -33
  111. package/src/pages/admin/billing/invoices/detail.tsx +1 -1
  112. package/src/pages/admin/billing/meters/create.tsx +60 -0
  113. package/src/pages/admin/billing/meters/detail.tsx +435 -0
  114. package/src/pages/admin/billing/meters/index.tsx +210 -0
  115. package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
  116. package/src/pages/admin/billing/subscriptions/detail.tsx +47 -14
  117. package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
  118. package/src/pages/admin/customers/customers/detail.tsx +14 -10
  119. package/src/pages/admin/customers/index.tsx +5 -0
  120. package/src/pages/admin/developers/events/detail.tsx +1 -1
  121. package/src/pages/admin/developers/index.tsx +1 -1
  122. package/src/pages/admin/payments/intents/detail.tsx +1 -1
  123. package/src/pages/admin/payments/payouts/detail.tsx +1 -1
  124. package/src/pages/admin/payments/refunds/detail.tsx +1 -1
  125. package/src/pages/admin/products/index.tsx +3 -2
  126. package/src/pages/admin/products/links/detail.tsx +1 -1
  127. package/src/pages/admin/products/prices/actions.tsx +16 -4
  128. package/src/pages/admin/products/prices/detail.tsx +30 -3
  129. package/src/pages/admin/products/prices/list.tsx +8 -1
  130. package/src/pages/admin/products/pricing-tables/detail.tsx +1 -1
  131. package/src/pages/admin/products/products/create.tsx +233 -57
  132. package/src/pages/admin/products/products/detail.tsx +2 -1
  133. package/src/pages/admin/settings/payment-methods/index.tsx +3 -0
  134. package/src/pages/customer/credit-grant/detail.tsx +308 -0
  135. package/src/pages/customer/index.tsx +44 -9
  136. package/src/pages/customer/recharge/account.tsx +5 -5
  137. package/src/pages/customer/subscription/change-payment.tsx +4 -2
  138. package/src/pages/customer/subscription/detail.tsx +48 -14
  139. package/src/pages/customer/subscription/embed.tsx +1 -1
@@ -1,18 +1,14 @@
1
1
  /* eslint-disable @typescript-eslint/brace-style */
2
2
  /* eslint-disable @typescript-eslint/indent */
3
- import prettyMsI18n from 'pretty-ms-i18n';
4
-
5
3
  import { Op } from 'sequelize';
6
- import { getUserLocale } from '../../../integrations/blocklet/notification';
7
4
  import { translate } from '../../../locales';
8
- import { Customer, PaymentMethod, Refund, Subscription } from '../../../store/models';
5
+ import { PaymentMethod, Refund } from '../../../store/models';
9
6
  import { Invoice } from '../../../store/models/invoice';
10
7
  import { PaymentCurrency } from '../../../store/models/payment-currency';
11
- import { getMainProductName } from '../../product';
12
- import { getCustomerSubscriptionPageUrl, getSubscriptionStakeCancellation } from '../../subscription';
13
- import { formatTime, getPrettyMsI18nLocale } from '../../time';
14
- import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
15
- import { formatCurrencyInfo, getSubscriptionNotificationCustomActions } from '../../util';
8
+ import { getSubscriptionStakeCancellation } from '../../subscription';
9
+ import { formatTime } from '../../time';
10
+ import { formatCurrencyInfo } from '../../util';
11
+ import { BaseSubscriptionEmailTemplate, BaseEmailTemplateType } from './base';
16
12
 
17
13
  export interface SubscriptionCanceledEmailTemplateOptions {
18
14
  subscriptionId: string;
@@ -22,7 +18,6 @@ interface SubscriptionCanceledEmailTemplateContext {
22
18
  locale: string;
23
19
  productName: string;
24
20
  at: string;
25
-
26
21
  chainHost: string | undefined;
27
22
  userDid: string;
28
23
  paymentInfo: string;
@@ -30,263 +25,259 @@ interface SubscriptionCanceledEmailTemplateContext {
30
25
  currentPeriodEnd: string;
31
26
  duration: string;
32
27
  cancellationReason: string;
33
-
34
28
  viewSubscriptionLink: string;
35
29
  customActions: any[];
30
+ isCreditSubscription: boolean;
36
31
  }
37
32
 
38
- export class SubscriptionCanceledEmailTemplate implements BaseEmailTemplate<SubscriptionCanceledEmailTemplateContext> {
33
+ export class SubscriptionCanceledEmailTemplate extends BaseSubscriptionEmailTemplate<SubscriptionCanceledEmailTemplateContext> {
39
34
  options: SubscriptionCanceledEmailTemplateOptions;
40
35
 
41
36
  constructor(options: SubscriptionCanceledEmailTemplateOptions) {
37
+ super();
42
38
  this.options = options;
43
39
  }
44
40
 
45
41
  async getContext(): Promise<SubscriptionCanceledEmailTemplateContext> {
46
- const subscription: Subscription | null = await Subscription.findByPk(this.options.subscriptionId);
47
- if (!subscription) {
48
- throw new Error(`Subscription(${this.options.subscriptionId}) not found`);
49
- }
42
+ // 获取基础订阅数据
43
+ const basicData = await this.getSubscriptionBasicData(this.options.subscriptionId);
44
+ const { subscription, userDid, locale, productName, paymentCurrency, isCreditSubscription } = basicData;
45
+
50
46
  if (subscription.status !== 'canceled') {
51
47
  throw new Error(`Subscription(${this.options.subscriptionId}) status(${subscription.status}) must be canceled`);
52
48
  }
53
49
 
54
- const customer = await Customer.findByPk(subscription.customer_id);
55
- if (!customer) {
56
- throw new Error(`Customer(${subscription.customer_id}) not found`);
57
- }
50
+ // 获取发票和支付信息
51
+ let invoice = null;
58
52
 
59
- const invoice = await Invoice.findOne({
60
- where: {
61
- id: subscription.latest_invoice_id,
62
- },
63
- include: [{ model: PaymentCurrency, as: 'paymentCurrency' }],
64
- });
65
- if (!invoice) {
66
- throw new Error(`Invoice(${subscription.latest_invoice_id}) not found`);
53
+ let paymentMethod = await PaymentMethod.findByPk(subscription.default_payment_method_id);
54
+ if (!paymentMethod) {
55
+ throw new Error(`PaymentMethod not found for subscription: ${subscription.id}`);
67
56
  }
68
- const paymentCurrency = (await PaymentCurrency.findOne({
69
- where: {
70
- id: subscription.currency_id,
71
- },
72
- })) as PaymentCurrency;
73
-
74
- const userDid: string = customer.did;
75
- const locale = await getUserLocale(userDid);
76
- const productName = await getMainProductName(subscription.id);
77
- const at: string = formatTime(subscription.canceled_at * 1000);
78
- const currentPeriodStart: string = formatTime(invoice.period_start * 1000);
79
- const currentPeriodEnd: string = formatTime(invoice.period_end * 1000);
80
- const duration: string = prettyMsI18n(
81
- new Date(currentPeriodEnd).getTime() - new Date(currentPeriodStart).getTime(),
82
- {
83
- locale: getPrettyMsI18nLocale(locale),
84
- }
85
- );
86
- const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
87
-
88
- // @ts-ignore
89
- const paymentInfo: string = formatCurrencyInfo(invoice.total, invoice?.paymentCurrency, paymentMethod);
90
57
 
91
- const customerCancelRequest = subscription.cancelation_details?.reason === 'cancellation_requested';
92
- let cancellationReason = '';
93
- const { stakeEnough, stakeReturn, stakeSlash, hasStake } = await getSubscriptionStakeCancellation(
94
- subscription,
95
- paymentMethod!,
96
- paymentCurrency
97
- );
98
- if (customerCancelRequest) {
99
- // cancel request from customer
100
- cancellationReason = translate('notification.subscriptionCanceled.customerCanceled', locale);
101
- if (stakeReturn && hasStake && stakeEnough) {
102
- cancellationReason = translate('notification.subscriptionCanceled.customerCanceledAndStakeReturned', locale);
103
- }
104
- } else {
105
- // default admin cancel
106
- const refund = await Refund.findOne({
107
- where: {
108
- subscription_id: subscription.id,
109
- type: 'refund',
110
- status: {
111
- [Op.not]: 'canceled',
112
- },
113
- },
58
+ if (!isCreditSubscription && subscription.latest_invoice_id) {
59
+ invoice = await Invoice.findOne({
60
+ where: { id: subscription.latest_invoice_id },
61
+ include: [{ model: PaymentCurrency, as: 'paymentCurrency' }],
114
62
  });
115
- const conditions = [
116
- {
117
- condition: refund && stakeSlash,
118
- key: 'notification.subscriptionCanceled.adminCanceledAndRefundedAndStakeSlashed',
119
- },
120
- {
121
- condition: refund && stakeReturn,
122
- key: 'notification.subscriptionCanceled.adminCanceledAndRefundedAndStakeReturned',
123
- },
124
- { condition: refund, key: 'notification.subscriptionCanceled.adminCanceledAndRefunded' },
125
- { condition: stakeReturn, key: 'notification.subscriptionCanceled.adminCanceledAndStakeReturned' },
126
- ];
127
- const matchedCondition = conditions.find((item) => item.condition);
128
- if (matchedCondition) {
129
- cancellationReason = translate(matchedCondition.key, locale);
63
+ if (!invoice) {
64
+ throw new Error(`Invoice(${subscription.latest_invoice_id}) not found`);
65
+ }
66
+ if (invoice.default_payment_method_id !== paymentMethod.id) {
67
+ paymentMethod = await PaymentMethod.findByPk(invoice.default_payment_method_id);
130
68
  }
131
69
  }
132
70
 
133
- if (subscription.cancelation_details?.reason === 'payment_failed') {
134
- cancellationReason = translate('notification.subscriptionCanceled.paymentFailed', locale);
135
- }
136
-
137
- if (subscription.cancelation_details?.reason === 'stake_revoked') {
138
- cancellationReason = translate('notification.subscriptionCanceled.stakeRevoked', locale);
139
- }
140
-
141
- // @ts-expect-error
142
- const chainHost: string | undefined = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
143
- const viewSubscriptionLink = getCustomerSubscriptionPageUrl({
144
- subscriptionId: subscription.id,
71
+ // 获取链接
72
+ const links = this.generateSubscriptionLinks(
73
+ subscription,
145
74
  locale,
146
75
  userDid,
147
- });
148
-
149
- const customActions = getSubscriptionNotificationCustomActions(
150
- subscription,
151
76
  'customer.subscription.deleted',
152
- locale
77
+ invoice?.id,
78
+ isCreditSubscription
153
79
  );
154
80
 
81
+ const at = formatTime(subscription.canceled_at * 1000);
82
+
83
+ const periodInfo = isCreditSubscription
84
+ ? this.formatSubscriptionPeriod(subscription.current_period_start, subscription.current_period_end, locale)
85
+ : this.formatSubscriptionPeriod(invoice!.period_start, invoice!.period_end, locale);
86
+
87
+ const paymentInfo = isCreditSubscription ? '' : formatCurrencyInfo(invoice!.total, paymentCurrency, paymentMethod);
88
+
89
+ const cancellationReason = await this.getCancellationReason(subscription, paymentMethod!, paymentCurrency, locale);
90
+
91
+ // @ts-expect-error
92
+ const chainHost = paymentMethod?.settings?.[paymentMethod.type]?.api_host;
93
+
155
94
  return {
156
95
  locale,
157
96
  productName,
158
97
  at,
159
-
160
98
  userDid,
161
99
  chainHost,
162
100
  paymentInfo,
163
- currentPeriodStart,
164
- currentPeriodEnd,
165
- duration,
101
+ currentPeriodStart: periodInfo.currentPeriodStart,
102
+ currentPeriodEnd: periodInfo.currentPeriodEnd,
103
+ duration: periodInfo.duration,
166
104
  cancellationReason,
167
-
168
- viewSubscriptionLink,
169
- customActions,
105
+ viewSubscriptionLink: links.viewSubscriptionLink,
106
+ customActions: links.customActions,
107
+ isCreditSubscription,
170
108
  };
171
109
  }
172
110
 
111
+ private async getCancellationReason(
112
+ subscription: any,
113
+ paymentMethod: PaymentMethod,
114
+ paymentCurrency: any,
115
+ locale: string
116
+ ): Promise<string> {
117
+ const customerCancelRequest = subscription.cancelation_details?.reason === 'cancellation_requested';
118
+
119
+ // 特殊取消原因处理
120
+ if (subscription.cancelation_details?.reason === 'payment_failed') {
121
+ return translate('notification.subscriptionCanceled.paymentFailed', locale);
122
+ }
123
+
124
+ if (subscription.cancelation_details?.reason === 'stake_revoked') {
125
+ return translate('notification.subscriptionCanceled.stakeRevoked', locale);
126
+ }
127
+
128
+ const { stakeEnough, stakeReturn, stakeSlash, hasStake } = await getSubscriptionStakeCancellation(
129
+ subscription,
130
+ paymentMethod,
131
+ paymentCurrency
132
+ );
133
+
134
+ if (customerCancelRequest) {
135
+ // 客户主动取消
136
+ if (stakeReturn && hasStake && stakeEnough) {
137
+ return translate('notification.subscriptionCanceled.customerCanceledAndStakeReturned', locale);
138
+ }
139
+ return translate('notification.subscriptionCanceled.customerCanceled', locale);
140
+ }
141
+
142
+ // 管理员取消 - 检查退款情况
143
+ const refund = await Refund.findOne({
144
+ where: {
145
+ subscription_id: subscription.id,
146
+ type: 'refund',
147
+ status: { [Op.not]: 'canceled' },
148
+ },
149
+ });
150
+
151
+ const conditions = [
152
+ {
153
+ condition: refund && stakeSlash,
154
+ key: 'notification.subscriptionCanceled.adminCanceledAndRefundedAndStakeSlashed',
155
+ },
156
+ {
157
+ condition: refund && stakeReturn,
158
+ key: 'notification.subscriptionCanceled.adminCanceledAndRefundedAndStakeReturned',
159
+ },
160
+ { condition: refund, key: 'notification.subscriptionCanceled.adminCanceledAndRefunded' },
161
+ { condition: stakeReturn, key: 'notification.subscriptionCanceled.adminCanceledAndStakeReturned' },
162
+ ];
163
+
164
+ const matchedCondition = conditions.find((item) => item.condition);
165
+ return matchedCondition ? translate(matchedCondition.key, locale) : '';
166
+ }
167
+
173
168
  async getTemplate(): Promise<BaseEmailTemplateType> {
169
+ const context = await this.getContext();
174
170
  const {
175
171
  locale,
176
172
  productName,
177
173
  at,
178
-
179
174
  userDid,
180
175
  paymentInfo,
181
176
  currentPeriodStart,
182
177
  currentPeriodEnd,
183
178
  duration,
184
179
  cancellationReason,
185
-
186
180
  viewSubscriptionLink,
187
181
  customActions,
188
- } = await this.getContext();
182
+ isCreditSubscription,
183
+ } = context;
184
+
185
+ // 构建字段
186
+ const commonFields = this.buildCommonFields(userDid, productName, locale);
187
+
188
+ // 支付金额字段
189
+ const paymentAmountFields = isCreditSubscription
190
+ ? []
191
+ : [
192
+ {
193
+ type: 'text',
194
+ data: {
195
+ type: 'plain',
196
+ color: '#9397A1',
197
+ text: translate('notification.common.paymentAmount', locale),
198
+ },
199
+ },
200
+ {
201
+ type: 'text',
202
+ data: {
203
+ type: 'plain',
204
+ text: paymentInfo,
205
+ },
206
+ },
207
+ ];
208
+
209
+ // 有效期字段
210
+ const validityPeriodFields = isCreditSubscription
211
+ ? []
212
+ : [
213
+ {
214
+ type: 'text',
215
+ data: {
216
+ type: 'plain',
217
+ color: '#9397A1',
218
+ text: translate('notification.common.validityPeriod', locale),
219
+ },
220
+ },
221
+ {
222
+ type: 'text',
223
+ data: {
224
+ type: 'plain',
225
+ text: `${currentPeriodStart} ~ ${currentPeriodEnd}(${duration})`,
226
+ },
227
+ },
228
+ ];
229
+
230
+ // 取消原因字段
231
+ const cancellationReasonFields = [
232
+ {
233
+ type: 'text',
234
+ data: {
235
+ type: 'plain',
236
+ color: '#9397A1',
237
+ text: translate('notification.common.cancellationReason', locale),
238
+ },
239
+ },
240
+ {
241
+ type: 'text',
242
+ data: {
243
+ type: 'plain',
244
+ text: cancellationReason,
245
+ },
246
+ },
247
+ ];
248
+
249
+ // 构建操作按钮
250
+ const actions = [
251
+ viewSubscriptionLink && {
252
+ name: translate('notification.common.viewSubscription', locale),
253
+ title: translate('notification.common.viewSubscription', locale),
254
+ link: viewSubscriptionLink,
255
+ },
256
+ ...customActions,
257
+ ].filter(Boolean);
189
258
 
190
259
  const template: BaseEmailTemplateType = {
191
- title: `${translate('notification.subscriptionCanceled.title', locale, {
260
+ title: translate('notification.subscriptionCanceled.title', locale, {
192
261
  productName,
193
- })}`,
194
- body: `${translate('notification.subscriptionCanceled.body', locale, {
262
+ }),
263
+ body: translate('notification.subscriptionCanceled.body', locale, {
195
264
  at,
196
265
  productName,
197
- })}`,
266
+ }),
198
267
  // @ts-expect-error
199
268
  attachments: [
200
269
  {
201
270
  type: 'section',
202
271
  fields: [
203
- {
204
- type: 'text',
205
- data: {
206
- type: 'plain',
207
- color: '#9397A1',
208
- text: translate('notification.common.account', locale),
209
- },
210
- },
211
- {
212
- type: 'text',
213
- data: {
214
- type: 'plain',
215
- text: userDid,
216
- },
217
- },
218
- {
219
- type: 'text',
220
- data: {
221
- type: 'plain',
222
- color: '#9397A1',
223
- text: translate('notification.common.product', locale),
224
- },
225
- },
226
- {
227
- type: 'text',
228
- data: {
229
- type: 'plain',
230
- text: productName,
231
- },
232
- },
233
- {
234
- type: 'text',
235
- data: {
236
- type: 'plain',
237
- color: '#9397A1',
238
- text: translate('notification.common.paymentAmount', locale),
239
- },
240
- },
241
- {
242
- type: 'text',
243
- data: {
244
- type: 'plain',
245
- text: paymentInfo,
246
- },
247
- },
248
- {
249
- type: 'text',
250
- data: {
251
- type: 'plain',
252
- color: '#9397A1',
253
- text: translate('notification.common.validityPeriod', locale),
254
- },
255
- },
256
- {
257
- type: 'text',
258
- data: {
259
- type: 'plain',
260
- text: `${currentPeriodStart} ~ ${currentPeriodEnd}(${duration})`,
261
- },
262
- },
263
- {
264
- type: 'text',
265
- data: {
266
- type: 'plain',
267
- color: '#9397A1',
268
- text: translate('notification.common.cancellationReason', locale),
269
- },
270
- },
271
- {
272
- type: 'text',
273
- data: {
274
- type: 'plain',
275
- text: cancellationReason,
276
- },
277
- },
272
+ ...commonFields,
273
+ ...paymentAmountFields,
274
+ ...validityPeriodFields,
275
+ ...cancellationReasonFields,
278
276
  ].filter(Boolean),
279
277
  },
280
278
  ].filter(Boolean),
281
279
  // @ts-ignore
282
- actions: [
283
- viewSubscriptionLink && {
284
- name: translate('notification.common.viewSubscription', locale),
285
- title: translate('notification.common.viewSubscription', locale),
286
- link: viewSubscriptionLink,
287
- },
288
- ...customActions,
289
- ].filter(Boolean),
280
+ actions,
290
281
  };
291
282
 
292
283
  return template;