198 lines
7.2 KiB
TypeScript
198 lines
7.2 KiB
TypeScript
import { Controller, Get, Post, Body, Param, Logger } from '@nestjs/common';
|
||
import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';
|
||
import { WalletApplicationService } from '@/application/services';
|
||
import { GetMyWalletQuery } from '@/application/queries';
|
||
import {
|
||
DeductForPlantingCommand,
|
||
AllocateFundsCommand,
|
||
FundAllocationItem,
|
||
FreezeForPlantingCommand,
|
||
ConfirmPlantingDeductionCommand,
|
||
UnfreezeForPlantingCommand,
|
||
} from '@/application/commands';
|
||
import { Public } from '@/shared/decorators';
|
||
|
||
/**
|
||
* 内部API控制器 - 供其他微服务调用
|
||
* 不需要JWT认证,通过内部网络访问
|
||
*/
|
||
@ApiTags('Internal Wallet API')
|
||
@Controller('wallets')
|
||
export class InternalWalletController {
|
||
private readonly logger = new Logger(InternalWalletController.name);
|
||
|
||
constructor(private readonly walletService: WalletApplicationService) {}
|
||
|
||
@Get(':userId/balance')
|
||
@Public()
|
||
@ApiOperation({ summary: '获取用户钱包余额(内部API)' })
|
||
@ApiParam({ name: 'userId', description: '用户ID或accountSequence' })
|
||
@ApiResponse({ status: 200, description: '余额信息' })
|
||
async getBalance(@Param('userId') userId: string) {
|
||
// 判断是 accountSequence (以 D 开头) 还是 userId (纯数字)
|
||
const isAccountSequence = userId.startsWith('D');
|
||
// 如果是 accountSequence,userId 参数设为 '0'(会被忽略,通过 accountSequence 查找)
|
||
// 如果是纯 userId,accountSequence 参数设为 userId(会通过 userId 查找)
|
||
const query = new GetMyWalletQuery(
|
||
isAccountSequence ? userId : userId, // accountSequence
|
||
isAccountSequence ? '0' : userId, // userId (如果是 accountSequence 则忽略)
|
||
);
|
||
const wallet = await this.walletService.getMyWallet(query);
|
||
return {
|
||
userId,
|
||
available: wallet.balances.usdt.available,
|
||
locked: wallet.balances.usdt.frozen,
|
||
currency: 'USDT',
|
||
};
|
||
}
|
||
|
||
@Post('deduct-for-planting')
|
||
@Public()
|
||
@ApiOperation({ summary: '认种扣款(内部API) - 直接扣款模式' })
|
||
@ApiResponse({ status: 200, description: '扣款结果' })
|
||
async deductForPlanting(
|
||
@Body() dto: { userId: string; accountSequence?: string; amount: number; orderId: string },
|
||
) {
|
||
// 优先使用 accountSequence,如果未提供则使用 userId
|
||
const userIdentifier = dto.accountSequence || dto.userId;
|
||
const command = new DeductForPlantingCommand(
|
||
userIdentifier,
|
||
dto.amount,
|
||
dto.orderId,
|
||
);
|
||
const success = await this.walletService.deductForPlanting(command);
|
||
return { success };
|
||
}
|
||
|
||
@Post('freeze-for-planting')
|
||
@Public()
|
||
@ApiOperation({ summary: '认种冻结资金(内部API) - 预扣款模式第一步' })
|
||
@ApiResponse({ status: 200, description: '冻结结果' })
|
||
async freezeForPlanting(
|
||
@Body() dto: { userId: string; accountSequence?: string; amount: number; orderId: string },
|
||
) {
|
||
this.logger.log(`========== freeze-for-planting 请求 ==========`);
|
||
this.logger.log(`请求参数: ${JSON.stringify(dto)}`);
|
||
this.logger.log(` userId: ${dto.userId}`);
|
||
this.logger.log(` accountSequence: ${dto.accountSequence || '未提供'}`);
|
||
this.logger.log(` amount: ${dto.amount}`);
|
||
this.logger.log(` orderId: ${dto.orderId}`);
|
||
|
||
try {
|
||
// 优先使用 accountSequence,如果未提供则使用 userId
|
||
const userIdentifier = dto.accountSequence || dto.userId;
|
||
this.logger.log(` 使用标识符: ${userIdentifier}`);
|
||
|
||
const command = new FreezeForPlantingCommand(
|
||
userIdentifier,
|
||
dto.amount,
|
||
dto.orderId,
|
||
);
|
||
const result = await this.walletService.freezeForPlanting(command);
|
||
this.logger.log(`冻结成功: ${JSON.stringify(result)}`);
|
||
return result;
|
||
} catch (error) {
|
||
this.logger.error(`冻结失败: ${error.message}`);
|
||
this.logger.error(`错误堆栈: ${error.stack}`);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
@Post('confirm-planting-deduction')
|
||
@Public()
|
||
@ApiOperation({ summary: '确认认种扣款(内部API) - 预扣款模式第二步' })
|
||
@ApiResponse({ status: 200, description: '确认结果' })
|
||
async confirmPlantingDeduction(
|
||
@Body() dto: { userId: string; accountSequence?: string; orderId: string },
|
||
) {
|
||
// 优先使用 accountSequence,如果未提供则使用 userId
|
||
const userIdentifier = dto.accountSequence || dto.userId;
|
||
const command = new ConfirmPlantingDeductionCommand(
|
||
userIdentifier,
|
||
dto.orderId,
|
||
);
|
||
const success = await this.walletService.confirmPlantingDeduction(command);
|
||
return { success };
|
||
}
|
||
|
||
@Post('unfreeze-for-planting')
|
||
@Public()
|
||
@ApiOperation({ summary: '解冻认种资金(内部API) - 认种失败时回滚' })
|
||
@ApiResponse({ status: 200, description: '解冻结果' })
|
||
async unfreezeForPlanting(
|
||
@Body() dto: { userId: string; accountSequence?: string; orderId: string },
|
||
) {
|
||
// 优先使用 accountSequence,如果未提供则使用 userId
|
||
const userIdentifier = dto.accountSequence || dto.userId;
|
||
const command = new UnfreezeForPlantingCommand(
|
||
userIdentifier,
|
||
dto.orderId,
|
||
);
|
||
const success = await this.walletService.unfreezeForPlanting(command);
|
||
return { success };
|
||
}
|
||
|
||
@Post('allocate-funds')
|
||
@Public()
|
||
@ApiOperation({ summary: '资金分配(内部API)' })
|
||
@ApiResponse({ status: 200, description: '分配结果' })
|
||
async allocateFunds(
|
||
@Body() dto: { orderId: string; allocations: FundAllocationItem[] },
|
||
) {
|
||
const command = new AllocateFundsCommand(
|
||
dto.orderId,
|
||
'', // payerUserId will be determined from order
|
||
dto.allocations,
|
||
);
|
||
const result = await this.walletService.allocateFunds(command);
|
||
return { success: result.success };
|
||
}
|
||
|
||
@Post('ensure-region-accounts')
|
||
@Public()
|
||
@ApiOperation({ summary: '确保区域账户存在(内部API) - 在选择省市确认后调用' })
|
||
@ApiResponse({ status: 200, description: '区域账户创建结果' })
|
||
async ensureRegionAccounts(
|
||
@Body() dto: { provinceCode: string; provinceName: string; cityCode: string; cityName: string },
|
||
) {
|
||
this.logger.log(`确保区域账户存在: 省=${dto.provinceName}(${dto.provinceCode}), 市=${dto.cityName}(${dto.cityCode})`);
|
||
|
||
const result = await this.walletService.ensureRegionAccounts({
|
||
provinceCode: dto.provinceCode,
|
||
provinceName: dto.provinceName,
|
||
cityCode: dto.cityCode,
|
||
cityName: dto.cityName,
|
||
});
|
||
|
||
return result;
|
||
}
|
||
|
||
@Post('settle-to-balance')
|
||
@Public()
|
||
@ApiOperation({ summary: '结算到余额(内部API) - 可结算USDT转入钱包余额' })
|
||
@ApiResponse({ status: 200, description: '结算结果' })
|
||
async settleToBalance(
|
||
@Body() dto: {
|
||
accountSequence: string;
|
||
usdtAmount: number;
|
||
rewardEntryIds: string[];
|
||
breakdown?: Record<string, number>;
|
||
memo?: string;
|
||
},
|
||
) {
|
||
this.logger.log(`========== settle-to-balance 请求 ==========`);
|
||
this.logger.log(`请求参数: ${JSON.stringify(dto)}`);
|
||
|
||
const result = await this.walletService.settleToBalance({
|
||
accountSequence: dto.accountSequence,
|
||
usdtAmount: dto.usdtAmount,
|
||
rewardEntryIds: dto.rewardEntryIds,
|
||
breakdown: dto.breakdown,
|
||
memo: dto.memo,
|
||
});
|
||
|
||
this.logger.log(`结算结果: ${JSON.stringify(result)}`);
|
||
return result;
|
||
}
|
||
}
|