fix(wallet-service): 添加钱包状态检查,确保只有 ACTIVE 钱包可结算

This commit is contained in:
hailin 2026-01-06 06:50:08 -08:00
parent 5204d24c88
commit 65cb574f59
2 changed files with 13 additions and 6 deletions

View File

@ -596,7 +596,8 @@
"Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(authorization\\): 火柴人排名过滤已撤销授权的考核记录\n\n- findRankingsByMonthAndRegion 和 findRankingsByMonthAndRoleType 增加过滤条件\n- 排除 authorization.status = ''REVOKED'' 的记录\n- 解决同一用户因有多条授权记录(含已撤销)而重复显示的问题\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(reward-service\\): 修复 WalletServiceClient 未正确解析 wallet-service 响应格式的 Bug\n\n问题原因:\nwallet-service 使用全局 TransformInterceptor 拦截器,会将所有响应包装成:\n{ success: true, data: { success: boolean, ... }, timestamp: \"...\" }\n\n原代码直接读取外层的 success 字段(始终为 true导致即使业务失败\n内层 data.success = false也被误判为成功。\n\n具体案例:\n用户 D25122700024 点击结算时wallet-service 因余额不足返回:\n{ success: true, data: { success: false, error: \"Insufficient...\" }, ... }\nreward-service 误读为成功,导致奖励被标记为 SETTLED 但钱包余额未变更。\n\n修复内容:\n1. settleToBalance: 解析 response_data.data 获取真实业务结果\n2. confirmPlantingDeduction: 同上\n3. allocateFunds: 同上\n\n所有方法现在会:\n- 使用 response_data.data || response_data 兼容包装和非包装格式\n- 严格检查 data.success !== true 来判断业务是否成功\n- 失败时记录详细错误日志\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(wallet-service\\): 统一奖励分配到 settleable_usdt与 reward-service 保持一致\n\n问题原因:\nwallet-service 对不同类型奖励的分配方式不一致:\n- SHARE_RIGHT: 正确使用 addSettleableReward\\(\\) → settleable_usdt\n- CITY_TEAM_RIGHT/COMMUNITY_RIGHT: 错误使用 addAvailableBalance\\(\\) → usdt_available\n\n这导致 reward-service 记录的 SETTLEABLE 奖励总额与 wallet-service 的\nsettleable_usdt 字段不匹配。用户 D25122700024 的案例中:\n- reward-service: 3条奖励共 4464 USDT \\(SHARE_RIGHT 3600 + CITY_TEAM_RIGHT 288 + COMMUNITY_RIGHT 576\\)\n- wallet-service: settleable_usdt = 3600 \\(仅 SHARE_RIGHT\\)\n差额 864 USDT 被错误地放入了 usdt_available\n\n修复内容:\n1. allocateCommunityRight: 改用 addSettleableReward\\(\\) 替代 addAvailableBalance\\(\\)\n2. allocateToRegionAccount: 改用 addSettleableReward\\(\\) 替代 addAvailableBalance\\(\\)\n3. 流水类型统一使用 REWARD_TO_SETTLEABLE 替代 SYSTEM_ALLOCATION\n4. 日志和备注更新以反映新的分配方式\n\n设计原则:\n- reward-service 是奖励的权威来源\n- wallet-service 应跟随 reward-service 的设计\n- 所有奖励都应进入 settleable_usdt用户主动结算后才转入 usdt_available\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(ls \"c:\\\\Users\\\\dong\\\\Desktop\\\\rwadurian\\\\backend\\\\services\\\\reward-service\\\\prisma\"\" 2>/dev/null || dir \"c:UsersdongDesktoprwadurianbackendservicesreward-serviceprisma\"\")"
"Bash(ls \"c:\\\\Users\\\\dong\\\\Desktop\\\\rwadurian\\\\backend\\\\services\\\\reward-service\\\\prisma\"\" 2>/dev/null || dir \"c:UsersdongDesktoprwadurianbackendservicesreward-serviceprisma\"\")",
"Bash(git commit -m \"$\\(cat <<''EOF''\nfix\\(wallet-service\\): 修复 settleToBalance 方法缺少事务保护的严重 Bug\n\n问题原因:\nsettleToBalance 方法先执行 wallet.save\\(\\) 更新账户余额,再执行\nledgerRepo.save\\(\\) 写入流水记录。两个操作不在同一个事务中。\n\n当流水写入失败时如 memo 字段超过 VarChar\\(500\\) 限制),账户余额\n已经被修改但流水记录未写入导致数据不一致。\n\n具体案例:\n用户 D25122700023 点击结算时memo 内容超长66笔奖励详情\nwallet-service 先把 settleable_usdt 转入 usdt_available然后\n写流水失败。账户余额被改但没有对应流水。\n\n修复内容:\n1. settleToBalance: 使用 prisma.$transaction 确保原子性\n - 账户余额更新和流水记录在同一事务中\n - 任一操作失败整个事务回滚\n2. schema: memo 字段从 VarChar\\(500\\) 改为 Text 类型,无长度限制\n\n🤖 Generated with [Claude Code]\\(https://claude.com/claude-code\\)\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>\nEOF\n\\)\")"
],
"deny": [],
"ask": []

View File

@ -926,24 +926,30 @@ export class WalletApplicationService {
}
userId = walletRecord.userId;
// 2. 验证钱包状态是否 ACTIVE
if (walletRecord.status !== 'ACTIVE') {
throw new Error(`Wallet is not active: ${walletRecord.status}`);
}
const Decimal = (await import('decimal.js')).default;
const usdtAmountDecimal = new Decimal(params.usdtAmount);
const currentSettleable = new Decimal(walletRecord.settleableUsdt.toString());
const currentAvailable = new Decimal(walletRecord.usdtAvailable.toString());
const currentSettledTotal = new Decimal(walletRecord.settledTotalUsdt.toString());
// 2. 验证可结算余额足够
// 3. 验证可结算余额足够
if (currentSettleable.lessThan(usdtAmountDecimal)) {
throw new Error(`Insufficient settleable balance: ${currentSettleable} < ${usdtAmountDecimal}`);
}
// 3. 计算新余额
// 4. 计算新余额
const newSettleable = currentSettleable.minus(usdtAmountDecimal);
const newAvailable = currentAvailable.plus(usdtAmountDecimal);
const newSettledTotal = currentSettledTotal.plus(usdtAmountDecimal);
balanceAfter = newAvailable.toNumber();
// 4. 更新钱包账户(在事务内)
// 5. 更新钱包账户(在事务内)
await tx.walletAccount.update({
where: { accountSequence: params.accountSequence },
data: {
@ -954,7 +960,7 @@ export class WalletApplicationService {
},
});
// 5. 创建流水记录(在事务内)
// 6. 创建流水记录(在事务内)
await tx.ledgerEntry.create({
data: {
accountSequence: params.accountSequence,
@ -976,7 +982,7 @@ export class WalletApplicationService {
});
});
// 6. 使缓存失效(在事务外,事务成功后执行)
// 7. 使缓存失效(在事务外,事务成功后执行)
await this.walletCacheService.invalidateWallet(userId);
this.logger.log(`Successfully settled ${params.usdtAmount} USDT to balance for ${params.accountSequence}`);