chore(wallet-service): 移除已执行的OTP修复脚本
This commit is contained in:
parent
c2ff11bd6d
commit
dbeef0a80b
|
|
@ -20,8 +20,6 @@ import {
|
||||||
import { RedisModule } from './redis';
|
import { RedisModule } from './redis';
|
||||||
import { KafkaModule } from './kafka';
|
import { KafkaModule } from './kafka';
|
||||||
import { IdentityModule } from './external/identity';
|
import { IdentityModule } from './external/identity';
|
||||||
// OTP: One-Time fix for D25122600004 -> D25122600006 transfer (remove after fix)
|
|
||||||
import { TransferFixService } from './otp/transfer-fix.service';
|
|
||||||
|
|
||||||
const repositories = [
|
const repositories = [
|
||||||
{
|
{
|
||||||
|
|
@ -54,7 +52,7 @@ const repositories = [
|
||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [RedisModule, KafkaModule, IdentityModule],
|
imports: [RedisModule, KafkaModule, IdentityModule],
|
||||||
providers: [PrismaService, ...repositories, TransferFixService], // OTP: remove TransferFixService after fix
|
providers: [PrismaService, ...repositories],
|
||||||
exports: [PrismaService, RedisModule, KafkaModule, IdentityModule, FeeConfigRepositoryImpl, ...repositories],
|
exports: [PrismaService, RedisModule, KafkaModule, IdentityModule, FeeConfigRepositoryImpl, ...repositories],
|
||||||
})
|
})
|
||||||
export class InfrastructureModule {}
|
export class InfrastructureModule {}
|
||||||
|
|
|
||||||
|
|
@ -1,185 +0,0 @@
|
||||||
/**
|
|
||||||
* One-Time-Fix: 修复 D25122600004 -> D25122600006 的转账
|
|
||||||
*
|
|
||||||
* 问题:由于并发修改导致冻结余额少了 2 USDT (手续费)
|
|
||||||
* 解决:修复冻结余额,完成转账,更新订单状态
|
|
||||||
*
|
|
||||||
* 幂等性:检查订单状态,已 CONFIRMED 则跳过
|
|
||||||
* 部署后删除此文件和 infrastructure.module.ts 中的引用
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
|
||||||
import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service';
|
|
||||||
import { LedgerEntryType, WithdrawalStatus } from '@/domain/value-objects';
|
|
||||||
import Decimal from 'decimal.js';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class TransferFixService implements OnModuleInit {
|
|
||||||
private readonly logger = new Logger(TransferFixService.name);
|
|
||||||
|
|
||||||
// 需要修复的订单号
|
|
||||||
private readonly ORDER_NO = 'WD1766719397843H90GUW';
|
|
||||||
private readonly SENDER_ACCOUNT = 'D25122600004';
|
|
||||||
private readonly RECEIVER_ACCOUNT = 'D25122600006';
|
|
||||||
private readonly MISSING_FEE = new Decimal('2'); // 缺失的手续费
|
|
||||||
|
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
|
||||||
|
|
||||||
async onModuleInit() {
|
|
||||||
// 延迟 5 秒执行,确保所有服务都已启动
|
|
||||||
setTimeout(() => this.executeFixOnce(), 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async executeFixOnce(): Promise<void> {
|
|
||||||
this.logger.log('========================================');
|
|
||||||
this.logger.log('[OTP-FIX] Starting one-time transfer fix');
|
|
||||||
this.logger.log(`[OTP-FIX] Order: ${this.ORDER_NO}`);
|
|
||||||
this.logger.log(`[OTP-FIX] From: ${this.SENDER_ACCOUNT} -> To: ${this.RECEIVER_ACCOUNT}`);
|
|
||||||
this.logger.log('========================================');
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 检查订单状态
|
|
||||||
const order = await this.prisma.withdrawalOrder.findUnique({
|
|
||||||
where: { orderNo: this.ORDER_NO },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!order) {
|
|
||||||
this.logger.log('[OTP-FIX] Order not found, skipping (may be wrong environment)');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果已经确认,说明已修复过(幂等性)
|
|
||||||
if (order.status === WithdrawalStatus.CONFIRMED) {
|
|
||||||
this.logger.log('[OTP-FIX] Order already CONFIRMED, skipping');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order.status !== WithdrawalStatus.FROZEN) {
|
|
||||||
this.logger.warn(`[OTP-FIX] Unexpected order status: ${order.status}, skipping`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行修复
|
|
||||||
await this.prisma.$transaction(async (tx) => {
|
|
||||||
// 1. 获取发送方钱包
|
|
||||||
const senderWallet = await tx.walletAccount.findUnique({
|
|
||||||
where: { accountSequence: this.SENDER_ACCOUNT },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!senderWallet) {
|
|
||||||
throw new Error(`Sender wallet not found: ${this.SENDER_ACCOUNT}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 获取接收方钱包
|
|
||||||
const receiverWallet = await tx.walletAccount.findUnique({
|
|
||||||
where: { accountSequence: this.RECEIVER_ACCOUNT },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!receiverWallet) {
|
|
||||||
throw new Error(`Receiver wallet not found: ${this.RECEIVER_ACCOUNT}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const amount = new Decimal(order.amount.toString());
|
|
||||||
const fee = new Decimal(order.fee.toString());
|
|
||||||
const totalAmount = amount.add(fee);
|
|
||||||
|
|
||||||
this.logger.log(`[OTP-FIX] Amount: ${amount}, Fee: ${fee}, Total: ${totalAmount}`);
|
|
||||||
|
|
||||||
// 3. 修复发送方冻结余额 (加上缺失的手续费) 并同时扣除
|
|
||||||
const senderCurrentFrozen = new Decimal(senderWallet.usdtFrozen.toString());
|
|
||||||
// 加上缺失的手续费后再扣除全部
|
|
||||||
const senderNewFrozen = senderCurrentFrozen.add(this.MISSING_FEE).minus(totalAmount);
|
|
||||||
|
|
||||||
this.logger.log(`[OTP-FIX] Sender frozen: ${senderCurrentFrozen} + ${this.MISSING_FEE} - ${totalAmount} = ${senderNewFrozen}`);
|
|
||||||
|
|
||||||
await tx.walletAccount.update({
|
|
||||||
where: { id: senderWallet.id },
|
|
||||||
data: {
|
|
||||||
usdtFrozen: senderNewFrozen,
|
|
||||||
version: senderWallet.version + 1,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. 增加接收方余额
|
|
||||||
const receiverCurrentAvailable = new Decimal(receiverWallet.usdtAvailable.toString());
|
|
||||||
const receiverNewAvailable = receiverCurrentAvailable.add(amount);
|
|
||||||
|
|
||||||
this.logger.log(`[OTP-FIX] Receiver available: ${receiverCurrentAvailable} + ${amount} = ${receiverNewAvailable}`);
|
|
||||||
|
|
||||||
await tx.walletAccount.update({
|
|
||||||
where: { id: receiverWallet.id },
|
|
||||||
data: {
|
|
||||||
usdtAvailable: receiverNewAvailable,
|
|
||||||
version: receiverWallet.version + 1,
|
|
||||||
updatedAt: new Date(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 5. 创建发送方流水 (TRANSFER_OUT)
|
|
||||||
await tx.ledgerEntry.create({
|
|
||||||
data: {
|
|
||||||
accountSequence: order.accountSequence,
|
|
||||||
userId: order.userId,
|
|
||||||
entryType: LedgerEntryType.TRANSFER_OUT,
|
|
||||||
amount: amount.negated(),
|
|
||||||
assetType: 'USDT',
|
|
||||||
balanceAfter: senderWallet.usdtAvailable,
|
|
||||||
refOrderId: order.orderNo,
|
|
||||||
refTxHash: 'OTP-FIX-INTERNAL',
|
|
||||||
memo: `转账至 ${order.toAccountSequence} (OTP修复)`,
|
|
||||||
payloadJson: {
|
|
||||||
toAccountSequence: order.toAccountSequence,
|
|
||||||
toUserId: order.toUserId?.toString(),
|
|
||||||
fee: order.fee.toString(),
|
|
||||||
fixNote: 'One-time fix for concurrent modification issue',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 6. 创建接收方流水 (TRANSFER_IN)
|
|
||||||
await tx.ledgerEntry.create({
|
|
||||||
data: {
|
|
||||||
accountSequence: order.toAccountSequence!,
|
|
||||||
userId: order.toUserId!,
|
|
||||||
entryType: LedgerEntryType.TRANSFER_IN,
|
|
||||||
amount: amount,
|
|
||||||
assetType: 'USDT',
|
|
||||||
balanceAfter: receiverNewAvailable,
|
|
||||||
refOrderId: order.orderNo,
|
|
||||||
refTxHash: 'OTP-FIX-INTERNAL',
|
|
||||||
memo: `来自 ${order.accountSequence} 的转账 (OTP修复)`,
|
|
||||||
payloadJson: {
|
|
||||||
fromAccountSequence: order.accountSequence,
|
|
||||||
fromUserId: order.userId.toString(),
|
|
||||||
fixNote: 'One-time fix for concurrent modification issue',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 7. 更新订单状态为 CONFIRMED
|
|
||||||
await tx.withdrawalOrder.update({
|
|
||||||
where: { id: order.id },
|
|
||||||
data: {
|
|
||||||
status: WithdrawalStatus.CONFIRMED,
|
|
||||||
txHash: 'OTP-FIX-INTERNAL',
|
|
||||||
broadcastedAt: new Date(),
|
|
||||||
confirmedAt: new Date(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.logger.log('[OTP-FIX] Transaction completed successfully');
|
|
||||||
});
|
|
||||||
|
|
||||||
this.logger.log('========================================');
|
|
||||||
this.logger.log('[OTP-FIX] Transfer fix completed!');
|
|
||||||
this.logger.log(`[OTP-FIX] ${this.SENDER_ACCOUNT} -> ${this.RECEIVER_ACCOUNT}: ${this.prisma}`);
|
|
||||||
this.logger.log('[OTP-FIX] Order status: CONFIRMED');
|
|
||||||
this.logger.log('[OTP-FIX] Please remove this file after deployment');
|
|
||||||
this.logger.log('========================================');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error('[OTP-FIX] Failed to execute fix', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue