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.
- package/api/src/crons/index.ts +8 -0
- package/api/src/index.ts +4 -0
- package/api/src/libs/credit-grant.ts +146 -0
- package/api/src/libs/env.ts +1 -0
- package/api/src/libs/invoice.ts +4 -3
- package/api/src/libs/notification/template/base.ts +388 -2
- package/api/src/libs/notification/template/customer-credit-grant-granted.ts +149 -0
- package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +151 -0
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +254 -0
- package/api/src/libs/notification/template/subscription-canceled.ts +193 -202
- package/api/src/libs/notification/template/subscription-refund-succeeded.ts +215 -237
- package/api/src/libs/notification/template/subscription-renewed.ts +130 -200
- package/api/src/libs/notification/template/subscription-succeeded.ts +100 -202
- package/api/src/libs/notification/template/subscription-trial-start.ts +142 -188
- package/api/src/libs/notification/template/subscription-trial-will-end.ts +146 -174
- package/api/src/libs/notification/template/subscription-upgraded.ts +96 -192
- package/api/src/libs/notification/template/subscription-will-canceled.ts +94 -135
- package/api/src/libs/notification/template/subscription-will-renew.ts +220 -245
- package/api/src/libs/payment.ts +69 -0
- package/api/src/libs/queue/index.ts +3 -2
- package/api/src/libs/session.ts +8 -0
- package/api/src/libs/subscription.ts +74 -3
- package/api/src/libs/util.ts +3 -1
- package/api/src/libs/ws.ts +23 -1
- package/api/src/locales/en.ts +33 -0
- package/api/src/locales/zh.ts +31 -0
- package/api/src/queues/credit-consume.ts +728 -0
- package/api/src/queues/credit-grant.ts +572 -0
- package/api/src/queues/notification.ts +173 -128
- package/api/src/queues/payment.ts +210 -122
- package/api/src/queues/subscription.ts +179 -0
- package/api/src/routes/checkout-sessions.ts +157 -9
- package/api/src/routes/connect/shared.ts +3 -2
- package/api/src/routes/credit-grants.ts +241 -0
- package/api/src/routes/credit-transactions.ts +208 -0
- package/api/src/routes/customers.ts +34 -5
- package/api/src/routes/index.ts +8 -0
- package/api/src/routes/meter-events.ts +347 -0
- package/api/src/routes/meters.ts +219 -0
- package/api/src/routes/payment-currencies.ts +20 -2
- package/api/src/routes/payment-links.ts +1 -1
- package/api/src/routes/payment-methods.ts +14 -2
- package/api/src/routes/prices.ts +43 -0
- package/api/src/routes/pricing-table.ts +13 -7
- package/api/src/routes/products.ts +63 -4
- package/api/src/routes/settings.ts +1 -1
- package/api/src/routes/subscriptions.ts +4 -0
- package/api/src/routes/webhook-endpoints.ts +0 -3
- package/api/src/store/migrations/20250610-billing-credit.ts +43 -0
- package/api/src/store/models/credit-grant.ts +486 -0
- package/api/src/store/models/credit-transaction.ts +268 -0
- package/api/src/store/models/customer.ts +8 -0
- package/api/src/store/models/index.ts +52 -1
- package/api/src/store/models/meter-event.ts +423 -0
- package/api/src/store/models/meter.ts +176 -0
- package/api/src/store/models/payment-currency.ts +66 -14
- package/api/src/store/models/price.ts +6 -0
- package/api/src/store/models/product.ts +2 -2
- package/api/src/store/models/subscription.ts +24 -0
- package/api/src/store/models/types.ts +28 -2
- package/api/tests/libs/subscription.spec.ts +53 -0
- package/blocklet.yml +9 -1
- package/package.json +4 -4
- package/scripts/sdk.js +233 -1
- package/src/app.tsx +10 -0
- package/src/components/collapse.tsx +11 -1
- package/src/components/conditional-section.tsx +87 -0
- package/src/components/customer/credit-grant-item-list.tsx +99 -0
- package/src/components/customer/credit-overview.tsx +246 -0
- package/src/components/customer/form.tsx +7 -3
- package/src/components/invoice/list.tsx +19 -1
- package/src/components/metadata/form.tsx +287 -91
- package/src/components/meter/actions.tsx +101 -0
- package/src/components/meter/add-usage-dialog.tsx +239 -0
- package/src/components/meter/events-list.tsx +657 -0
- package/src/components/meter/form.tsx +245 -0
- package/src/components/meter/products.tsx +264 -0
- package/src/components/meter/usage-guide.tsx +174 -0
- package/src/components/payment-currency/form.tsx +2 -0
- package/src/components/payment-intent/list.tsx +19 -1
- package/src/components/payment-link/item.tsx +2 -2
- package/src/components/payment-link/preview.tsx +1 -1
- package/src/components/payment-link/product-select.tsx +52 -12
- package/src/components/payment-method/arcblock.tsx +2 -0
- package/src/components/payment-method/base.tsx +2 -0
- package/src/components/payment-method/bitcoin.tsx +2 -0
- package/src/components/payment-method/ethereum.tsx +2 -0
- package/src/components/payment-method/stripe.tsx +2 -0
- package/src/components/payouts/list.tsx +19 -1
- package/src/components/payouts/portal/list.tsx +6 -11
- package/src/components/price/currency-select.tsx +56 -32
- package/src/components/price/form.tsx +912 -407
- package/src/components/pricing-table/preview.tsx +1 -1
- package/src/components/product/add-price.tsx +9 -7
- package/src/components/product/create.tsx +7 -4
- package/src/components/product/edit-price.tsx +21 -12
- package/src/components/product/features.tsx +17 -7
- package/src/components/product/form.tsx +100 -90
- package/src/components/refund/list.tsx +19 -1
- package/src/components/section/header.tsx +5 -18
- package/src/components/subscription/items/index.tsx +1 -1
- package/src/components/subscription/metrics.tsx +37 -5
- package/src/components/subscription/portal/actions.tsx +2 -1
- package/src/contexts/products.tsx +26 -9
- package/src/hooks/subscription.ts +34 -0
- package/src/libs/meter-utils.ts +196 -0
- package/src/libs/util.ts +4 -0
- package/src/locales/en.tsx +389 -5
- package/src/locales/zh.tsx +368 -1
- package/src/pages/admin/billing/index.tsx +61 -33
- package/src/pages/admin/billing/invoices/detail.tsx +1 -1
- package/src/pages/admin/billing/meters/create.tsx +60 -0
- package/src/pages/admin/billing/meters/detail.tsx +435 -0
- package/src/pages/admin/billing/meters/index.tsx +210 -0
- package/src/pages/admin/billing/meters/meter-event.tsx +346 -0
- package/src/pages/admin/billing/subscriptions/detail.tsx +47 -14
- package/src/pages/admin/customers/customers/credit-grant/detail.tsx +391 -0
- package/src/pages/admin/customers/customers/detail.tsx +14 -10
- package/src/pages/admin/customers/index.tsx +5 -0
- package/src/pages/admin/developers/events/detail.tsx +1 -1
- package/src/pages/admin/developers/index.tsx +1 -1
- package/src/pages/admin/payments/intents/detail.tsx +1 -1
- package/src/pages/admin/payments/payouts/detail.tsx +1 -1
- package/src/pages/admin/payments/refunds/detail.tsx +1 -1
- package/src/pages/admin/products/index.tsx +3 -2
- package/src/pages/admin/products/links/detail.tsx +1 -1
- package/src/pages/admin/products/prices/actions.tsx +16 -4
- package/src/pages/admin/products/prices/detail.tsx +30 -3
- package/src/pages/admin/products/prices/list.tsx +8 -1
- package/src/pages/admin/products/pricing-tables/detail.tsx +1 -1
- package/src/pages/admin/products/products/create.tsx +233 -57
- package/src/pages/admin/products/products/detail.tsx +2 -1
- package/src/pages/admin/settings/payment-methods/index.tsx +3 -0
- package/src/pages/customer/credit-grant/detail.tsx +308 -0
- package/src/pages/customer/index.tsx +44 -9
- package/src/pages/customer/recharge/account.tsx +5 -5
- package/src/pages/customer/subscription/change-payment.tsx +4 -2
- package/src/pages/customer/subscription/detail.tsx +48 -14
- package/src/pages/customer/subscription/embed.tsx +1 -1
package/api/src/libs/payment.ts
CHANGED
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
TCustomer,
|
|
20
20
|
TLineItemExpanded,
|
|
21
21
|
Payout,
|
|
22
|
+
CreditGrant,
|
|
23
|
+
Customer,
|
|
22
24
|
} from '../store/models';
|
|
23
25
|
import type { TPaymentCurrency } from '../store/models/payment-currency';
|
|
24
26
|
import { blocklet, ethWallet, wallet, getVaultAddress } from './auth';
|
|
@@ -26,6 +28,7 @@ import logger from './logger';
|
|
|
26
28
|
import { getBlockletJson, getUserOrAppInfo, OCAP_PAYMENT_TX_TYPE, resolveAddressChainTypes } from './util';
|
|
27
29
|
import { CHARGE_SUPPORTED_CHAIN_TYPES, EVM_CHAIN_TYPES } from './constants';
|
|
28
30
|
import { getTokenByAddress } from '../integrations/arcblock/stake';
|
|
31
|
+
import { isCreditMetered } from './session';
|
|
29
32
|
|
|
30
33
|
export interface SufficientForPaymentResult {
|
|
31
34
|
sufficient: boolean;
|
|
@@ -38,6 +41,10 @@ export interface SufficientForPaymentResult {
|
|
|
38
41
|
| 'NO_TOKEN'
|
|
39
42
|
| 'NO_ENOUGH_ALLOWANCE'
|
|
40
43
|
| 'NO_ENOUGH_TOKEN'
|
|
44
|
+
| 'NO_CUSTOMER'
|
|
45
|
+
| 'NO_CREDIT_GRANTS'
|
|
46
|
+
| 'NO_ENOUGH_CREDIT'
|
|
47
|
+
| 'CREDIT_CHECK_ERROR'
|
|
41
48
|
| 'NOT_SUPPORTED',
|
|
42
49
|
string
|
|
43
50
|
>;
|
|
@@ -139,12 +146,61 @@ export async function checkTokenBalance(args: {
|
|
|
139
146
|
};
|
|
140
147
|
}
|
|
141
148
|
|
|
149
|
+
export async function isCreditGrantSufficientForPayment(args: {
|
|
150
|
+
paymentMethod: PaymentMethod;
|
|
151
|
+
paymentCurrency: TPaymentCurrency;
|
|
152
|
+
userDid: string;
|
|
153
|
+
amount: string;
|
|
154
|
+
lineItems?: TLineItemExpanded[];
|
|
155
|
+
}): Promise<SufficientForPaymentResult> {
|
|
156
|
+
const { paymentMethod, paymentCurrency, userDid, amount, lineItems } = args;
|
|
157
|
+
if (paymentMethod.type === 'arcblock' && paymentCurrency.type === 'credit') {
|
|
158
|
+
const customer = await Customer.findOne({ where: { did: userDid } });
|
|
159
|
+
if (!customer) {
|
|
160
|
+
return { sufficient: false, reason: 'NO_CUSTOMER' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const priceIds: string[] = [];
|
|
164
|
+
if (lineItems && lineItems.length > 0) {
|
|
165
|
+
lineItems.forEach((item) => {
|
|
166
|
+
if (isCreditMetered(item.price)) {
|
|
167
|
+
priceIds.push(item.price_id);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const creditSummary = await CreditGrant.getEffectiveCreditSummary({
|
|
173
|
+
customerId: customer.id,
|
|
174
|
+
currencyId: paymentCurrency.id,
|
|
175
|
+
priceIds,
|
|
176
|
+
});
|
|
177
|
+
if (!creditSummary) {
|
|
178
|
+
return { sufficient: false, reason: 'NO_CREDIT_GRANTS' };
|
|
179
|
+
}
|
|
180
|
+
const availableCredit = new BN(creditSummary[paymentCurrency.id]?.remainingAmount || '0');
|
|
181
|
+
if (availableCredit.lt(new BN(amount))) {
|
|
182
|
+
return { sufficient: false, reason: 'NO_ENOUGH_CREDIT' };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
sufficient: true,
|
|
187
|
+
token: { address: paymentCurrency.id, balance: availableCredit.toString() },
|
|
188
|
+
requestedAmount: amount,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
sufficient: false,
|
|
193
|
+
reason: 'NOT_SUPPORTED',
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
142
197
|
export async function isDelegationSufficientForPayment(args: {
|
|
143
198
|
paymentMethod: PaymentMethod;
|
|
144
199
|
paymentCurrency: TPaymentCurrency;
|
|
145
200
|
userDid: string;
|
|
146
201
|
amount: string;
|
|
147
202
|
delegatorAmounts?: string[];
|
|
203
|
+
lineItems?: TLineItemExpanded[];
|
|
148
204
|
}): Promise<SufficientForPaymentResult> {
|
|
149
205
|
const { paymentCurrency, paymentMethod, userDid, amount, delegatorAmounts } = args;
|
|
150
206
|
const tokenAddress = paymentCurrency.contract as string;
|
|
@@ -156,6 +212,19 @@ export async function isDelegationSufficientForPayment(args: {
|
|
|
156
212
|
}
|
|
157
213
|
|
|
158
214
|
if (paymentMethod.type === 'arcblock') {
|
|
215
|
+
// Check if this is a credit currency type
|
|
216
|
+
if (paymentCurrency.type === 'credit') {
|
|
217
|
+
const result = await isCreditGrantSufficientForPayment({
|
|
218
|
+
paymentMethod,
|
|
219
|
+
paymentCurrency,
|
|
220
|
+
userDid,
|
|
221
|
+
amount,
|
|
222
|
+
lineItems: args.lineItems,
|
|
223
|
+
});
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Regular token handling for non-credit currencies
|
|
159
228
|
// user have bond wallet did?
|
|
160
229
|
const { user } = await blocklet.getUser(userDid, { enableConnectedAccount: true });
|
|
161
230
|
const delegator = getWalletDid(user);
|
|
@@ -8,6 +8,7 @@ import { nanoid } from 'nanoid';
|
|
|
8
8
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
9
9
|
import logger from '../logger';
|
|
10
10
|
import { sleep, tryWithTimeout } from '../util';
|
|
11
|
+
import dayjs from '../dayjs';
|
|
11
12
|
import createQueueStore from './store';
|
|
12
13
|
import { Job } from '../../store/models/job';
|
|
13
14
|
import { sequelize } from '../../store/sequelize';
|
|
@@ -112,6 +113,7 @@ export default function createQueue<T = any>({ name, onJob, options = defaults }
|
|
|
112
113
|
queueEvents.emit(e, data);
|
|
113
114
|
jobEvents.emit(e, data);
|
|
114
115
|
};
|
|
116
|
+
const now = dayjs().unix();
|
|
115
117
|
|
|
116
118
|
if (!job) {
|
|
117
119
|
throw new Error('Can not queue empty job');
|
|
@@ -119,7 +121,7 @@ export default function createQueue<T = any>({ name, onJob, options = defaults }
|
|
|
119
121
|
|
|
120
122
|
const jobId = getJobId(id, job);
|
|
121
123
|
|
|
122
|
-
if (delay || runAt) {
|
|
124
|
+
if ((delay && delay >= MIN_DELAY) || (runAt && runAt > now)) {
|
|
123
125
|
if (!enableScheduledJob) {
|
|
124
126
|
throw new Error('Must set options.enableScheduledJob to true to run delay jobs');
|
|
125
127
|
}
|
|
@@ -135,7 +137,6 @@ export default function createQueue<T = any>({ name, onJob, options = defaults }
|
|
|
135
137
|
attrs.will_run_at = Date.now() + delay * 1000;
|
|
136
138
|
}
|
|
137
139
|
if (runAt) {
|
|
138
|
-
// 如果 runAt 大于当前时间,则延迟执行,否则直接执行
|
|
139
140
|
attrs.delay = 1;
|
|
140
141
|
attrs.will_run_at = runAt * 1000;
|
|
141
142
|
}
|
package/api/src/libs/session.ts
CHANGED
|
@@ -975,3 +975,11 @@ export async function getSubscriptionLineItems(
|
|
|
975
975
|
|
|
976
976
|
return subItems;
|
|
977
977
|
}
|
|
978
|
+
|
|
979
|
+
export function isCreditMetered(price: TPrice | TPriceExpanded) {
|
|
980
|
+
return price.type === 'recurring' && price.recurring?.usage_type === 'metered' && !!price.recurring?.meter_id;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
export function isCreditMeteredLineItems(lineItems: TLineItemExpanded[]) {
|
|
984
|
+
return lineItems.every((item) => item.price && isCreditMetered(item.price));
|
|
985
|
+
}
|
|
@@ -189,15 +189,76 @@ export function getSubscriptionStakeSetup(items: TLineItemExpanded[], currencyId
|
|
|
189
189
|
return staking;
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
export function getSubscriptionCycleSetup(
|
|
192
|
+
export function getSubscriptionCycleSetup(
|
|
193
|
+
recurring: PriceRecurring,
|
|
194
|
+
previousPeriodEnd: number,
|
|
195
|
+
options?: {
|
|
196
|
+
catchUp?: boolean;
|
|
197
|
+
maxMissedPeriods?: number;
|
|
198
|
+
}
|
|
199
|
+
) {
|
|
193
200
|
const cycle = getRecurringPeriod(recurring);
|
|
201
|
+
const now = dayjs().unix();
|
|
202
|
+
|
|
203
|
+
// Basic setup without catch-up
|
|
204
|
+
if (!options?.catchUp) {
|
|
205
|
+
return {
|
|
206
|
+
recurring,
|
|
207
|
+
cycle,
|
|
208
|
+
period: {
|
|
209
|
+
start: previousPeriodEnd,
|
|
210
|
+
end: dayjs.unix(previousPeriodEnd).add(cycle, 'millisecond').unix(),
|
|
211
|
+
},
|
|
212
|
+
missedPeriods: 0,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Calculate missed periods if catch-up is enabled
|
|
217
|
+
const expectedEnd = dayjs.unix(previousPeriodEnd).add(cycle, 'millisecond').unix();
|
|
218
|
+
|
|
219
|
+
if (now <= expectedEnd) {
|
|
220
|
+
// No catch-up needed
|
|
221
|
+
return {
|
|
222
|
+
recurring,
|
|
223
|
+
cycle,
|
|
224
|
+
period: {
|
|
225
|
+
start: previousPeriodEnd,
|
|
226
|
+
end: expectedEnd,
|
|
227
|
+
},
|
|
228
|
+
missedPeriods: 0,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Calculate how many periods we've missed
|
|
233
|
+
const timeSinceExpected = now - expectedEnd;
|
|
234
|
+
const cycleInSeconds = cycle / 1000;
|
|
235
|
+
const missedPeriods = Math.floor(timeSinceExpected / cycleInSeconds);
|
|
236
|
+
const maxMissedPeriods = options.maxMissedPeriods || 100; // Default limit to prevent excessive catch-up
|
|
237
|
+
|
|
238
|
+
const actualMissedPeriods = Math.min(missedPeriods, maxMissedPeriods);
|
|
239
|
+
|
|
240
|
+
// Calculate the current period we should be in
|
|
241
|
+
const currentPeriodStart = dayjs
|
|
242
|
+
.unix(previousPeriodEnd)
|
|
243
|
+
.add((actualMissedPeriods + 1) * cycle, 'millisecond')
|
|
244
|
+
.unix();
|
|
245
|
+
const currentPeriodEnd = dayjs.unix(currentPeriodStart).add(cycle, 'millisecond').unix();
|
|
194
246
|
|
|
195
247
|
return {
|
|
196
248
|
recurring,
|
|
197
249
|
cycle,
|
|
198
250
|
period: {
|
|
199
|
-
start:
|
|
200
|
-
end:
|
|
251
|
+
start: currentPeriodStart,
|
|
252
|
+
end: currentPeriodEnd,
|
|
253
|
+
},
|
|
254
|
+
missedPeriods: actualMissedPeriods,
|
|
255
|
+
// Additional info for recovery handling
|
|
256
|
+
recovery: {
|
|
257
|
+
originalPeriodEnd: previousPeriodEnd,
|
|
258
|
+
expectedPeriodEnd: expectedEnd,
|
|
259
|
+
timeMissed: timeSinceExpected,
|
|
260
|
+
periodsSkipped: actualMissedPeriods,
|
|
261
|
+
wasLimited: missedPeriods > maxMissedPeriods,
|
|
201
262
|
},
|
|
202
263
|
};
|
|
203
264
|
}
|
|
@@ -1627,3 +1688,13 @@ export function calculateRecommendedRechargeAmount(subscriptions: any[], currenc
|
|
|
1627
1688
|
interval: recommendedInterval,
|
|
1628
1689
|
};
|
|
1629
1690
|
}
|
|
1691
|
+
|
|
1692
|
+
export async function getMeterPriceIdsFromSubscription(subscription: Subscription): Promise<string[]> {
|
|
1693
|
+
const subscriptionItems = (await SubscriptionItem.findAll({
|
|
1694
|
+
where: { subscription_id: subscription.id },
|
|
1695
|
+
include: [{ model: Price, as: 'price' }],
|
|
1696
|
+
})) as (SubscriptionItem & { price: Price })[];
|
|
1697
|
+
|
|
1698
|
+
const meterItems = subscriptionItems.filter((x) => x.price.recurring?.meter_id);
|
|
1699
|
+
return meterItems.map((m) => m.price.id);
|
|
1700
|
+
}
|
package/api/src/libs/util.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { joinURL, withQuery, withTrailingSlash } from 'ufo';
|
|
|
12
12
|
import axios from 'axios';
|
|
13
13
|
import { ethers } from 'ethers';
|
|
14
14
|
import { fromUnitToToken } from '@ocap/util';
|
|
15
|
+
import get from 'lodash/get';
|
|
15
16
|
import dayjs from './dayjs';
|
|
16
17
|
import { blocklet, wallet } from './auth';
|
|
17
18
|
import type { PaymentCurrency, PaymentMethod, Subscription } from '../store/models';
|
|
@@ -268,11 +269,12 @@ export async function getUserOrAppInfo(
|
|
|
268
269
|
}
|
|
269
270
|
const { user } = await blocklet.getUser(address);
|
|
270
271
|
if (user) {
|
|
272
|
+
const locale = get(user, 'locale', 'en');
|
|
271
273
|
return {
|
|
272
274
|
name: user?.fullName,
|
|
273
275
|
avatar: joinURL(process.env.BLOCKLET_APP_URL!, user?.avatar),
|
|
274
276
|
type: 'user',
|
|
275
|
-
url: getCustomerProfileUrl({ userDid: address, locale
|
|
277
|
+
url: getCustomerProfileUrl({ userDid: address, locale }),
|
|
276
278
|
};
|
|
277
279
|
}
|
|
278
280
|
return {
|
package/api/src/libs/ws.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { sendToRelay } from '@blocklet/sdk/service/notification';
|
|
2
2
|
import { publish } from '@blocklet/sdk/service/eventbus';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
CheckoutSession,
|
|
5
|
+
Invoice,
|
|
6
|
+
PaymentIntent,
|
|
7
|
+
Subscription,
|
|
8
|
+
Refund,
|
|
9
|
+
CreditGrant,
|
|
10
|
+
Customer,
|
|
11
|
+
} from '../store/models';
|
|
4
12
|
import { events } from './event';
|
|
5
13
|
|
|
6
14
|
export function broadcast(eventName: string, data: any, extraParams?: Record<string, any>) {
|
|
@@ -93,4 +101,18 @@ export function initEventBroadcast() {
|
|
|
93
101
|
events.on('manual.notification', (data: Record<string, any>) => {
|
|
94
102
|
broadcast('manual.notification', data);
|
|
95
103
|
});
|
|
104
|
+
|
|
105
|
+
// credit
|
|
106
|
+
events.on('customer.credit_grant.granted', (data: CreditGrant, extraParams?: Record<string, any>) => {
|
|
107
|
+
broadcast('customer.credit_grant.granted', data, extraParams);
|
|
108
|
+
});
|
|
109
|
+
events.on('customer.credit_grant.low_balance', (data: CreditGrant, extraParams?: Record<string, any>) => {
|
|
110
|
+
broadcast('customer.credit_grant.low_balance', data, extraParams);
|
|
111
|
+
});
|
|
112
|
+
events.on('customer.credit_grant.depleted', (data: CreditGrant, extraParams?: Record<string, any>) => {
|
|
113
|
+
broadcast('customer.credit_grant.depleted', data, extraParams);
|
|
114
|
+
});
|
|
115
|
+
events.on('customer.credit.insufficient', (data: Customer, extraParams?: Record<string, any>) => {
|
|
116
|
+
broadcast('customer.credit.insufficient', data, extraParams);
|
|
117
|
+
});
|
|
96
118
|
}
|
package/api/src/locales/en.ts
CHANGED
|
@@ -48,6 +48,7 @@ export default flat({
|
|
|
48
48
|
subscriptionId: 'Subscription ID',
|
|
49
49
|
shouldPayAmount: 'Should pay amount',
|
|
50
50
|
billedAmount: 'Billed amount',
|
|
51
|
+
viewCreditGrant: 'View Credit Balance',
|
|
51
52
|
},
|
|
52
53
|
|
|
53
54
|
billingDiscrepancy: {
|
|
@@ -217,5 +218,37 @@ export default flat({
|
|
|
217
218
|
title: '{count} Subscriptions Renewed',
|
|
218
219
|
body: 'During {startTime} - {endTime}, {count} subscriptions were successfully renewed, total amount: {totalAmount}.\n\nSubscription List:\n{subscriptionList}',
|
|
219
220
|
},
|
|
221
|
+
|
|
222
|
+
creditInsufficient: {
|
|
223
|
+
title: 'Insufficient Credit',
|
|
224
|
+
bodyWithSubscription:
|
|
225
|
+
'Your available credit is only {availableAmount}, which is not enough to cover your subscription to {subscriptionName}. Please top up to avoid service interruption.',
|
|
226
|
+
bodyWithoutSubscription:
|
|
227
|
+
'Your available credit is only {availableAmount}, which is not enough to continue using the service. Please top up to avoid restrictions.',
|
|
228
|
+
exhaustedTitle: 'Credit Exhausted – Please Top Up',
|
|
229
|
+
exhaustedBodyWithSubscription:
|
|
230
|
+
'Your credit is fully exhausted and can no longer cover your subscription to {subscriptionName}. Please top up to avoid service interruption.',
|
|
231
|
+
exhaustedBodyWithoutSubscription:
|
|
232
|
+
'Your credit is fully exhausted (remaining balance: 0). Please top up to ensure uninterrupted service.',
|
|
233
|
+
meterEventName: 'Service',
|
|
234
|
+
availableCredit: 'Available Credit',
|
|
235
|
+
requiredCredit: 'Required Credit',
|
|
236
|
+
topUpNow: 'Top Up Now',
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
creditGrantGranted: {
|
|
240
|
+
title: 'Congratulations! Your granted credit is now active',
|
|
241
|
+
body: 'You have been granted {grantedAmount} credit, activated on {at}, valid until {expiresAt}. Enjoy your service!',
|
|
242
|
+
bodyNoExpire: 'You have been granted {grantedAmount} credit, activated on {at}. Enjoy your service!',
|
|
243
|
+
grantedCredit: 'Granted Credit',
|
|
244
|
+
validUntil: 'Valid Until',
|
|
245
|
+
neverExpires: 'Never Expires',
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
creditGrantLowBalance: {
|
|
249
|
+
title: 'Low Granted Credit Balance Warning',
|
|
250
|
+
body: 'Your granted credit balance is below 10%. Current available credit is {availableAmount}. Please top up or contact support to avoid service interruption.',
|
|
251
|
+
totalGrantedCredit: 'Total Granted Credit',
|
|
252
|
+
},
|
|
220
253
|
},
|
|
221
254
|
});
|
package/api/src/locales/zh.ts
CHANGED
|
@@ -48,6 +48,7 @@ export default flat({
|
|
|
48
48
|
subscriptionId: '订阅 ID',
|
|
49
49
|
shouldPayAmount: '应收金额',
|
|
50
50
|
billedAmount: '实缴金额',
|
|
51
|
+
viewCreditGrant: '查看额度',
|
|
51
52
|
},
|
|
52
53
|
|
|
53
54
|
sendTo: '发送给',
|
|
@@ -211,5 +212,35 @@ export default flat({
|
|
|
211
212
|
title: '{count} 个订阅续费成功',
|
|
212
213
|
body: '在 {startTime} - {endTime} 期间,共有 {count} 个订阅成功续费,总金额:{totalAmount}。\n\n订阅清单:\n{subscriptionList}',
|
|
213
214
|
},
|
|
215
|
+
|
|
216
|
+
creditInsufficient: {
|
|
217
|
+
title: '额度不足,服务可能受限',
|
|
218
|
+
bodyWithSubscription:
|
|
219
|
+
'您的信用额度仅剩 {availableAmount},不足以支付您订阅的 {subscriptionName}。请及时充值以避免服务中断。',
|
|
220
|
+
bodyWithoutSubscription: '您的信用额度仅剩 {availableAmount},不足以继续使用服务。请及时充值以避免服务受限。',
|
|
221
|
+
exhaustedTitle: '额度已用尽,请尽快充值',
|
|
222
|
+
exhaustedBodyWithSubscription:
|
|
223
|
+
'您的信用额度已用尽,无法继续支付您订阅的 {subscriptionName}。为确保服务不中断,请及时充值。',
|
|
224
|
+
exhaustedBodyWithoutSubscription: '您的信用额度已用尽(剩余额度为 0)。为确保服务正常使用,请及时充值。',
|
|
225
|
+
meterEventName: '服务项目',
|
|
226
|
+
availableCredit: '剩余额度',
|
|
227
|
+
requiredCredit: '所需额度',
|
|
228
|
+
topUpNow: '立即充值',
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
creditGrantGranted: {
|
|
232
|
+
title: '恭喜!您的额度授予已激活',
|
|
233
|
+
body: '您已获得 {grantedAmount} 的额度授予,激活时间为 {at},有效期至 {expiresAt}。祝您使用愉快!',
|
|
234
|
+
bodyNoExpire: '您已获得 {grantedAmount} 的额度授予,激活时间为 {at}。祝您使用愉快!',
|
|
235
|
+
grantedCredit: '授予额度',
|
|
236
|
+
validUntil: '有效期至',
|
|
237
|
+
neverExpires: '永不过期',
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
creditGrantLowBalance: {
|
|
241
|
+
title: '额度授予余额不足提醒',
|
|
242
|
+
body: '您的额度授予余额已低于 10%,当前剩余额度为 {availableAmount}。请及时充值或联系管理员以避免服务受限。',
|
|
243
|
+
totalGrantedCredit: '总授予额度',
|
|
244
|
+
},
|
|
214
245
|
},
|
|
215
246
|
});
|