feat(wallet-service): 添加内部转账入账修复脚本
新增一次性修复脚本用于补录因接收方钱包未创建导致入账失败的内部转账。 脚本特性: - DRY_RUN 模式:默认只检查不执行,需手动改为 false 才真正修复 - 完整验证:订单状态、类型、接收方信息、txHash - 幂等性检查:确认接收方没有 TRANSFER_IN 流水 - 转出方验证:确认转出方有 TRANSFER_OUT 流水(已扣款) - 乐观锁:使用 version 字段防止并发修改 - 审计追踪:payloadJson.dataFix=true 标记修复操作 - 详细日志:每步操作都有时间戳和日志级别 使用方法: 1. 在 wallet-service 容器内执行 DRY_RUN 检查 2. 确认无误后将 DRY_RUN 改为 false 再次执行 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ac0e73afac
commit
3b3342de5c
|
|
@ -586,7 +586,8 @@
|
||||||
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(wallet-service\\): add offline settlement deduction feature\n\nAdd new functionality for admins to automatically deduct all settled\nearnings when creating special deductions with amount=0, marking\neach record to prevent duplicate deductions.\n\n- Add OfflineSettlementDeduction model to track deducted records\n- Add API endpoints for querying unprocessed settlements and executing batch deduction\n- Add mode selection UI in admin-web pending-actions\n- Add offline settlement card display in mobile-app special deduction page\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''\nfeat\\(wallet-service\\): add offline settlement deduction feature\n\nAdd new functionality for admins to automatically deduct all settled\nearnings when creating special deductions with amount=0, marking\neach record to prevent duplicate deductions.\n\n- Add OfflineSettlementDeduction model to track deducted records\n- Add API endpoints for querying unprocessed settlements and executing batch deduction\n- Add mode selection UI in admin-web pending-actions\n- Add offline settlement card display in mobile-app special deduction page\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\\): convert BigInt to string for JSON serialization in getUnprocessedSettlements\n\nThe entry.id field is BigInt type from Prisma which cannot be JSON serialized directly.\nConvert to string for API response and back to BigInt when storing to database.\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\\): convert BigInt to string for JSON serialization in getUnprocessedSettlements\n\nThe entry.id field is BigInt type from Prisma which cannot be JSON serialized directly.\nConvert to string for API response and back to BigInt when storing to database.\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''\nfeat\\(mobile-app\\): improve empty state display for offline settlement deduction\n\nWhen there are no settlement records to deduct, show a more informative message:\n- If user has balance from deposits/transfers: explain it''s not from earnings\n- If user has no balance: explain there are no settlement records\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''\nfeat\\(mobile-app\\): improve empty state display for offline settlement deduction\n\nWhen there are no settlement records to deduct, show a more informative message:\n- If user has balance from deposits/transfers: explain it''s not from earnings\n- If user has no balance: explain there are no settlement records\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(xargs:*)"
|
"Bash(xargs:*)",
|
||||||
|
"Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(wallet/blockchain\\): 热钱包余额预检查及接收方钱包自动创建\n\n1. blockchain-service: 新增热钱包 dUSDT 余额定时更新调度器\n - 每 5 秒查询热钱包在 KAVA 链上的 dUSDT 余额\n - 更新到 Redis DB 0,key 格式: hot_wallet:dusdt_balance:{chainType}\n - TTL 30 秒,服务故障时缓存自动过期\n\n2. wallet-service: 新增热钱包余额缓存服务\n - 从 Redis DB 0 读取热钱包余额缓存\n - 严格模式:无法获取余额或余额不足时拒绝转账\n - 提示信息:\"财务系统审计中,请稍后再试\"\n\n3. wallet-service: 转账确认时自动创建接收方钱包\n - 解决接收方钱包不存在导致入账失败的问题\n - 使用 upsert 避免并发创建冲突\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\\)\")"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,359 @@
|
||||||
|
/**
|
||||||
|
* 一次性修复脚本:补录内部转账入账
|
||||||
|
*
|
||||||
|
* ⚠️ 警告:此脚本涉及资金操作,请务必仔细核对!
|
||||||
|
*
|
||||||
|
* 用于修复因接收方钱包未创建导致入账失败的内部转账。
|
||||||
|
*
|
||||||
|
* 执行前必须确认:
|
||||||
|
* 1. 订单号正确
|
||||||
|
* 2. 订单状态是 CONFIRMED(链上已完成)
|
||||||
|
* 3. 接收方确实没有收到这笔转账
|
||||||
|
* 4. 数据库中没有对应的 TRANSFER_IN 流水
|
||||||
|
*
|
||||||
|
* 安全机制:
|
||||||
|
* 1. DRY_RUN 模式:默认只检查不执行,需要手动改为 false 才会真正执行
|
||||||
|
* 2. 幂等性:检查 refOrderId + accountSequence + entryType 是否已存在
|
||||||
|
* 3. 事务性:所有操作在同一个数据库事务中
|
||||||
|
* 4. 乐观锁:使用 version 字段防止并发修改
|
||||||
|
* 5. 审计追踪:payloadJson.dataFix=true 标记为修复操作
|
||||||
|
*
|
||||||
|
* 使用方法:
|
||||||
|
* 在 wallet-service 容器内执行:
|
||||||
|
* npx ts-node scripts/fix-missing-transfer-in.ts
|
||||||
|
*
|
||||||
|
* 环境变量要求:
|
||||||
|
* - DATABASE_URL: 数据库连接字符串
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import Decimal from 'decimal.js';
|
||||||
|
|
||||||
|
// ========== 配置 ==========
|
||||||
|
// 只需提供订单号,其他信息自动从数据库获取
|
||||||
|
const ORDER_NO = 'WD1767599904858VG01WF';
|
||||||
|
|
||||||
|
// ⚠️ 安全开关:设为 true 时只检查不执行,设为 false 才会真正修复
|
||||||
|
const DRY_RUN = true;
|
||||||
|
// ==========================
|
||||||
|
|
||||||
|
function log(level: 'INFO' | 'WARN' | 'ERROR' | 'DEBUG', message: string, data?: unknown) {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const prefix = `[${timestamp}] [${level}]`;
|
||||||
|
if (data !== undefined) {
|
||||||
|
console.log(`${prefix} ${message}`, JSON.stringify(data, null, 2));
|
||||||
|
} else {
|
||||||
|
console.log(`${prefix} ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
log('INFO', '========================================');
|
||||||
|
log('INFO', '内部转账入账修复脚本');
|
||||||
|
log('INFO', '========================================');
|
||||||
|
log('INFO', `订单号: ${ORDER_NO}`);
|
||||||
|
log('INFO', `模式: ${DRY_RUN ? '🔍 DRY_RUN (只检查不执行)' : '⚡ LIVE (真正执行)'}`);
|
||||||
|
log('INFO', '');
|
||||||
|
|
||||||
|
if (DRY_RUN) {
|
||||||
|
log('WARN', '⚠️ DRY_RUN 模式:以下操作不会真正执行');
|
||||||
|
log('WARN', '⚠️ 确认无误后,将 DRY_RUN 改为 false 再次执行');
|
||||||
|
log('INFO', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const prisma = new PrismaClient({
|
||||||
|
log: ['warn', 'error'],
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await prisma.$transaction(async (tx) => {
|
||||||
|
// ==================== Step 1: 查询订单信息 ====================
|
||||||
|
log('INFO', '[Step 1/7] 查询订单信息...');
|
||||||
|
const order = await tx.withdrawalOrder.findUnique({
|
||||||
|
where: { orderNo: ORDER_NO },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!order) {
|
||||||
|
throw new Error(`❌ 订单不存在: ${ORDER_NO}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
log('DEBUG', '订单原始数据:', {
|
||||||
|
id: order.id,
|
||||||
|
orderNo: order.orderNo,
|
||||||
|
status: order.status,
|
||||||
|
isInternalTransfer: order.isInternalTransfer,
|
||||||
|
accountSequence: order.accountSequence,
|
||||||
|
userId: order.userId.toString(),
|
||||||
|
toAccountSequence: order.toAccountSequence,
|
||||||
|
toUserId: order.toUserId?.toString(),
|
||||||
|
amount: order.amount.toString(),
|
||||||
|
fee: order.fee.toString(),
|
||||||
|
txHash: order.txHash,
|
||||||
|
confirmedAt: order.confirmedAt?.toISOString(),
|
||||||
|
createdAt: order.createdAt.toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== Step 2: 验证订单状态 ====================
|
||||||
|
log('INFO', '[Step 2/7] 验证订单状态...');
|
||||||
|
|
||||||
|
if (order.status !== 'CONFIRMED') {
|
||||||
|
throw new Error(`❌ 订单状态不是 CONFIRMED,当前状态: ${order.status}`);
|
||||||
|
}
|
||||||
|
log('INFO', ` ✓ 订单状态: CONFIRMED`);
|
||||||
|
|
||||||
|
if (!order.isInternalTransfer) {
|
||||||
|
throw new Error(`❌ 订单不是内部转账,isInternalTransfer=${order.isInternalTransfer}`);
|
||||||
|
}
|
||||||
|
log('INFO', ` ✓ 订单类型: 内部转账`);
|
||||||
|
|
||||||
|
if (!order.toAccountSequence) {
|
||||||
|
throw new Error(`❌ 订单缺少接收方账号 toAccountSequence`);
|
||||||
|
}
|
||||||
|
if (!order.toUserId) {
|
||||||
|
throw new Error(`❌ 订单缺少接收方用户ID toUserId`);
|
||||||
|
}
|
||||||
|
log('INFO', ` ✓ 接收方信息完整`);
|
||||||
|
|
||||||
|
if (!order.txHash) {
|
||||||
|
throw new Error(`❌ 订单缺少交易哈希 txHash,链上交易可能未完成`);
|
||||||
|
}
|
||||||
|
log('INFO', ` ✓ 链上交易已完成: ${order.txHash}`);
|
||||||
|
|
||||||
|
const toAccountSequence = order.toAccountSequence;
|
||||||
|
const toUserId = order.toUserId;
|
||||||
|
const transferAmount = new Decimal(order.amount.toString());
|
||||||
|
|
||||||
|
log('INFO', '');
|
||||||
|
log('INFO', '📋 订单摘要:');
|
||||||
|
log('INFO', ` 订单号: ${order.orderNo}`);
|
||||||
|
log('INFO', ` 转出方: ${order.accountSequence} (userId=${order.userId})`);
|
||||||
|
log('INFO', ` 接收方: ${toAccountSequence} (userId=${toUserId})`);
|
||||||
|
log('INFO', ` 金额: ${transferAmount.toString()} USDT`);
|
||||||
|
log('INFO', ` 手续费: ${order.fee.toString()} USDT`);
|
||||||
|
log('INFO', ` TxHash: ${order.txHash}`);
|
||||||
|
log('INFO', ` 确认时间: ${order.confirmedAt?.toISOString() || 'N/A'}`);
|
||||||
|
log('INFO', '');
|
||||||
|
|
||||||
|
// ==================== Step 3: 检查转出方流水 ====================
|
||||||
|
log('INFO', '[Step 3/7] 检查转出方 TRANSFER_OUT 流水...');
|
||||||
|
const transferOutEntry = await tx.ledgerEntry.findFirst({
|
||||||
|
where: {
|
||||||
|
refOrderId: ORDER_NO,
|
||||||
|
accountSequence: order.accountSequence,
|
||||||
|
entryType: 'TRANSFER_OUT',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!transferOutEntry) {
|
||||||
|
log('WARN', ` ⚠️ 转出方没有 TRANSFER_OUT 流水!这可能表示整个转账都没有正常处理`);
|
||||||
|
log('WARN', ` 请先确认转出方是否已扣款`);
|
||||||
|
} else {
|
||||||
|
log('INFO', ` ✓ 转出方 TRANSFER_OUT 流水存在 (id=${transferOutEntry.id})`);
|
||||||
|
log('DEBUG', '转出方流水:', {
|
||||||
|
id: transferOutEntry.id,
|
||||||
|
amount: transferOutEntry.amount.toString(),
|
||||||
|
balanceAfter: transferOutEntry.balanceAfter.toString(),
|
||||||
|
createdAt: transferOutEntry.createdAt.toISOString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Step 4: 幂等性检查 ====================
|
||||||
|
log('INFO', '[Step 4/7] 幂等性检查 - 接收方 TRANSFER_IN 流水...');
|
||||||
|
const existingTransferIn = await tx.ledgerEntry.findFirst({
|
||||||
|
where: {
|
||||||
|
refOrderId: ORDER_NO,
|
||||||
|
accountSequence: toAccountSequence,
|
||||||
|
entryType: 'TRANSFER_IN',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingTransferIn) {
|
||||||
|
log('ERROR', ` ❌ TRANSFER_IN 流水已存在!`);
|
||||||
|
log('ERROR', '流水详情:', {
|
||||||
|
id: existingTransferIn.id,
|
||||||
|
amount: existingTransferIn.amount.toString(),
|
||||||
|
balanceAfter: existingTransferIn.balanceAfter.toString(),
|
||||||
|
createdAt: existingTransferIn.createdAt.toISOString(),
|
||||||
|
});
|
||||||
|
log('ERROR', '');
|
||||||
|
log('ERROR', '========================================');
|
||||||
|
log('ERROR', '❌ 中止:接收方已有入账流水,不能重复入账!');
|
||||||
|
log('ERROR', '========================================');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log('INFO', ` ✓ 未找到 TRANSFER_IN 流水,可以安全入账`);
|
||||||
|
|
||||||
|
// ==================== Step 5: 查找接收方钱包 ====================
|
||||||
|
log('INFO', '[Step 5/7] 查找接收方钱包...');
|
||||||
|
let wallet = await tx.walletAccount.findUnique({
|
||||||
|
where: { accountSequence: toAccountSequence },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!wallet) {
|
||||||
|
wallet = await tx.walletAccount.findUnique({
|
||||||
|
where: { userId: toUserId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wallet) {
|
||||||
|
log('INFO', ` ✓ 钱包已存在`);
|
||||||
|
log('DEBUG', '钱包信息:', {
|
||||||
|
id: wallet.id,
|
||||||
|
accountSequence: wallet.accountSequence,
|
||||||
|
userId: wallet.userId.toString(),
|
||||||
|
usdtAvailable: wallet.usdtAvailable.toString(),
|
||||||
|
usdtFrozen: wallet.usdtFrozen.toString(),
|
||||||
|
version: wallet.version,
|
||||||
|
createdAt: wallet.createdAt.toISOString(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log('WARN', ` ⚠️ 钱包不存在,需要创建`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Step 6: 计算新余额 ====================
|
||||||
|
log('INFO', '[Step 6/7] 计算新余额...');
|
||||||
|
const currentBalance = wallet ? new Decimal(wallet.usdtAvailable.toString()) : new Decimal(0);
|
||||||
|
const newBalance = currentBalance.add(transferAmount);
|
||||||
|
const currentVersion = wallet?.version ?? 0;
|
||||||
|
|
||||||
|
log('INFO', ` 当前余额: ${currentBalance.toString()} USDT`);
|
||||||
|
log('INFO', ` 转账金额: +${transferAmount.toString()} USDT`);
|
||||||
|
log('INFO', ` 新余额: ${newBalance.toString()} USDT`);
|
||||||
|
log('INFO', '');
|
||||||
|
|
||||||
|
// ==================== DRY_RUN 检查 ====================
|
||||||
|
if (DRY_RUN) {
|
||||||
|
log('INFO', '========================================');
|
||||||
|
log('INFO', '🔍 DRY_RUN 模式 - 检查完成');
|
||||||
|
log('INFO', '========================================');
|
||||||
|
log('INFO', '');
|
||||||
|
log('INFO', '以上检查全部通过,可以安全执行修复。');
|
||||||
|
log('INFO', '');
|
||||||
|
log('INFO', '将要执行的操作:');
|
||||||
|
if (!wallet) {
|
||||||
|
log('INFO', ` 1. 创建钱包: ${toAccountSequence} (userId=${toUserId})`);
|
||||||
|
log('INFO', ` 2. 设置余额: ${newBalance.toString()} USDT`);
|
||||||
|
} else {
|
||||||
|
log('INFO', ` 1. 更新钱包余额: ${currentBalance.toString()} -> ${newBalance.toString()} USDT`);
|
||||||
|
log('INFO', ` 2. 更新版本号: ${currentVersion} -> ${currentVersion + 1}`);
|
||||||
|
}
|
||||||
|
log('INFO', ` 3. 创建 TRANSFER_IN 流水记录`);
|
||||||
|
log('INFO', '');
|
||||||
|
log('INFO', '确认无误后,请将 DRY_RUN 改为 false 再次执行。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== Step 7: 执行修复 ====================
|
||||||
|
log('INFO', '[Step 7/7] 执行修复...');
|
||||||
|
|
||||||
|
// 7a. 创建或更新钱包
|
||||||
|
if (!wallet) {
|
||||||
|
log('INFO', ' 创建钱包...');
|
||||||
|
wallet = await tx.walletAccount.create({
|
||||||
|
data: {
|
||||||
|
accountSequence: toAccountSequence,
|
||||||
|
userId: toUserId,
|
||||||
|
usdtAvailable: newBalance, // 直接设置为转账金额
|
||||||
|
usdtFrozen: new Decimal(0),
|
||||||
|
dstAvailable: new Decimal(0),
|
||||||
|
dstFrozen: new Decimal(0),
|
||||||
|
bnbAvailable: new Decimal(0),
|
||||||
|
bnbFrozen: new Decimal(0),
|
||||||
|
ogAvailable: new Decimal(0),
|
||||||
|
ogFrozen: new Decimal(0),
|
||||||
|
rwadAvailable: new Decimal(0),
|
||||||
|
rwadFrozen: new Decimal(0),
|
||||||
|
hashpower: new Decimal(0),
|
||||||
|
pendingUsdt: new Decimal(0),
|
||||||
|
pendingHashpower: new Decimal(0),
|
||||||
|
settleableUsdt: new Decimal(0),
|
||||||
|
settleableHashpower: new Decimal(0),
|
||||||
|
settledTotalUsdt: new Decimal(0),
|
||||||
|
settledTotalHashpower: new Decimal(0),
|
||||||
|
expiredTotalUsdt: new Decimal(0),
|
||||||
|
expiredTotalHashpower: new Decimal(0),
|
||||||
|
status: 'ACTIVE',
|
||||||
|
hasPlanted: false,
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
log('INFO', ` ✓ 钱包创建成功 (id=${wallet.id}, balance=${newBalance.toString()})`);
|
||||||
|
} else {
|
||||||
|
log('INFO', ' 更新钱包余额...');
|
||||||
|
const updateResult = await tx.walletAccount.updateMany({
|
||||||
|
where: {
|
||||||
|
id: wallet.id,
|
||||||
|
version: currentVersion,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
usdtAvailable: newBalance,
|
||||||
|
version: currentVersion + 1,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (updateResult.count === 0) {
|
||||||
|
throw new Error('❌ 乐观锁冲突:钱包数据已被其他操作修改,请重试');
|
||||||
|
}
|
||||||
|
log('INFO', ` ✓ 余额更新成功 (version: ${currentVersion} -> ${currentVersion + 1})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7b. 创建流水记录
|
||||||
|
log('INFO', ' 创建流水记录...');
|
||||||
|
const entry = await tx.ledgerEntry.create({
|
||||||
|
data: {
|
||||||
|
accountSequence: toAccountSequence,
|
||||||
|
userId: toUserId,
|
||||||
|
entryType: 'TRANSFER_IN',
|
||||||
|
amount: transferAmount,
|
||||||
|
assetType: 'USDT',
|
||||||
|
balanceAfter: newBalance,
|
||||||
|
refOrderId: ORDER_NO,
|
||||||
|
refTxHash: order.txHash,
|
||||||
|
memo: `来自 ${order.accountSequence} 的转账(数据修复)`,
|
||||||
|
payloadJson: {
|
||||||
|
fromAccountSequence: order.accountSequence,
|
||||||
|
fromUserId: order.userId.toString(),
|
||||||
|
originalOrderNo: ORDER_NO,
|
||||||
|
dataFix: true,
|
||||||
|
fixedAt: new Date().toISOString(),
|
||||||
|
fixReason: 'Receiver wallet not existed when transfer confirmed',
|
||||||
|
scriptVersion: '1.0.0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
log('INFO', ` ✓ 流水记录创建成功 (id=${entry.id})`);
|
||||||
|
|
||||||
|
// ==================== 完成 ====================
|
||||||
|
log('INFO', '');
|
||||||
|
log('INFO', '========================================');
|
||||||
|
log('INFO', '✅ 修复成功!');
|
||||||
|
log('INFO', '========================================');
|
||||||
|
log('INFO', `接收方: ${toAccountSequence}`);
|
||||||
|
log('INFO', `入账金额: ${transferAmount.toString()} USDT`);
|
||||||
|
log('INFO', `新余额: ${newBalance.toString()} USDT`);
|
||||||
|
log('INFO', `流水ID: ${entry.id}`);
|
||||||
|
log('INFO', '');
|
||||||
|
log('INFO', '验证命令:');
|
||||||
|
log('INFO', ` SELECT account_sequence, usdt_available, version FROM wallet_accounts WHERE account_sequence = '${toAccountSequence}';`);
|
||||||
|
log('INFO', ` SELECT id, entry_type, amount, balance_after, ref_order_id, created_at FROM wallet_ledger_entries WHERE ref_order_id = '${ORDER_NO}';`);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log('ERROR', '');
|
||||||
|
log('ERROR', '========================================');
|
||||||
|
log('ERROR', '❌ 修复失败');
|
||||||
|
log('ERROR', '========================================');
|
||||||
|
if (error instanceof Error) {
|
||||||
|
log('ERROR', `错误信息: ${error.message}`);
|
||||||
|
log('DEBUG', `错误堆栈: ${error.stack}`);
|
||||||
|
} else {
|
||||||
|
log('ERROR', '未知错误:', error);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
} finally {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
Loading…
Reference in New Issue