rwadurian/backend/services/wallet-service/src/api/controllers/internal-wallet.controller.ts

198 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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');
// 如果是 accountSequenceuserId 参数设为 '0'(会被忽略,通过 accountSequence 查找)
// 如果是纯 userIdaccountSequence 参数设为 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;
}
}