import { Controller, Get, Post, Body, Param, Query, Logger } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery } from '@nestjs/swagger'; import { WalletApplicationService, FiatWithdrawalApplicationService } from '@/application/services'; import { GetMyWalletQuery } from '@/application/queries'; import { DeductForPlantingCommand, AllocateFundsCommand, FundAllocationItem, FreezeForPlantingCommand, ConfirmPlantingDeductionCommand, UnfreezeForPlantingCommand, } from '@/application/commands'; import { Public } from '@/shared/decorators'; import { FiatWithdrawalStatus } from '@/domain/value-objects/fiat-withdrawal-status.enum'; /** * 内部API控制器 - 供其他微服务调用 * 不需要JWT认证,通过内部网络访问 */ @ApiTags('Internal Wallet API') @Controller('wallets') export class InternalWalletController { private readonly logger = new Logger(InternalWalletController.name); constructor( private readonly walletService: WalletApplicationService, private readonly fiatWithdrawalService: FiatWithdrawalApplicationService, ) {} @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; 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; } // =============== 法币提现管理 API =============== @Get('fiat-withdrawals/pending-review') @Public() @ApiOperation({ summary: '获取待审核的法币提现订单(内部API)' }) @ApiResponse({ status: 200, description: '待审核订单列表' }) async getFiatWithdrawalsPendingReview() { return this.fiatWithdrawalService.getPendingReviewOrders(); } @Get('fiat-withdrawals/pending-payment') @Public() @ApiOperation({ summary: '获取待打款的法币提现订单(内部API)' }) @ApiResponse({ status: 200, description: '待打款订单列表' }) async getFiatWithdrawalsPendingPayment() { return this.fiatWithdrawalService.getPendingPaymentOrders(); } @Get('fiat-withdrawals/by-status') @Public() @ApiOperation({ summary: '根据状态获取法币提现订单(内部API)' }) @ApiQuery({ name: 'status', enum: FiatWithdrawalStatus }) @ApiResponse({ status: 200, description: '订单列表' }) async getFiatWithdrawalsByStatus(@Query('status') status: FiatWithdrawalStatus) { return this.fiatWithdrawalService.getOrdersByStatus(status); } @Post('fiat-withdrawals/:orderNo/review') @Public() @ApiOperation({ summary: '审核法币提现订单(内部API)' }) @ApiParam({ name: 'orderNo', description: '订单号' }) @ApiResponse({ status: 200, description: '审核结果' }) async reviewFiatWithdrawal( @Param('orderNo') orderNo: string, @Body() dto: { approved: boolean; reviewedBy: string; remark?: string }, ) { return this.fiatWithdrawalService.reviewFiatWithdrawal({ orderNo, approved: dto.approved, reviewedBy: dto.reviewedBy, remark: dto.remark, }); } @Post('fiat-withdrawals/:orderNo/start-payment') @Public() @ApiOperation({ summary: '开始打款(内部API)' }) @ApiParam({ name: 'orderNo', description: '订单号' }) @ApiResponse({ status: 200, description: '操作结果' }) async startFiatPayment( @Param('orderNo') orderNo: string, @Body() dto: { paidBy: string }, ) { return this.fiatWithdrawalService.startPayment({ orderNo, paidBy: dto.paidBy, }); } @Post('fiat-withdrawals/:orderNo/complete-payment') @Public() @ApiOperation({ summary: '完成打款(内部API)' }) @ApiParam({ name: 'orderNo', description: '订单号' }) @ApiResponse({ status: 200, description: '操作结果' }) async completeFiatPayment( @Param('orderNo') orderNo: string, @Body() dto: { paymentProof?: string; remark?: string }, ) { return this.fiatWithdrawalService.completePayment({ orderNo, paymentProof: dto.paymentProof, remark: dto.remark, }); } }