revert: 回滚 settleToBalance 的直接 Prisma 实现,准备用 Unit of Work 模式重新实现
This commit is contained in:
parent
4c6e64a604
commit
7dc25b75d2
|
|
@ -909,89 +909,42 @@ export class WalletApplicationService {
|
||||||
}> {
|
}> {
|
||||||
this.logger.log(`Settling ${params.usdtAmount} USDT to balance for ${params.accountSequence}`);
|
this.logger.log(`Settling ${params.usdtAmount} USDT to balance for ${params.accountSequence}`);
|
||||||
|
|
||||||
// 生成结算ID
|
|
||||||
const settlementId = `STL_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
||||||
let balanceAfter = 0;
|
|
||||||
let userId: bigint = BigInt(0);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用事务确保账户余额更新和流水记录的原子性
|
// 1. 查找钱包
|
||||||
await this.prisma.$transaction(async (tx) => {
|
const wallet = await this.walletRepo.findByAccountSequence(params.accountSequence);
|
||||||
// 1. 查找钱包
|
if (!wallet) {
|
||||||
const walletRecord = await tx.walletAccount.findUnique({
|
throw new WalletNotFoundError(`accountSequence: ${params.accountSequence}`);
|
||||||
where: { accountSequence: params.accountSequence },
|
}
|
||||||
});
|
|
||||||
if (!walletRecord) {
|
|
||||||
throw new WalletNotFoundError(`accountSequence: ${params.accountSequence}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
userId = walletRecord.userId;
|
const usdtAmount = Money.USDT(params.usdtAmount);
|
||||||
|
const userId = wallet.userId.value;
|
||||||
|
|
||||||
// 2. 验证钱包状态是否 ACTIVE
|
// 2. 生成结算ID
|
||||||
if (walletRecord.status !== 'ACTIVE') {
|
const settlementId = `STL_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||||
throw new Error(`Wallet is not active: ${walletRecord.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Decimal = (await import('decimal.js')).default;
|
// 3. 执行钱包结算
|
||||||
const usdtAmountDecimal = new Decimal(params.usdtAmount);
|
wallet.settleToBalance(usdtAmount, settlementId);
|
||||||
const currentSettleable = new Decimal(walletRecord.settleableUsdt.toString());
|
await this.walletRepo.save(wallet);
|
||||||
const currentAvailable = new Decimal(walletRecord.usdtAvailable.toString());
|
|
||||||
const currentSettledTotal = new Decimal(walletRecord.settledTotalUsdt.toString());
|
|
||||||
|
|
||||||
// 3. 验证可结算余额足够
|
// 4. 记录账本流水(含详细来源信息)
|
||||||
if (currentSettleable.lessThan(usdtAmountDecimal)) {
|
const ledgerEntry = LedgerEntry.create({
|
||||||
throw new Error(`Insufficient settleable balance: ${currentSettleable} < ${usdtAmountDecimal}`);
|
accountSequence: wallet.accountSequence,
|
||||||
}
|
userId: UserId.create(userId),
|
||||||
|
entryType: LedgerEntryType.REWARD_SETTLED,
|
||||||
// 4. 计算新余额
|
amount: usdtAmount,
|
||||||
const newSettleable = currentSettleable.minus(usdtAmountDecimal);
|
balanceAfter: wallet.balances.usdt.available,
|
||||||
const newAvailable = currentAvailable.plus(usdtAmountDecimal);
|
refOrderId: settlementId,
|
||||||
const newSettledTotal = currentSettledTotal.plus(usdtAmountDecimal);
|
memo: params.memo || `结算 ${params.usdtAmount} 绿积分到钱包余额`,
|
||||||
balanceAfter = newAvailable.toNumber();
|
payloadJson: {
|
||||||
|
settlementType: 'SETTLE_TO_BALANCE',
|
||||||
// 5. 更新钱包账户(在事务内,使用乐观锁)
|
rewardEntryIds: params.rewardEntryIds,
|
||||||
const currentVersion = walletRecord.version;
|
rewardCount: params.rewardEntryIds.length,
|
||||||
const updateResult = await tx.walletAccount.updateMany({
|
breakdown: params.breakdown,
|
||||||
where: {
|
},
|
||||||
accountSequence: params.accountSequence,
|
|
||||||
version: currentVersion, // 乐观锁:只有版本匹配才更新
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
settleableUsdt: newSettleable.toFixed(8),
|
|
||||||
usdtAvailable: newAvailable.toFixed(8),
|
|
||||||
settledTotalUsdt: newSettledTotal.toFixed(8),
|
|
||||||
version: currentVersion + 1,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (updateResult.count === 0) {
|
|
||||||
throw new Error(`Optimistic lock conflict for wallet ${params.accountSequence}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 创建流水记录(在事务内)
|
|
||||||
await tx.ledgerEntry.create({
|
|
||||||
data: {
|
|
||||||
accountSequence: params.accountSequence,
|
|
||||||
userId: walletRecord.userId,
|
|
||||||
entryType: LedgerEntryType.REWARD_SETTLED,
|
|
||||||
amount: usdtAmountDecimal.toFixed(8),
|
|
||||||
assetType: 'USDT',
|
|
||||||
balanceAfter: newAvailable.toFixed(8),
|
|
||||||
refOrderId: settlementId,
|
|
||||||
memo: params.memo || `结算 ${params.usdtAmount} 绿积分到钱包余额`,
|
|
||||||
payloadJson: {
|
|
||||||
settlementType: 'SETTLE_TO_BALANCE',
|
|
||||||
rewardEntryIds: params.rewardEntryIds,
|
|
||||||
rewardCount: params.rewardEntryIds.length,
|
|
||||||
breakdown: params.breakdown,
|
|
||||||
},
|
|
||||||
createdAt: new Date(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
await this.ledgerRepo.save(ledgerEntry);
|
||||||
|
|
||||||
// 7. 使缓存失效(在事务外,事务成功后执行)
|
// 5. 使缓存失效
|
||||||
await this.walletCacheService.invalidateWallet(userId);
|
await this.walletCacheService.invalidateWallet(userId);
|
||||||
|
|
||||||
this.logger.log(`Successfully settled ${params.usdtAmount} USDT to balance for ${params.accountSequence}`);
|
this.logger.log(`Successfully settled ${params.usdtAmount} USDT to balance for ${params.accountSequence}`);
|
||||||
|
|
@ -1000,7 +953,7 @@ export class WalletApplicationService {
|
||||||
success: true,
|
success: true,
|
||||||
settlementId,
|
settlementId,
|
||||||
settledAmount: params.usdtAmount,
|
settledAmount: params.usdtAmount,
|
||||||
balanceAfter,
|
balanceAfter: wallet.balances.usdt.available.value,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(`Failed to settle to balance for ${params.accountSequence}: ${error.message}`);
|
this.logger.error(`Failed to settle to balance for ${params.accountSequence}: ${error.message}`);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue