payment-kit 1.18.56 → 1.19.1

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 (214) hide show
  1. package/.eslintrc.js +6 -0
  2. package/api/src/crons/index.ts +8 -0
  3. package/api/src/index.ts +4 -0
  4. package/api/src/libs/credit-grant.ts +146 -0
  5. package/api/src/libs/env.ts +1 -0
  6. package/api/src/libs/invoice.ts +4 -3
  7. package/api/src/libs/notification/template/base.ts +388 -2
  8. package/api/src/libs/notification/template/customer-credit-grant-granted.ts +149 -0
  9. package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +151 -0
  10. package/api/src/libs/notification/template/customer-credit-insufficient.ts +254 -0
  11. package/api/src/libs/notification/template/subscription-canceled.ts +193 -202
  12. package/api/src/libs/notification/template/subscription-refund-succeeded.ts +215 -237
  13. package/api/src/libs/notification/template/subscription-renewed.ts +130 -200
  14. package/api/src/libs/notification/template/subscription-succeeded.ts +100 -202
  15. package/api/src/libs/notification/template/subscription-trial-start.ts +142 -188
  16. package/api/src/libs/notification/template/subscription-trial-will-end.ts +146 -174
  17. package/api/src/libs/notification/template/subscription-upgraded.ts +96 -192
  18. package/api/src/libs/notification/template/subscription-will-canceled.ts +94 -135
  19. package/api/src/libs/notification/template/subscription-will-renew.ts +220 -245
  20. package/api/src/libs/payment.ts +69 -0
  21. package/api/src/libs/queue/index.ts +3 -2
  22. package/api/src/libs/session.ts +8 -0
  23. package/api/src/libs/subscription.ts +74 -3
  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 +715 -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/index.ts +8 -0
  37. package/api/src/routes/meter-events.ts +347 -0
  38. package/api/src/routes/meters.ts +219 -0
  39. package/api/src/routes/payment-currencies.ts +14 -2
  40. package/api/src/routes/payment-links.ts +1 -1
  41. package/api/src/routes/payment-methods.ts +14 -2
  42. package/api/src/routes/prices.ts +43 -0
  43. package/api/src/routes/pricing-table.ts +13 -7
  44. package/api/src/routes/products.ts +63 -4
  45. package/api/src/routes/settings.ts +1 -1
  46. package/api/src/routes/subscriptions.ts +4 -0
  47. package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
  48. package/api/src/store/models/credit-grant.ts +486 -0
  49. package/api/src/store/models/credit-transaction.ts +268 -0
  50. package/api/src/store/models/customer.ts +8 -0
  51. package/api/src/store/models/index.ts +52 -1
  52. package/api/src/store/models/meter-event.ts +423 -0
  53. package/api/src/store/models/meter.ts +176 -0
  54. package/api/src/store/models/payment-currency.ts +66 -14
  55. package/api/src/store/models/price.ts +6 -0
  56. package/api/src/store/models/product.ts +2 -2
  57. package/api/src/store/models/subscription.ts +24 -0
  58. package/api/src/store/models/types.ts +28 -2
  59. package/api/tests/libs/subscription.spec.ts +53 -0
  60. package/blocklet.yml +9 -1
  61. package/package.json +57 -58
  62. package/scripts/sdk.js +233 -1
  63. package/src/app.tsx +10 -0
  64. package/src/components/actions.tsx +22 -9
  65. package/src/components/balance-list.tsx +40 -12
  66. package/src/components/collapse.tsx +33 -15
  67. package/src/components/copyable.tsx +8 -7
  68. package/src/components/currency.tsx +15 -7
  69. package/src/components/customer/actions.tsx +1 -5
  70. package/src/components/customer/credit-grant-item-list.tsx +99 -0
  71. package/src/components/customer/credit-overview.tsx +233 -0
  72. package/src/components/customer/form.tsx +7 -2
  73. package/src/components/customer/link.tsx +4 -12
  74. package/src/components/customer/notification-preference.tsx +18 -9
  75. package/src/components/customer/overdraft-protection.tsx +112 -41
  76. package/src/components/drawer-form.tsx +42 -18
  77. package/src/components/error.tsx +1 -5
  78. package/src/components/event/list.tsx +9 -10
  79. package/src/components/filter-toolbar.tsx +20 -19
  80. package/src/components/info-card.tsx +32 -18
  81. package/src/components/info-metric.tsx +16 -6
  82. package/src/components/info-row-group.tsx +1 -7
  83. package/src/components/info-row.tsx +30 -24
  84. package/src/components/invoice/action.tsx +1 -7
  85. package/src/components/invoice/list.tsx +34 -26
  86. package/src/components/invoice/recharge.tsx +5 -7
  87. package/src/components/invoice/table.tsx +17 -12
  88. package/src/components/layout/user.tsx +1 -1
  89. package/src/components/metadata/form.tsx +290 -94
  90. package/src/components/metadata/list.tsx +11 -3
  91. package/src/components/meter/actions.tsx +101 -0
  92. package/src/components/meter/add-usage-dialog.tsx +239 -0
  93. package/src/components/meter/events-list.tsx +657 -0
  94. package/src/components/meter/form.tsx +245 -0
  95. package/src/components/meter/products.tsx +264 -0
  96. package/src/components/meter/usage-guide.tsx +174 -0
  97. package/src/components/passport/actions.tsx +9 -4
  98. package/src/components/payment-currency/add.tsx +16 -3
  99. package/src/components/payment-currency/form.tsx +14 -6
  100. package/src/components/payment-intent/actions.tsx +24 -16
  101. package/src/components/payment-intent/list.tsx +30 -9
  102. package/src/components/payment-link/actions.tsx +1 -5
  103. package/src/components/payment-link/after-pay.tsx +4 -2
  104. package/src/components/payment-link/before-pay.tsx +14 -4
  105. package/src/components/payment-link/item.tsx +27 -6
  106. package/src/components/payment-link/preview.tsx +9 -9
  107. package/src/components/payment-link/product-select.tsx +69 -15
  108. package/src/components/payment-method/arcblock.tsx +8 -1
  109. package/src/components/payment-method/base.tsx +8 -1
  110. package/src/components/payment-method/bitcoin.tsx +8 -1
  111. package/src/components/payment-method/ethereum.tsx +8 -1
  112. package/src/components/payment-method/evm-rpc-input.tsx +11 -7
  113. package/src/components/payment-method/form.tsx +2 -7
  114. package/src/components/payment-method/stripe.tsx +2 -0
  115. package/src/components/payouts/actions.tsx +1 -5
  116. package/src/components/payouts/list.tsx +30 -10
  117. package/src/components/payouts/portal/list.tsx +11 -9
  118. package/src/components/price/currency-select.tsx +63 -32
  119. package/src/components/price/form.tsx +895 -370
  120. package/src/components/price/upsell-select.tsx +10 -2
  121. package/src/components/price/upsell.tsx +7 -2
  122. package/src/components/pricing-table/actions.tsx +1 -5
  123. package/src/components/pricing-table/customer-settings.tsx +5 -1
  124. package/src/components/pricing-table/payment-settings.tsx +14 -4
  125. package/src/components/pricing-table/preview.tsx +9 -9
  126. package/src/components/pricing-table/price-item.tsx +6 -1
  127. package/src/components/pricing-table/product-item.tsx +6 -1
  128. package/src/components/pricing-table/product-settings.tsx +17 -4
  129. package/src/components/product/actions.tsx +1 -5
  130. package/src/components/product/add-price.tsx +9 -7
  131. package/src/components/product/create.tsx +8 -9
  132. package/src/components/product/cross-sell-select.tsx +5 -1
  133. package/src/components/product/cross-sell.tsx +7 -2
  134. package/src/components/product/edit-price.tsx +21 -12
  135. package/src/components/product/features.tsx +26 -6
  136. package/src/components/product/form.tsx +115 -72
  137. package/src/components/progress-bar.tsx +1 -1
  138. package/src/components/refund/actions.tsx +1 -7
  139. package/src/components/refund/list.tsx +31 -18
  140. package/src/components/section/header.tsx +12 -14
  141. package/src/components/subscription/actions/cancel.tsx +22 -5
  142. package/src/components/subscription/actions/index.tsx +9 -10
  143. package/src/components/subscription/actions/pause.tsx +32 -6
  144. package/src/components/subscription/actions/slash-stake.tsx +5 -3
  145. package/src/components/subscription/description.tsx +12 -8
  146. package/src/components/subscription/items/index.tsx +31 -16
  147. package/src/components/subscription/items/usage-records.tsx +19 -5
  148. package/src/components/subscription/list.tsx +5 -7
  149. package/src/components/subscription/metrics.tsx +62 -15
  150. package/src/components/subscription/portal/actions.tsx +78 -71
  151. package/src/components/subscription/portal/cancel.tsx +10 -3
  152. package/src/components/subscription/portal/list.tsx +48 -26
  153. package/src/components/uploader.tsx +5 -13
  154. package/src/components/webhook/attempts.tsx +51 -16
  155. package/src/components/webhook/request-info.tsx +8 -6
  156. package/src/contexts/products.tsx +27 -10
  157. package/src/hooks/subscription.ts +34 -0
  158. package/src/libs/meter-utils.ts +196 -0
  159. package/src/libs/util.ts +4 -0
  160. package/src/locales/en.tsx +385 -4
  161. package/src/locales/zh.tsx +364 -0
  162. package/src/pages/admin/billing/index.tsx +61 -33
  163. package/src/pages/admin/billing/invoices/detail.tsx +49 -13
  164. package/src/pages/admin/billing/meters/create.tsx +60 -0
  165. package/src/pages/admin/billing/meters/detail.tsx +435 -0
  166. package/src/pages/admin/billing/meters/index.tsx +210 -0
  167. package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
  168. package/src/pages/admin/billing/subscriptions/detail.tsx +90 -25
  169. package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
  170. package/src/pages/admin/customers/customers/detail.tsx +67 -14
  171. package/src/pages/admin/customers/customers/index.tsx +6 -1
  172. package/src/pages/admin/customers/index.tsx +5 -0
  173. package/src/pages/admin/developers/events/detail.tsx +37 -11
  174. package/src/pages/admin/developers/index.tsx +1 -1
  175. package/src/pages/admin/developers/webhooks/detail.tsx +41 -11
  176. package/src/pages/admin/index.tsx +15 -2
  177. package/src/pages/admin/overview.tsx +107 -19
  178. package/src/pages/admin/payments/intents/detail.tsx +58 -14
  179. package/src/pages/admin/payments/payouts/detail.tsx +63 -15
  180. package/src/pages/admin/payments/refunds/detail.tsx +58 -14
  181. package/src/pages/admin/products/index.tsx +11 -4
  182. package/src/pages/admin/products/links/create.tsx +22 -4
  183. package/src/pages/admin/products/links/detail.tsx +43 -14
  184. package/src/pages/admin/products/passports/index.tsx +23 -4
  185. package/src/pages/admin/products/prices/actions.tsx +16 -9
  186. package/src/pages/admin/products/prices/detail.tsx +73 -14
  187. package/src/pages/admin/products/prices/list.tsx +15 -3
  188. package/src/pages/admin/products/pricing-tables/create.tsx +45 -12
  189. package/src/pages/admin/products/pricing-tables/detail.tsx +45 -14
  190. package/src/pages/admin/products/products/create.tsx +233 -54
  191. package/src/pages/admin/products/products/detail.tsx +74 -18
  192. package/src/pages/admin/settings/index.tsx +8 -1
  193. package/src/pages/admin/settings/payment-methods/index.tsx +87 -19
  194. package/src/pages/admin/settings/vault-config/edit-form.tsx +42 -28
  195. package/src/pages/admin/settings/vault-config/index.tsx +57 -10
  196. package/src/pages/customer/credit-grant/detail.tsx +308 -0
  197. package/src/pages/customer/index.tsx +76 -17
  198. package/src/pages/customer/invoice/detail.tsx +63 -14
  199. package/src/pages/customer/invoice/past-due.tsx +11 -3
  200. package/src/pages/customer/payout/detail.tsx +56 -13
  201. package/src/pages/customer/recharge/account.tsx +78 -18
  202. package/src/pages/customer/recharge/subscription.tsx +86 -25
  203. package/src/pages/customer/refund/list.tsx +60 -24
  204. package/src/pages/customer/subscription/change-payment.tsx +17 -6
  205. package/src/pages/customer/subscription/change-plan.tsx +34 -7
  206. package/src/pages/customer/subscription/detail.tsx +134 -34
  207. package/src/pages/customer/subscription/embed.tsx +25 -5
  208. package/src/pages/home.tsx +26 -4
  209. package/src/pages/integrations/donations/edit-form.tsx +25 -9
  210. package/src/pages/integrations/donations/index.tsx +26 -9
  211. package/src/pages/integrations/donations/preview.tsx +59 -15
  212. package/src/pages/integrations/index.tsx +10 -1
  213. package/src/pages/integrations/overview.tsx +78 -17
  214. package/vite.config.ts +60 -30
