217 lines
5.7 KiB
TypeScript
217 lines
5.7 KiB
TypeScript
import {
|
|
UserType,
|
|
CallType,
|
|
TranslationType,
|
|
BillingRule,
|
|
UserAccount,
|
|
CallRecord,
|
|
BILLING_CONFIG
|
|
} from '../types/billing';
|
|
|
|
export class BillingService {
|
|
private static instance: BillingService;
|
|
private billingRules: BillingRule[] = [];
|
|
private userAccount: UserAccount | null = null;
|
|
|
|
private constructor() {
|
|
this.initializeDefaultRules();
|
|
}
|
|
|
|
public static getInstance(): BillingService {
|
|
if (!BillingService.instance) {
|
|
BillingService.instance = new BillingService();
|
|
}
|
|
return BillingService.instance;
|
|
}
|
|
|
|
// 初始化默认计费规则
|
|
private initializeDefaultRules() {
|
|
this.billingRules = [
|
|
// 普通用户规则
|
|
{
|
|
id: 'individual_voice_text',
|
|
name: '语音通话+文字翻译',
|
|
callType: CallType.VOICE,
|
|
translationType: TranslationType.TEXT,
|
|
pricePerMinute: 50,
|
|
minimumCharge: 50,
|
|
userType: UserType.INDIVIDUAL,
|
|
},
|
|
{
|
|
id: 'individual_video_sign',
|
|
name: '视频通话+手语翻译',
|
|
callType: CallType.VIDEO,
|
|
translationType: TranslationType.SIGN_LANGUAGE,
|
|
pricePerMinute: 100,
|
|
minimumCharge: 100,
|
|
userType: UserType.INDIVIDUAL,
|
|
},
|
|
{
|
|
id: 'individual_video_human',
|
|
name: '视频通话+真人翻译',
|
|
callType: CallType.VIDEO,
|
|
translationType: TranslationType.HUMAN_INTERPRETER,
|
|
pricePerMinute: 200,
|
|
minimumCharge: 200,
|
|
userType: UserType.INDIVIDUAL,
|
|
},
|
|
// 企业用户规则(相同但可能有优惠)
|
|
{
|
|
id: 'enterprise_voice_text',
|
|
name: '语音通话+文字翻译',
|
|
callType: CallType.VOICE,
|
|
translationType: TranslationType.TEXT,
|
|
pricePerMinute: 40, // 企业优惠价
|
|
minimumCharge: 40,
|
|
userType: UserType.ENTERPRISE,
|
|
},
|
|
{
|
|
id: 'enterprise_video_sign',
|
|
name: '视频通话+手语翻译',
|
|
callType: CallType.VIDEO,
|
|
translationType: TranslationType.SIGN_LANGUAGE,
|
|
pricePerMinute: 80,
|
|
minimumCharge: 80,
|
|
userType: UserType.ENTERPRISE,
|
|
},
|
|
{
|
|
id: 'enterprise_video_human',
|
|
name: '视频通话+真人翻译',
|
|
callType: CallType.VIDEO,
|
|
translationType: TranslationType.HUMAN_INTERPRETER,
|
|
pricePerMinute: 160,
|
|
minimumCharge: 160,
|
|
userType: UserType.ENTERPRISE,
|
|
},
|
|
];
|
|
}
|
|
|
|
// 设置用户账户
|
|
setUserAccount(account: UserAccount) {
|
|
this.userAccount = account;
|
|
}
|
|
|
|
// 获取用户账户
|
|
getUserAccount(): UserAccount | null {
|
|
return this.userAccount;
|
|
}
|
|
|
|
// 获取计费规则
|
|
getBillingRule(callType: CallType, translationType: TranslationType, userType: UserType): BillingRule | null {
|
|
return this.billingRules.find(rule =>
|
|
rule.callType === callType &&
|
|
rule.translationType === translationType &&
|
|
rule.userType === userType
|
|
) || null;
|
|
}
|
|
|
|
// 计算通话费用
|
|
calculateCallCost(
|
|
callType: CallType,
|
|
translationType: TranslationType,
|
|
durationMinutes: number,
|
|
interpreterRate?: number
|
|
): number {
|
|
if (!this.userAccount) return 0;
|
|
|
|
const rule = this.getBillingRule(callType, translationType, this.userAccount.userType);
|
|
if (!rule) return 0;
|
|
|
|
// 向上取整分钟数
|
|
const roundedMinutes = Math.ceil(durationMinutes);
|
|
|
|
// 基础费用
|
|
let baseCost = Math.max(roundedMinutes * rule.pricePerMinute, rule.minimumCharge);
|
|
|
|
// 如果有翻译员费用,额外计算
|
|
let interpreterCost = 0;
|
|
if (interpreterRate && translationType === TranslationType.HUMAN_INTERPRETER) {
|
|
interpreterCost = roundedMinutes * interpreterRate;
|
|
}
|
|
|
|
return baseCost + interpreterCost;
|
|
}
|
|
|
|
// 检查余额是否足够
|
|
checkBalance(
|
|
callType: CallType,
|
|
translationType: TranslationType,
|
|
minutes: number = 1,
|
|
interpreterRate?: number
|
|
): {
|
|
sufficient: boolean;
|
|
requiredAmount: number;
|
|
currentBalance: number;
|
|
} {
|
|
if (!this.userAccount) {
|
|
return { sufficient: false, requiredAmount: 0, currentBalance: 0 };
|
|
}
|
|
|
|
const requiredAmount = this.calculateCallCost(callType, translationType, minutes, interpreterRate);
|
|
|
|
return {
|
|
sufficient: this.userAccount.balance >= requiredAmount,
|
|
requiredAmount,
|
|
currentBalance: this.userAccount.balance,
|
|
};
|
|
}
|
|
|
|
// 检查是否需要低余额警告
|
|
shouldShowLowBalanceWarning(
|
|
callType: CallType,
|
|
translationType: TranslationType,
|
|
interpreterRate?: number
|
|
): boolean {
|
|
const thresholdCost = this.calculateCallCost(
|
|
callType,
|
|
translationType,
|
|
BILLING_CONFIG.LOW_BALANCE_THRESHOLD_MINUTES,
|
|
interpreterRate
|
|
);
|
|
|
|
return this.userAccount ? this.userAccount.balance < thresholdCost : true;
|
|
}
|
|
|
|
// 扣费
|
|
deductBalance(amount: number): boolean {
|
|
if (!this.userAccount || this.userAccount.balance < amount) {
|
|
return false;
|
|
}
|
|
|
|
this.userAccount.balance -= amount;
|
|
return true;
|
|
}
|
|
|
|
// 充值
|
|
recharge(amount: number): void {
|
|
if (this.userAccount) {
|
|
this.userAccount.balance += amount;
|
|
}
|
|
}
|
|
|
|
// 格式化金额(分转元)
|
|
formatAmount(cents: number): string {
|
|
return `¥${(cents / 100).toFixed(2)}`;
|
|
}
|
|
|
|
// 获取所有计费规则
|
|
getAllBillingRules(): BillingRule[] {
|
|
return this.billingRules;
|
|
}
|
|
|
|
// 获取用户可用的计费规则
|
|
getUserBillingRules(): BillingRule[] {
|
|
if (!this.userAccount) return [];
|
|
return this.billingRules.filter(rule => rule.userType === this.userAccount!.userType);
|
|
}
|
|
|
|
// 充值账户
|
|
rechargeAccount(amount: number): boolean {
|
|
if (!this.userAccount || amount <= 0) {
|
|
return false;
|
|
}
|
|
|
|
this.userAccount.balance += amount;
|
|
return true;
|
|
}
|
|
}
|