payment-kit 1.24.1 → 1.24.3
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/crons/index.ts +5 -5
- package/api/src/crons/overdue-detection.ts +725 -0
- package/api/src/libs/env.ts +2 -0
- package/api/src/libs/notification/template/customer-auto-recharge-daily-limit-exceeded.ts +222 -0
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +20 -63
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +57 -7
- package/api/src/locales/en.ts +74 -36
- package/api/src/locales/zh.ts +77 -39
- package/api/src/queues/auto-recharge.ts +7 -0
- package/api/src/queues/credit-consume.ts +23 -2
- package/api/src/queues/notification.ts +119 -1
- package/api/src/routes/credit-transactions.ts +85 -7
- package/api/src/routes/invoices.ts +12 -0
- package/api/src/routes/meter-events.ts +169 -0
- package/blocklet.yml +1 -1
- package/package.json +6 -6
- package/src/components/customer/credit-overview.tsx +7 -1
- package/src/locales/en.tsx +20 -0
- package/src/locales/zh.tsx +20 -0
- package/src/pages/admin/billing/index.tsx +4 -0
- package/src/pages/admin/billing/meter-events/index.tsx +588 -0
- package/src/pages/admin/billing/overdue/index.tsx +289 -0
- package/src/pages/admin/customers/customers/credit-transaction/detail.tsx +15 -0
- package/src/pages/admin/overview.tsx +129 -1
- package/src/pages/customer/credit-transaction/detail.tsx +12 -0
package/api/src/locales/en.ts
CHANGED
|
@@ -66,8 +66,8 @@ export default flat({
|
|
|
66
66
|
},
|
|
67
67
|
|
|
68
68
|
webhookEndpointFailed: {
|
|
69
|
-
title: 'Webhook
|
|
70
|
-
body: 'Your webhook endpoint has failed {failedCount} times
|
|
69
|
+
title: 'System Alert: Webhook Failure ({webhookUrl})',
|
|
70
|
+
body: 'Your webhook endpoint has failed {failedCount} times in a row. Please check your endpoint configuration immediately.',
|
|
71
71
|
},
|
|
72
72
|
|
|
73
73
|
sendTo: 'Sent to',
|
|
@@ -89,38 +89,70 @@ export default flat({
|
|
|
89
89
|
view: 'Manage subscriptions',
|
|
90
90
|
},
|
|
91
91
|
|
|
92
|
+
healthReport: {
|
|
93
|
+
title: 'Payment Kit Daily Data Report',
|
|
94
|
+
period: 'Data period: {start} – {end} (UTC)',
|
|
95
|
+
attentionSummary: '⚠️ {count} items need attention',
|
|
96
|
+
attentionSummaryEmpty: '✅ No items need attention today',
|
|
97
|
+
attentionEmpty: 'No items need attention',
|
|
98
|
+
attentionUnreportedLabel: 'Metering anomaly subscriptions',
|
|
99
|
+
attentionUnreportedValue: '{count} total, IDs: {ids}',
|
|
100
|
+
attentionDiscrepantLabel: 'Billing discrepancy subscriptions',
|
|
101
|
+
attentionDiscrepantValue: '{count} total, IDs: {ids}',
|
|
102
|
+
attentionPendingConsumptionLabel: 'Overdue consumption',
|
|
103
|
+
attentionPendingConsumptionValue: '{customers} customers, total {amount}',
|
|
104
|
+
attentionRefundLabel: 'Refunds',
|
|
105
|
+
attentionRefundValue: '{refundCount} refunds, total {refundAmount}',
|
|
106
|
+
attentionPastDueLabel: 'Past due subscriptions',
|
|
107
|
+
attentionPastDueValue: '{count}',
|
|
108
|
+
coreMetricsTitle: '💰 Core Operating Metrics',
|
|
109
|
+
coreRevenueLabel: 'Revenue',
|
|
110
|
+
coreRevenueValue: '{revenue}',
|
|
111
|
+
coreNewSubscriptionsLabel: 'New subscriptions',
|
|
112
|
+
coreNewSubscriptionsValue: '{count}',
|
|
113
|
+
coreCanceledSubscriptionsLabel: 'Canceled subscriptions',
|
|
114
|
+
coreCanceledSubscriptionsValue: '{count}',
|
|
115
|
+
corePaymentSuccessRateLabel: 'Payment success rate',
|
|
116
|
+
corePaymentSuccessRateValue: '{rate}% ({succeeded}, {failed})',
|
|
117
|
+
paymentSuccessCount: 'Success {count}',
|
|
118
|
+
paymentFailedCount: 'Failed {count}',
|
|
119
|
+
viewDataOverview: 'Data Overview',
|
|
120
|
+
viewSubscriptions: 'View Subscriptions',
|
|
121
|
+
viewOverdue: 'View Overdue Consumption',
|
|
122
|
+
},
|
|
123
|
+
|
|
92
124
|
subscriptionTrialStart: {
|
|
93
|
-
title: '
|
|
94
|
-
body: '
|
|
125
|
+
title: '{productName} Trial Active',
|
|
126
|
+
body: 'Your {productName} trial is now active. You have full access until {subscriptionTrialEnd}.',
|
|
95
127
|
},
|
|
96
128
|
|
|
97
129
|
subscriptionTrialWillEnd: {
|
|
98
|
-
title: '
|
|
99
|
-
body: 'Your trial for {productName}
|
|
130
|
+
title: 'Trial Ending Soon: {productName}',
|
|
131
|
+
body: 'Your trial for {productName} ends in {willRenewDuration}. To ensure uninterrupted service, please check your payment details.',
|
|
100
132
|
unableToPayBody:
|
|
101
|
-
'Your trial
|
|
133
|
+
'Your trial ends in {willRenewDuration}, but your current balance ({balance}) is below the renewal price ({price}). Please add funds to ensure uninterrupted service.',
|
|
102
134
|
unableToPayReason:
|
|
103
|
-
'The estimated
|
|
135
|
+
'The estimated renewal is {price}, but your balance is only {balance}. Please add funds to avoid payment failure.',
|
|
104
136
|
},
|
|
105
137
|
|
|
106
138
|
subscriptionSucceed: {
|
|
107
|
-
title:
|
|
108
|
-
body: '
|
|
139
|
+
title: '{productName} is ready to use',
|
|
140
|
+
body: 'Your subscription is confirmed. You can now access {productName} or manage your deployment settings below.',
|
|
109
141
|
},
|
|
110
142
|
|
|
111
143
|
oneTimePaymentSucceeded: {
|
|
112
|
-
title: '
|
|
113
|
-
body: '
|
|
144
|
+
title: 'Purchase Confirmed: {productName}',
|
|
145
|
+
body: 'We have received your payment for {productName} on {at}. Your service is ready to use.',
|
|
114
146
|
},
|
|
115
147
|
|
|
116
148
|
subscriptionUpgraded: {
|
|
117
|
-
title: '
|
|
118
|
-
body: 'Your
|
|
149
|
+
title: 'Upgrade Complete: {productName}',
|
|
150
|
+
body: 'Your {productName} plan was successfully upgraded on {at}. All new features are available immediately.',
|
|
119
151
|
},
|
|
120
152
|
|
|
121
153
|
subscriptionWillRenew: {
|
|
122
|
-
title: '{productName}
|
|
123
|
-
body: '
|
|
154
|
+
title: 'Upcoming renewal: {productName}',
|
|
155
|
+
body: 'Just a heads up that your subscription for {productName} will renew on {at}. No action is needed if you want to keep running.',
|
|
124
156
|
unableToPayBody:
|
|
125
157
|
'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.',
|
|
126
158
|
unableToPayReason:
|
|
@@ -130,14 +162,14 @@ export default flat({
|
|
|
130
162
|
},
|
|
131
163
|
|
|
132
164
|
subscriptionRenewed: {
|
|
133
|
-
title: '{productName}
|
|
134
|
-
body: '
|
|
165
|
+
title: 'Payment Successful: {productName}',
|
|
166
|
+
body: 'Your renewal for {productName} was processed on {at}. No further action is needed.',
|
|
135
167
|
noExpenseIncurred: 'No expenses incurred during the service period',
|
|
136
168
|
},
|
|
137
169
|
|
|
138
170
|
subscriptionRenewFailed: {
|
|
139
|
-
title: '{productName}
|
|
140
|
-
body:
|
|
171
|
+
title: 'Action Required: Payment for {productName} failed',
|
|
172
|
+
body: "We couldn't process the renewal for {productName} on {at}. To prevent your App from going offline, please update your payment method.",
|
|
141
173
|
reason: {
|
|
142
174
|
noDidWallet: 'You have not connected a DID Wallet. Please connect your DID Wallet to ensure sufficient balance',
|
|
143
175
|
noDelegation: 'Your DID Wallet has not been authorized. Please update authorization',
|
|
@@ -156,8 +188,8 @@ export default flat({
|
|
|
156
188
|
},
|
|
157
189
|
|
|
158
190
|
autoRechargeFailed: {
|
|
159
|
-
title: 'Auto Top-Up
|
|
160
|
-
body:
|
|
191
|
+
title: 'Action Required: Auto Top-Up Failed',
|
|
192
|
+
body: "We couldn't process your auto top-up for {creditCurrencyName} on {at}. Please update your payment method to ensure your services stay online.",
|
|
161
193
|
reason: {
|
|
162
194
|
noDidWallet: 'You have not connected a DID Wallet. Please connect your DID Wallet to ensure sufficient balance',
|
|
163
195
|
noDelegation: 'Your DID Wallet has not been authorized. Please update authorization',
|
|
@@ -175,9 +207,16 @@ export default flat({
|
|
|
175
207
|
},
|
|
176
208
|
},
|
|
177
209
|
|
|
210
|
+
autoRechargeDailyLimitExceeded: {
|
|
211
|
+
title: 'Auto Top-Up daily limit reached',
|
|
212
|
+
body: 'Your {creditCurrencyName} balance is low, but auto top-up was not triggered because you have reached the daily recharge limit on {at}. You can manually reload or wait until tomorrow for automatic top-up.',
|
|
213
|
+
dailyLimit: 'Daily Limit',
|
|
214
|
+
dailyUsed: 'Used Today',
|
|
215
|
+
},
|
|
216
|
+
|
|
178
217
|
subscriptionRefundSucceeded: {
|
|
179
|
-
title: '{productName}
|
|
180
|
-
body: '
|
|
218
|
+
title: 'Refund Processed: {productName}',
|
|
219
|
+
body: 'A refund of {refundInfo} for {productName} was processed on {at}. It may take a few days to appear in your account.',
|
|
181
220
|
},
|
|
182
221
|
|
|
183
222
|
oneTimePaymentRefundSucceeded: {
|
|
@@ -207,23 +246,23 @@ export default flat({
|
|
|
207
246
|
|
|
208
247
|
customerRevenueSucceeded: {
|
|
209
248
|
donate: {
|
|
210
|
-
title: '
|
|
211
|
-
body: '
|
|
249
|
+
title: 'New Tip Received!',
|
|
250
|
+
body: 'You received a tip of {amount} from {user} on {at}. Nice work!',
|
|
212
251
|
tipDetail: 'View Tip Reference',
|
|
213
252
|
},
|
|
214
253
|
payment: {
|
|
215
|
-
title: '
|
|
216
|
-
body: '
|
|
254
|
+
title: 'Payment Received',
|
|
255
|
+
body: 'You received a payment of {amount} from {user} on {at}.',
|
|
217
256
|
},
|
|
218
257
|
sender: 'Payer',
|
|
219
258
|
viewDetail: 'View Details',
|
|
220
259
|
sent: '{address} has sent {amount}',
|
|
221
260
|
},
|
|
222
261
|
subscriptWillCanceled: {
|
|
223
|
-
title: '{productName}
|
|
262
|
+
title: 'Subscription Cancelled: {productName}',
|
|
263
|
+
body: "This subscription has been cancelled and will end on {at}. If this was a mistake, or if you'd like to reactivate, you can do so below.",
|
|
224
264
|
pastDue:
|
|
225
265
|
'Your {productName} subscription will be automatically cancelled by the system after {at} ({willCancelDuration} later) due to repeated automatic payment failures. Please resolve the automatic payment issue manually to avoid service interruption. If you have any questions, please feel free to contact us.',
|
|
226
|
-
body: 'Your subscription to {productName} will be automatically canceled on {at} ({willCancelDuration} later). If you have any questions, please feel free to contact us.',
|
|
227
266
|
renewAmount: 'Deduction amount',
|
|
228
267
|
cancelReason: 'Cancel reason',
|
|
229
268
|
revokeStake: 'Revoke stake',
|
|
@@ -263,11 +302,9 @@ export default flat({
|
|
|
263
302
|
},
|
|
264
303
|
|
|
265
304
|
creditInsufficient: {
|
|
266
|
-
title: '
|
|
305
|
+
title: 'Service Risk: Low Credit Balance',
|
|
267
306
|
bodyWithSubscription:
|
|
268
|
-
'Your
|
|
269
|
-
bodyWithoutSubscription:
|
|
270
|
-
'Your available credit balance ({availableAmount}) is not enough to continue using the service. To ensure uninterrupted service, please reload your account.',
|
|
307
|
+
'Your credits ({availableAmount}) are too low to maintain your {subscriptionName} subscription. Please reload now to ensure your App stays online.',
|
|
271
308
|
exhaustedTitle: 'Credit Balance Exhausted – Please Reload',
|
|
272
309
|
exhaustedBodyWithSubscription:
|
|
273
310
|
'Your credit balance is fully exhausted and can no longer cover your subscription to {subscriptionName}. To ensure uninterrupted service, please reload your account.',
|
|
@@ -276,6 +313,7 @@ export default flat({
|
|
|
276
313
|
meterEventName: 'Service',
|
|
277
314
|
availableCredit: 'Available Balance',
|
|
278
315
|
requiredCredit: 'Required Credit',
|
|
316
|
+
pendingAmount: 'Outstanding Amount',
|
|
279
317
|
},
|
|
280
318
|
|
|
281
319
|
creditGrantGranted: {
|
|
@@ -288,8 +326,8 @@ export default flat({
|
|
|
288
326
|
},
|
|
289
327
|
|
|
290
328
|
creditLowBalance: {
|
|
291
|
-
title: '
|
|
292
|
-
body: 'Your {currency} balance
|
|
329
|
+
title: 'Low Balance Alert',
|
|
330
|
+
body: 'Your {currency} balance is down to {lowBalancePercentage}. Please reload soon to prevent service interruption.',
|
|
293
331
|
remainingBalance: 'Remaining Balance',
|
|
294
332
|
status: 'Status',
|
|
295
333
|
lessThanOnePercent: 'less than 1%',
|
package/api/src/locales/zh.ts
CHANGED
|
@@ -76,11 +76,6 @@ export default flat({
|
|
|
76
76
|
body: '检测到 {productName} 账单金额核算不一致,请留意。',
|
|
77
77
|
},
|
|
78
78
|
|
|
79
|
-
webhookEndpointFailed: {
|
|
80
|
-
title: 'Webhook 端点警告: {webhookUrl}',
|
|
81
|
-
body: '您的 Webhook 端点已连续失败 {failedCount} 次,请查看详情并确保端点可正常访问和工作。',
|
|
82
|
-
},
|
|
83
|
-
|
|
84
79
|
meteringSubscriptionDetection: {
|
|
85
80
|
title: '[{appName}] 按量计费订阅检测',
|
|
86
81
|
body: '在 {startTimeStr} - {endTimeStr} 期间,共扫描了 {totalCount} 份按量计费的订阅,其中 {normalCount} 份为正常订阅,{abnormalCount} 份存在异常,包括 {unreportedCount} 份未上报的订阅和 {discrepantCount} 份账单核算有问题的订阅。\n\n 异常订阅:',
|
|
@@ -88,38 +83,74 @@ export default flat({
|
|
|
88
83
|
view: '管理订阅',
|
|
89
84
|
},
|
|
90
85
|
|
|
86
|
+
webhookEndpointFailed: {
|
|
87
|
+
title: '系统警报:Webhook 失败 ({webhookUrl})',
|
|
88
|
+
body: '您的 Webhook 端点已连续失败 {failedCount} 次,请立即检查您的端点配置。',
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
healthReport: {
|
|
92
|
+
title: 'Payment Kit 每日数据报告',
|
|
93
|
+
period: '数据周期:{start} – {end}(UTC)',
|
|
94
|
+
attentionSummary: '⚠️ {count} 个需要关注的事项',
|
|
95
|
+
attentionSummaryEmpty: '✅ 今日无需要关注的事项',
|
|
96
|
+
attentionEmpty: '暂无需要关注的事项',
|
|
97
|
+
attentionUnreportedLabel: '计费异常订阅',
|
|
98
|
+
attentionUnreportedValue: '共 {count} 个,ID:{ids}',
|
|
99
|
+
attentionDiscrepantLabel: '计费差异订阅',
|
|
100
|
+
attentionDiscrepantValue: '共 {count} 个,ID:{ids}',
|
|
101
|
+
attentionPendingConsumptionLabel: '欠费额度',
|
|
102
|
+
attentionPendingConsumptionValue: '{customers} 个用户,合计 {amount}',
|
|
103
|
+
attentionRefundLabel: '退款',
|
|
104
|
+
attentionRefundValue: '{refundCount} 笔退款,合计 {refundAmount}',
|
|
105
|
+
attentionPastDueLabel: '逾期订阅',
|
|
106
|
+
attentionPastDueValue: '{count} 个',
|
|
107
|
+
coreMetricsTitle: '💰 核心运营指标',
|
|
108
|
+
coreRevenueLabel: '收入',
|
|
109
|
+
coreRevenueValue: '{revenue}',
|
|
110
|
+
coreNewSubscriptionsLabel: '新增订阅',
|
|
111
|
+
coreNewSubscriptionsValue: '{count} 个',
|
|
112
|
+
coreCanceledSubscriptionsLabel: '取消订阅',
|
|
113
|
+
coreCanceledSubscriptionsValue: '{count} 个',
|
|
114
|
+
corePaymentSuccessRateLabel: '支付成功率',
|
|
115
|
+
corePaymentSuccessRateValue: '{rate}%,{succeeded},{failed}',
|
|
116
|
+
paymentSuccessCount: '成功 {count} 笔',
|
|
117
|
+
paymentFailedCount: '失败 {count} 笔',
|
|
118
|
+
viewDataOverview: '数据总览',
|
|
119
|
+
viewSubscriptions: '查看订阅列表',
|
|
120
|
+
viewOverdue: '查看欠费额度',
|
|
121
|
+
},
|
|
122
|
+
|
|
91
123
|
subscriptionTrialStart: {
|
|
92
|
-
title: '
|
|
93
|
-
body: '
|
|
124
|
+
title: '{productName} 试用已激活',
|
|
125
|
+
body: '您的 {productName} 试用现已激活。在 {subscriptionTrialEnd} 之前,您可以完全使用所有功能。',
|
|
94
126
|
},
|
|
95
127
|
|
|
96
128
|
subscriptionTrialWillEnd: {
|
|
97
|
-
title: '{productName}
|
|
98
|
-
body: '
|
|
129
|
+
title: '试用期即将结束:{productName}',
|
|
130
|
+
body: '您的 {productName} 试用期将在 {willRenewDuration} 后结束。为确保服务不中断,请检查您的支付详情。',
|
|
99
131
|
unableToPayBody:
|
|
100
|
-
'
|
|
101
|
-
unableToPayReason:
|
|
102
|
-
'预计扣款金额为 {price},但当前余额不足(余额为 {balance}),请确保您的账户余额充足,避免扣费失败。',
|
|
132
|
+
'您的试用期将在 {willRenewDuration} 后结束,但您的当前余额({balance})低于续费价格({price})。请充值以确保服务不中断。',
|
|
133
|
+
unableToPayReason: '预计续费金额为 {price},但您的余额仅为 {balance}。请充值以避免扣费失败。',
|
|
103
134
|
},
|
|
104
135
|
|
|
105
136
|
subscriptionSucceed: {
|
|
106
|
-
title: '
|
|
107
|
-
body: '
|
|
137
|
+
title: '{productName} 已准备就绪',
|
|
138
|
+
body: '您的订阅已确认。您现在可以访问 {productName} 或在下方管理您的部署设置。',
|
|
108
139
|
},
|
|
109
140
|
|
|
110
141
|
oneTimePaymentSucceeded: {
|
|
111
|
-
title: '
|
|
112
|
-
body: '
|
|
142
|
+
title: '购买已确认:{productName}',
|
|
143
|
+
body: '我们已收到您在 {at} 对 {productName} 的付款。您的服务已准备就绪。',
|
|
113
144
|
},
|
|
114
145
|
|
|
115
146
|
subscriptionUpgraded: {
|
|
116
|
-
title: '
|
|
117
|
-
body: '
|
|
147
|
+
title: '升级完成:{productName}',
|
|
148
|
+
body: '您的 {productName} 套餐已于 {at} 成功升级。所有新功能现已立即可用。',
|
|
118
149
|
},
|
|
119
150
|
|
|
120
151
|
subscriptionWillRenew: {
|
|
121
|
-
title: '{productName}
|
|
122
|
-
body: '
|
|
152
|
+
title: '即将续费:{productName}',
|
|
153
|
+
body: '提醒您,{productName} 订阅将在 {at} 续费。如果您想继续运行,无需任何操作。',
|
|
123
154
|
unableToPayBody:
|
|
124
155
|
'您订阅的 {productName} 将在 {at}({willRenewDuration}后) 发起自动扣费,若有任何疑问或者需要帮助,请随时与我们联系。',
|
|
125
156
|
unableToPayReason:
|
|
@@ -129,14 +160,14 @@ export default flat({
|
|
|
129
160
|
},
|
|
130
161
|
|
|
131
162
|
subscriptionRenewed: {
|
|
132
|
-
title: '{productName}
|
|
133
|
-
body: '您的 {productName}
|
|
163
|
+
title: '扣费成功:{productName}',
|
|
164
|
+
body: '您的 {productName} 续费已于 {at} 处理完成。无需进一步操作。',
|
|
134
165
|
noExpenseIncurred: '服务期间未产生费用',
|
|
135
166
|
},
|
|
136
167
|
|
|
137
168
|
subscriptionRenewFailed: {
|
|
138
|
-
title: '{productName} 扣费失败',
|
|
139
|
-
body: '
|
|
169
|
+
title: '需要操作:{productName} 扣费失败',
|
|
170
|
+
body: '我们无法处理 {productName} 在 {at} 的续费。为防止您的应用下线,请更新您的支付方式。',
|
|
140
171
|
reason: {
|
|
141
172
|
noDidWallet: '您尚未绑定 DID Wallet,请绑定 DID Wallet,确保余额充足。',
|
|
142
173
|
noDelegation: '您的 DID Wallet 尚未授权,请更新授权。',
|
|
@@ -151,8 +182,8 @@ export default flat({
|
|
|
151
182
|
},
|
|
152
183
|
},
|
|
153
184
|
autoRechargeFailed: {
|
|
154
|
-
title: '
|
|
155
|
-
body: '
|
|
185
|
+
title: '需要操作:自动充值失败',
|
|
186
|
+
body: '我们无法处理您在 {at} 对 {creditCurrencyName} 的自动充值。请更新您的支付方式以确保服务保持在线。',
|
|
156
187
|
reason: {
|
|
157
188
|
noDidWallet: '您尚未绑定 DID Wallet,请绑定 DID Wallet,确保余额充足。',
|
|
158
189
|
noDelegation: '您的 DID Wallet 尚未授权,请更新授权。',
|
|
@@ -167,9 +198,16 @@ export default flat({
|
|
|
167
198
|
},
|
|
168
199
|
},
|
|
169
200
|
|
|
201
|
+
autoRechargeDailyLimitExceeded: {
|
|
202
|
+
title: '自动充值已达每日限额',
|
|
203
|
+
body: '您的 {creditCurrencyName} 余额偏低,但由于已达到每日充值限额,自动充值于 {at} 未能触发。您可以手动充值,或等待明天自动充值。',
|
|
204
|
+
dailyLimit: '每日限额',
|
|
205
|
+
dailyUsed: '今日已用',
|
|
206
|
+
},
|
|
207
|
+
|
|
170
208
|
subscriptionRefundSucceeded: {
|
|
171
|
-
title: '{productName}
|
|
172
|
-
body: '
|
|
209
|
+
title: '退款已处理:{productName}',
|
|
210
|
+
body: '{productName} 的退款 {refundInfo} 已于 {at} 处理完成。可能需要几天时间才会显示在您的账户中。',
|
|
173
211
|
},
|
|
174
212
|
|
|
175
213
|
oneTimePaymentRefundSucceeded: {
|
|
@@ -199,13 +237,13 @@ export default flat({
|
|
|
199
237
|
|
|
200
238
|
customerRevenueSucceeded: {
|
|
201
239
|
donate: {
|
|
202
|
-
title: '
|
|
203
|
-
body: '
|
|
240
|
+
title: '收到新打赏!',
|
|
241
|
+
body: '您于 {at} 收到了来自 {user} 的 {amount} 打赏。干得好!',
|
|
204
242
|
tipDetail: '查看打赏原文',
|
|
205
243
|
},
|
|
206
244
|
payment: {
|
|
207
|
-
title: '
|
|
208
|
-
body: '
|
|
245
|
+
title: '收到付款',
|
|
246
|
+
body: '您于 {at} 收到了来自 {user} 的 {amount} 付款。',
|
|
209
247
|
},
|
|
210
248
|
sender: '付款方',
|
|
211
249
|
viewDetail: '查看详情',
|
|
@@ -213,10 +251,10 @@ export default flat({
|
|
|
213
251
|
},
|
|
214
252
|
|
|
215
253
|
subscriptWillCanceled: {
|
|
216
|
-
title: '{productName}
|
|
254
|
+
title: '订阅已取消:{productName}',
|
|
255
|
+
body: '此订阅已被取消,将于 {at} 结束。如果这是个错误,或者您想重新激活,可以在下方操作。',
|
|
217
256
|
pastDue:
|
|
218
257
|
'由于长时间未能自动完成扣费,您订阅的 {productName} 将于 {at} ({willCancelDuration}后) 被系统自动取消订阅。请您及时手动处理扣费问题,以免影响使用。如有任何疑问,请随时与我们联系。',
|
|
219
|
-
body: '您订阅的 {productName} 将于 {at} ({willCancelDuration}后) 被系统取消订阅,如有疑问,请随时与我们联系。',
|
|
220
258
|
renewAmount: '扣费金额',
|
|
221
259
|
cancelReason: '取消原因',
|
|
222
260
|
adminCanceled: '管理员取消',
|
|
@@ -253,10 +291,9 @@ export default flat({
|
|
|
253
291
|
},
|
|
254
292
|
|
|
255
293
|
creditInsufficient: {
|
|
256
|
-
title: '
|
|
294
|
+
title: '服务风险:额度偏低',
|
|
257
295
|
bodyWithSubscription:
|
|
258
|
-
'
|
|
259
|
-
bodyWithoutSubscription: '您的信用额度仅剩 {availableAmount},不足以继续使用服务。为避免服务受限,请及时充值。',
|
|
296
|
+
'您的额度({availableAmount})过低,无法维持您的 {subscriptionName} 订阅。请立即充值以确保您的应用保持在线。',
|
|
260
297
|
exhaustedTitle: '额度已用尽,请尽快充值',
|
|
261
298
|
exhaustedBodyWithSubscription:
|
|
262
299
|
'您的信用额度已用尽,无法继续支付您订阅的 {subscriptionName}。为避免服务中断,请及时充值。',
|
|
@@ -264,6 +301,7 @@ export default flat({
|
|
|
264
301
|
meterEventName: '服务项目',
|
|
265
302
|
availableCredit: '剩余额度',
|
|
266
303
|
requiredCredit: '所需额度',
|
|
304
|
+
pendingAmount: '待支付额度',
|
|
267
305
|
},
|
|
268
306
|
|
|
269
307
|
creditGrantGranted: {
|
|
@@ -276,8 +314,8 @@ export default flat({
|
|
|
276
314
|
},
|
|
277
315
|
|
|
278
316
|
creditLowBalance: {
|
|
279
|
-
title: '
|
|
280
|
-
body: '您的 {currency}
|
|
317
|
+
title: '低余额警报',
|
|
318
|
+
body: '您的 {currency} 余额已降至 {lowBalancePercentage}。请尽快充值以防止服务中断。',
|
|
281
319
|
remainingBalance: '剩余余额',
|
|
282
320
|
status: '状态',
|
|
283
321
|
lessThanOnePercent: '低于 1%',
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
TPriceExpanded,
|
|
16
16
|
} from '../store/models';
|
|
17
17
|
import logger from '../libs/logger';
|
|
18
|
+
import { createEvent } from '../libs/audit';
|
|
18
19
|
import { createStripeInvoiceForAutoRecharge } from '../integrations/stripe/resource';
|
|
19
20
|
import { ensureInvoiceAndItems } from '../libs/invoice';
|
|
20
21
|
import dayjs from '../libs/dayjs';
|
|
@@ -126,6 +127,12 @@ export async function processAutoRecharge(job: AutoRechargeJobData) {
|
|
|
126
127
|
customerId,
|
|
127
128
|
currencyId,
|
|
128
129
|
});
|
|
130
|
+
// Send notification when daily limit is exceeded
|
|
131
|
+
createEvent('Customer', 'customer.auto_recharge.daily_limit_exceeded', customer, {
|
|
132
|
+
autoRechargeConfigId: config.id,
|
|
133
|
+
currencyId,
|
|
134
|
+
currentBalance: totalAvailable.toString(),
|
|
135
|
+
});
|
|
129
136
|
return;
|
|
130
137
|
}
|
|
131
138
|
|
|
@@ -55,7 +55,9 @@ async function checkLowBalance(
|
|
|
55
55
|
const totalCreditAmountBn = new BN(totalCreditAmount);
|
|
56
56
|
if (totalCreditAmountBn.lte(new BN(0))) return;
|
|
57
57
|
const remainingAmountBn = new BN(remainingBalance);
|
|
58
|
-
|
|
58
|
+
// Get threshold percentage from env var, default to 10%
|
|
59
|
+
const thresholdPercentage = parseInt(process.env.CREDIT_LOW_BALANCE_THRESHOLD_PERCENTAGE || '10', 10);
|
|
60
|
+
const threshold = totalCreditAmountBn.mul(new BN(thresholdPercentage)).div(new BN(100));
|
|
59
61
|
if (remainingAmountBn.gt(new BN(0)) && remainingAmountBn.lte(threshold)) {
|
|
60
62
|
const percentage = remainingAmountBn.mul(new BN(100)).div(totalCreditAmountBn).toString();
|
|
61
63
|
await createEvent('Customer', 'customer.credit.low_balance', context.customer, {
|
|
@@ -249,6 +251,9 @@ async function consumeAvailableCredits(
|
|
|
249
251
|
const pendingAmount = Math.max(0, finalPending.toNumber()).toString();
|
|
250
252
|
const remainingBalance = totalAvailable.sub(consumed).toString();
|
|
251
253
|
|
|
254
|
+
// Track whether insufficient event was triggered to skip low_balance notification
|
|
255
|
+
let insufficientTriggered = false;
|
|
256
|
+
|
|
252
257
|
// 如果无法完全消费所需额度,记录不足事件
|
|
253
258
|
if (finalPending.gt(new BN(0))) {
|
|
254
259
|
logger.warn('Insufficient credit balance', {
|
|
@@ -260,6 +265,7 @@ async function consumeAvailableCredits(
|
|
|
260
265
|
pendingAmount,
|
|
261
266
|
});
|
|
262
267
|
if ((context.subscription && context.subscription.isActive()) || !context.subscription) {
|
|
268
|
+
insufficientTriggered = true;
|
|
263
269
|
await createEvent('Customer', 'customer.credit.insufficient', context.customer, {
|
|
264
270
|
metadata: {
|
|
265
271
|
meter_event_id: context.meterEvent.id,
|
|
@@ -297,6 +303,7 @@ async function consumeAvailableCredits(
|
|
|
297
303
|
});
|
|
298
304
|
}
|
|
299
305
|
} else if (remainingBalance === '0') {
|
|
306
|
+
insufficientTriggered = true;
|
|
300
307
|
await createEvent('Customer', 'customer.credit.insufficient', context.customer, {
|
|
301
308
|
metadata: {
|
|
302
309
|
meter_event_id: context.meterEvent.id,
|
|
@@ -312,7 +319,10 @@ async function consumeAvailableCredits(
|
|
|
312
319
|
}).catch(console.error);
|
|
313
320
|
}
|
|
314
321
|
|
|
315
|
-
|
|
322
|
+
// Skip low_balance check if insufficient was already triggered (insufficient takes priority)
|
|
323
|
+
if (!insufficientTriggered) {
|
|
324
|
+
await checkLowBalance(customerId, currencyId, totalCreditAmountBN.toString(), remainingBalance, context);
|
|
325
|
+
}
|
|
316
326
|
|
|
317
327
|
return {
|
|
318
328
|
consumed: totalConsumed.toString(),
|
|
@@ -396,16 +406,26 @@ async function createCreditTransaction(
|
|
|
396
406
|
fromUnitToToken(consumeAmount, context.meter.paymentCurrency.decimal),
|
|
397
407
|
context.meter.paymentCurrency.symbol
|
|
398
408
|
);
|
|
409
|
+
|
|
399
410
|
let description = `Consume ${formattedAmount}`;
|
|
400
411
|
const resolvedSubscriptionId =
|
|
401
412
|
context.meterEvent.getSubscriptionId() || creditGrant.metadata?.subscription_id || context.subscription?.id;
|
|
413
|
+
|
|
402
414
|
if (resolvedSubscriptionId) {
|
|
403
415
|
description += ' for Subscription';
|
|
404
416
|
}
|
|
417
|
+
|
|
405
418
|
if (context.meterEvent.metadata?.description) {
|
|
406
419
|
description = context.meterEvent.metadata.description;
|
|
407
420
|
}
|
|
408
421
|
|
|
422
|
+
const hasPendingBalance = new BN(context.meterEvent.credit_pending || '0').gt(new BN(0));
|
|
423
|
+
const hasFailureHistory = !!(context.meterEvent.metadata?.last_error || context.meterEvent.metadata?.failed_at);
|
|
424
|
+
const isRepayment = hasPendingBalance && hasFailureHistory;
|
|
425
|
+
if (isRepayment) {
|
|
426
|
+
description = `Repay ${formattedAmount}`;
|
|
427
|
+
}
|
|
428
|
+
|
|
409
429
|
try {
|
|
410
430
|
const transaction = await CreditTransaction.create({
|
|
411
431
|
customer_id: context.meterEvent.getCustomerId(),
|
|
@@ -425,6 +445,7 @@ async function createCreditTransaction(
|
|
|
425
445
|
...(context.meterEvent.metadata || {}),
|
|
426
446
|
meter_event_id: context.meterEvent.id,
|
|
427
447
|
grant_depleted: consumeResult.depleted,
|
|
448
|
+
is_repayment: isRepayment,
|
|
428
449
|
},
|
|
429
450
|
});
|
|
430
451
|
|