@@ -1,27 +1,13 @@
1
1
  /* eslint-disable @typescript-eslint/brace-style */
2
2
  /* eslint-disable @typescript-eslint/indent */
3
3
  import { fromUnitToToken, toDid } from '@ocap/util';
4
- import prettyMsI18n from 'pretty-ms-i18n';
5
4
  import logger from '../../logger';
6
- import { getUserLocale } from '../../../integrations/blocklet/notification';
7
5
  import { translate } from '../../../locales';
8
- import {
9
- CheckoutSession,
10
- Customer,
11
- NFTMintChainType,
12
- Invoice,
13
- NftMintItem,
14
- PaymentIntent,
15
- PaymentMethod,
16
- Subscription,
17
- } from '../../../store/models';
6
+ import { CheckoutSession, NFTMintChainType, Invoice, NftMintItem, PaymentIntent } from '../../../store/models';
18
7
  import { PaymentCurrency } from '../../../store/models/payment-currency';
19
- import { getCustomerInvoicePageUrl } from '../../invoice';
20
- import { getMainProductName } from '../../product';
21
- import { getCustomerSubscriptionPageUrl } from '../../subscription';
22
- import { formatTime, getPrettyMsI18nLocale } from '../../time';
23
- import { formatCurrencyInfo, getExplorerLink, getSubscriptionNotificationCustomActions } from '../../util';
24
- import type { BaseEmailTemplate, BaseEmailTemplateType } from './base';
8
+ import { formatTime } from '../../time';
9
+ import { formatCurrencyInfo, getExplorerLink } from '../../util';
10
+ import { BaseSubscriptionEmailTemplate, BaseEmailTemplateType } from './base';
25
11
 
26
12
  export interface SubscriptionRenewedEmailTemplateOptions {
27
13
  subscriptionId: string;
@@ -32,133 +18,112 @@ interface SubscriptionRenewedEmailTemplateContext {
32
18
  locale: string;
33
19
  productName: string;
34
20
  at: string;
35
-
36
21
  nftMintItem: NftMintItem | undefined;
37
22
  userDid: string;
38
23
  paymentInfo: string;
39
- /**
40
- * @description 支付金额
41
- * @type {number}
42
- * @memberof SubscriptionRenewedEmailTemplateContext
43
- */
44
24
  amountPaid: number;
45
25
  currentPeriodStart: string;
46
26
  currentPeriodEnd: string;
47
27
  duration: string;
48
-
49
28
  viewSubscriptionLink: string;
50
29
  viewInvoiceLink: string;
51
30
  viewTxHashLink: string | undefined;
52
31
  invoice: Invoice;
53
32
  customActions: any[];
33
+ isCreditSubscription: boolean;
54
34
  }
55
35
 
56
- export class SubscriptionRenewedEmailTemplate implements BaseEmailTemplate<SubscriptionRenewedEmailTemplateContext> {
36
+ export class SubscriptionRenewedEmailTemplate extends BaseSubscriptionEmailTemplate<SubscriptionRenewedEmailTemplateContext> {
57
37
  options: SubscriptionRenewedEmailTemplateOptions;
58
38
 
59
39
  constructor(options: SubscriptionRenewedEmailTemplateOptions) {
40
+ super();
60
41
  this.options = options;
61
42
  }
62
43
 
63
44
  async getContext(): Promise<SubscriptionRenewedEmailTemplateContext> {
64
- const subscription: Subscription | null = await Subscription.findByPk(this.options.subscriptionId);
65
- if (!subscription) {
66
- throw new Error(`Subscription not found: ${this.options.subscriptionId}`);
67
- }
45
+ // 获取基础订阅数据
46
+ const basicData = await this.getSubscriptionBasicData(this.options.subscriptionId);
47
+ const { subscription, userDid, locale, productName, isCreditSubscription } = basicData;
48
+
68
49
  if (subscription.status !== 'active') {
69
50
  throw new Error(`Subscription not active: ${this.options.subscriptionId}`);
70
51
  }
71
52
 
72
- const customer = await Customer.findByPk(subscription.customer_id);
73
- if (!customer) {
74
- throw new Error(`Customer not found: ${subscription.customer_id}`);
75
- }
76
-
53
+ // 获取发票信息
77
54
  const invoice = (
78
55
  this.options.invoiceId
79
56
  ? await Invoice.findByPk(this.options.invoiceId)
80
57
  : await Invoice.findByPk(subscription.latest_invoice_id)
81
58
  ) as Invoice;
82
59
 
83
- const paymentIntent = await PaymentIntent.findByPk(invoice.payment_intent_id);
84
60
  const paymentCurrency = (await PaymentCurrency.findOne({
85
- where: {
86
- id: invoice.currency_id,
87
- },
61
+ where: { id: invoice.currency_id },
88
62
  })) as PaymentCurrency;
89
63
 
90
- const checkoutSession = await CheckoutSession.findBySubscriptionId(subscription.id);
91
- const userDid: string = customer.did;
92
- const locale = await getUserLocale(userDid);
93
- const productName = await getMainProductName(subscription.id);
94
- const at: string = formatTime((invoice?.status_transitions?.paid_at ?? Math.floor(Date.now() / 1000)) * 1000);
95
-
96
- const hasNft: boolean = checkoutSession?.nft_mint_status === 'minted';
97
- const nftMintItem: NftMintItem | undefined = hasNft
98
- ? checkoutSession?.nft_mint_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]
99
- : undefined;
100
- const amountPaid = +fromUnitToToken(invoice.total, paymentCurrency.decimal);
101
- const currentPeriodStart: string = formatTime(invoice.period_start * 1000);
102
- const currentPeriodEnd: string = formatTime(invoice.period_end * 1000);
103
- const duration: string = prettyMsI18n(
104
- new Date(currentPeriodEnd).getTime() - new Date(currentPeriodStart).getTime(),
105
- {
106
- locale: getPrettyMsI18nLocale(locale),
107
- }
64
+ // 获取支付信息
65
+ const paymentInfoResult = await this.getPaymentInfo(
66
+ subscription,
67
+ paymentCurrency,
68
+ isCreditSubscription,
69
+ invoice?.id
108
70
  );
109
71
 
110
- const paymentMethod: PaymentMethod | null = await PaymentMethod.findByPk(invoice.default_payment_method_id);
111
- const paymentInfo: string = formatCurrencyInfo(invoice.total, paymentCurrency, paymentMethod);
112
-
113
- const chainHost: string | undefined =
114
- paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.api_host;
115
- const viewSubscriptionLink = getCustomerSubscriptionPageUrl({
116
- subscriptionId: subscription.id,
72
+ // 获取链接
73
+ const links = this.generateSubscriptionLinks(
74
+ subscription,
117
75
  locale,
118
76
  userDid,
119
- });
120
- const viewInvoiceLink = getCustomerInvoicePageUrl({
121
- invoiceId: invoice.id,
122
- userDid,
123
- locale,
124
- });
77
+ 'customer.subscription.renewed',
78
+ invoice?.id,
79
+ isCreditSubscription
80
+ );
81
+
82
+ // 获取 NFT 信息
83
+ const nftInfo = await this.getNftMintInfo(subscription.id);
84
+
85
+ // 计算支付金额
86
+ const amountPaid = +fromUnitToToken(invoice.total, paymentCurrency.decimal);
87
+ const formattedPaymentInfo = formatCurrencyInfo(invoice.total, paymentCurrency, paymentInfoResult.paymentMethod);
88
+
89
+ // 格式化时间信息
90
+ const periodInfo = this.formatSubscriptionPeriod(invoice.period_start, invoice.period_end, locale);
91
+
92
+ // 获取交易哈希链接
93
+ const checkoutSession = await CheckoutSession.findBySubscriptionId(subscription.id);
94
+ const paymentIntent = await PaymentIntent.findByPk(invoice.payment_intent_id);
125
95
  const txHash: string | undefined =
126
96
  paymentIntent?.payment_details?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]?.tx_hash;
97
+
98
+ const chainHost: string | undefined =
99
+ paymentInfoResult.paymentMethod?.settings?.[checkoutSession?.nft_mint_details?.type as NFTMintChainType]
100
+ ?.api_host;
101
+
127
102
  const viewTxHashLink: string | undefined =
128
- hasNft && txHash
129
- ? getExplorerLink({
130
- type: 'tx',
131
- did: txHash,
132
- chainHost,
133
- })
134
- : undefined;
135
- const customActions = getSubscriptionNotificationCustomActions(
136
- subscription,
137
- 'customer.subscription.renewed',
138
- locale
139
- );
103
+ nftInfo.hasNft && txHash ? getExplorerLink({ type: 'tx', did: txHash, chainHost }) : undefined;
104
+
140
105
  return {
141
106
  locale,
142
107
  productName,
143
- at,
144
-
145
- nftMintItem,
108
+ at: formatTime((invoice?.status_transitions?.paid_at ?? Math.floor(Date.now() / 1000)) * 1000),
109
+ nftMintItem: nftInfo.nftMintItem,
146
110
  userDid,
147
- paymentInfo,
111
+ paymentInfo: formattedPaymentInfo,
148
112
  amountPaid,
149
- currentPeriodStart,
150
- currentPeriodEnd,
151
- duration,
152
-
153
- viewSubscriptionLink,
154
- viewInvoiceLink,
113
+ currentPeriodStart: periodInfo.currentPeriodStart,
114
+ currentPeriodEnd: periodInfo.currentPeriodEnd,
115
+ duration: periodInfo.duration,
116
+ viewSubscriptionLink: links.viewSubscriptionLink,
117
+ viewInvoiceLink: links.viewInvoiceLink!,
155
118
  viewTxHashLink,
156
119
  invoice,
157
- customActions,
120
+ customActions: links.customActions,
121
+ isCreditSubscription,
158
122
  };
159
123
  }
160
124
 
161
125
  async getTemplate(): Promise<BaseEmailTemplateType | null> {
126
+ const context = await this.getContext();
162
127
  const {
163
128
  locale,
164
129
  productName,
@@ -175,126 +140,91 @@ export class SubscriptionRenewedEmailTemplate implements BaseEmailTemplate<Subsc
175
140
  viewTxHashLink,
176
141
  invoice,
177
142
  customActions,
178
- } = await this.getContext();
143
+ isCreditSubscription,
144
+ } = context;
179
145
 
180
146
  if (invoice.total === '0') {
181
147
  logger.info('Invoice amount is 0, skipping renewed notification');
182
148
  return null;
183
149
  }
150
+
151
+ // 构建基础字段
152
+ const commonFields = this.buildCommonFields(userDid, productName, locale);
153
+
154
+ // 构建支付金额字段(包含特殊处理)
155
+ const paymentFields = isCreditSubscription
156
+ ? []
157
+ : [
158
+ {
159
+ type: 'text',
160
+ data: {
161
+ type: 'plain',
162
+ color: '#9397A1',
163
+ text: translate('notification.common.paymentAmount', locale),
164
+ },
165
+ },
166
+ {
167
+ type: 'text',
168
+ data: {
169
+ type: 'plain',
170
+ text: `${paymentInfo}${
171
+ amountPaid === 0 ? ` (${translate('notification.subscriptionRenewed.noExpenseIncurred', locale)})` : ''
172
+ }`,
173
+ },
174
+ },
175
+ ];
176
+
177
+ const periodFields = this.buildPeriodField(
178
+ currentPeriodStart,
179
+ currentPeriodEnd,
180
+ duration,
181
+ locale,
182
+ isCreditSubscription
183
+ );
184
+
185
+ // NFT 地址字段
186
+ const nftFields = nftMintItem
187
+ ? [
188
+ {
189
+ type: 'text',
190
+ data: {
191
+ type: 'plain',
192
+ color: '#9397A1',
193
+ text: translate('notification.common.nftAddress', locale),
194
+ },
195
+ },
196
+ {
197
+ type: 'text',
198
+ data: {
199
+ type: 'plain',
200
+ text: toDid(nftMintItem.address),
201
+ },
202
+ },
203
+ ]
204
+ : [];
205
+
206
+ // 构建操作按钮
207
+ const actions = this.buildCommonActions(viewSubscriptionLink, viewInvoiceLink, locale, isCreditSubscription, [
208
+ viewTxHashLink && {
209
+ name: translate('notification.common.viewTxHash', locale),
210
+ title: translate('notification.common.viewTxHash', locale),
211
+ link: viewTxHashLink as string,
212
+ },
213
+ ...customActions,
214
+ ]);
215
+
184
216
  const template: BaseEmailTemplateType = {
185
- title: `${translate('notification.subscriptionRenewed.title', locale, {
186
- productName,
187
- })}`,
188
- body: `${translate('notification.subscriptionRenewed.body', locale, {
189
- at,
190
- productName,
191
- })}`,
217
+ title: translate('notification.subscriptionRenewed.title', locale, { productName }),
218
+ body: translate('notification.subscriptionRenewed.body', locale, { at, productName }),
192
219
  // @ts-expect-error
193
220
  attachments: [
194
221
  {
195
222
  type: 'section',
196
- fields: [
197
- {
198
- type: 'text',
199
- data: {
200
- type: 'plain',
201
- color: '#9397A1',
202
- text: translate('notification.common.account', locale),
203
- },
204
- },
205
- {
206
- type: 'text',
207
- data: {
208
- type: 'plain',
209
- text: userDid,
210
- },
211
- },
212
- {
213
- type: 'text',
214
- data: {
215
- type: 'plain',
216
- color: '#9397A1',
217
- text: translate('notification.common.product', locale),
218
- },
219
- },
220
- {
221
- type: 'text',
222
- data: {
223
- type: 'plain',
224
- text: productName,
225
- },
226
- },
227
- {
228
- type: 'text',
229
- data: {
230
- type: 'plain',
231
- color: '#9397A1',
232
- text: translate('notification.common.paymentAmount', locale),
233
- },
234
- },
235
- {
236
- type: 'text',
237
- data: {
238
- type: 'plain',
239
- text: `${paymentInfo}${
240
- amountPaid === 0
241
- ? ` (${translate('notification.subscriptionRenewed.noExpenseIncurred', locale)})`
242
- : ''
243
- }`,
244
- },
245
- },
246
- {
247
- type: 'text',
248
- data: {
249
- type: 'plain',
250
- color: '#9397A1',
251
- text: translate('notification.common.validityPeriod', locale),
252
- },
253
- },
254
- {
255
- type: 'text',
256
- data: {
257
- type: 'plain',
258
- text: `${currentPeriodStart} ~ ${currentPeriodEnd}(${duration})`,
259
- },
260
- },
261
- nftMintItem && {
262
- type: 'text',
263
- data: {
264
- type: 'plain',
265
- color: '#9397A1',
266
- text: translate('notification.common.nftAddress', locale),
267
- },
268
- },
269
- nftMintItem && {
270
- type: 'text',
271
- data: {
272
- type: 'plain',
273
- text: toDid(nftMintItem.address),
274
- },
275
- },
276
- ].filter(Boolean),
223
+ fields: [...commonFields, ...paymentFields, ...periodFields, ...nftFields].filter(Boolean),
277
224
  },
278
225
  ].filter(Boolean),
279
226
  // @ts-ignore
280
- actions: [
281
- {
282
- name: translate('notification.common.viewSubscription', locale),
283
- title: translate('notification.common.viewSubscription', locale),
284
- link: viewSubscriptionLink,
285
- },
286
- {
287
- name: translate('notification.common.viewInvoice', locale),
288
- title: translate('notification.common.viewInvoice', locale),
289
- link: viewInvoiceLink,
290
- },
291
- viewTxHashLink && {
292
- name: translate('notification.common.viewTxHash', locale),
293
- title: translate('notification.common.viewTxHash', locale),
294
- link: viewTxHashLink as string,
295
- },
296
- ...customActions,
297
- ].filter(Boolean),
227
+ actions: actions.filter(Boolean),
298
228
  };
299
229
 
300
230
  return template;