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

221 lines
8.6 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, Query, UseGuards, Headers, HttpException, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiResponse, ApiQuery } from '@nestjs/swagger';
import { WalletApplicationService } from '@/application/services';
import { GetMyWalletQuery } from '@/application/queries';
import { ClaimRewardsCommand, SettleRewardsCommand, RequestWithdrawalCommand } from '@/application/commands';
import { CurrentUser, CurrentUserPayload } from '@/shared/decorators';
import { JwtAuthGuard } from '@/shared/guards/jwt-auth.guard';
import { SettleRewardsDTO, RequestWithdrawalDTO } from '@/api/dto/request';
import { WalletResponseDTO, WithdrawalResponseDTO, WithdrawalListItemDTO, WithdrawalFeeConfigResponseDTO, CalculateFeeResponseDTO } from '@/api/dto/response';
import { IdentityClientService } from '@/infrastructure/external/identity';
import { FeeConfigRepositoryImpl } from '@/infrastructure/persistence/repositories';
@ApiTags('Wallet')
@Controller('wallet')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
export class WalletController {
constructor(
private readonly walletService: WalletApplicationService,
private readonly identityClient: IdentityClientService,
private readonly feeConfigRepo: FeeConfigRepositoryImpl,
) {}
@Get('my-wallet')
@ApiOperation({ summary: '查询我的钱包', description: '获取当前用户的钱包余额、算力和奖励信息' })
@ApiResponse({ status: 200, type: WalletResponseDTO })
async getMyWallet(@CurrentUser() user: CurrentUserPayload): Promise<WalletResponseDTO> {
const query = new GetMyWalletQuery(
user.accountSequence,
user.userId,
);
return this.walletService.getMyWallet(query);
}
@Post('claim-rewards')
@ApiOperation({ summary: '领取奖励', description: '将待领取的奖励转为可结算状态' })
@ApiResponse({ status: 200, description: '领取成功' })
async claimRewards(@CurrentUser() user: CurrentUserPayload): Promise<{ message: string }> {
const command = new ClaimRewardsCommand(user.userId);
await this.walletService.claimRewards(command);
return { message: 'Rewards claimed successfully' };
}
@Post('settle')
@ApiOperation({ summary: '结算收益', description: '将可结算的USDT兑换为指定币种' })
@ApiResponse({ status: 200, description: '结算成功' })
async settleRewards(
@CurrentUser() user: CurrentUserPayload,
@Body() dto: SettleRewardsDTO,
): Promise<{ settlementOrderId: string }> {
const command = new SettleRewardsCommand(
user.userId,
dto.usdtAmount,
dto.settleCurrency,
);
const orderId = await this.walletService.settleRewards(command);
return { settlementOrderId: orderId };
}
@Post('withdraw/send-sms')
@ApiOperation({ summary: '发送提取验证短信', description: '向用户手机发送提取验证码' })
@ApiResponse({ status: 200, description: '发送成功' })
async sendWithdrawSms(
@CurrentUser() user: CurrentUserPayload,
@Headers('authorization') authHeader: string,
): Promise<{ message: string }> {
const token = authHeader?.replace('Bearer ', '') || '';
// 调用 identity-service 发送短信验证码
await this.identityClient.sendWithdrawSmsCode(user.userId, token);
return { message: '验证码已发送' };
}
@Post('withdraw')
@ApiOperation({ summary: '申请提现', description: '将USDT提现到指定地址需要短信验证和密码验证' })
@ApiResponse({ status: 201, type: WithdrawalResponseDTO })
async requestWithdrawal(
@CurrentUser() user: CurrentUserPayload,
@Body() dto: RequestWithdrawalDTO,
@Headers('authorization') authHeader: string,
): Promise<WithdrawalResponseDTO> {
// 提取 JWT token
const token = authHeader?.replace('Bearer ', '') || '';
// 验证短信验证码
if (!dto.smsCode) {
throw new HttpException('请输入短信验证码', HttpStatus.BAD_REQUEST);
}
const isSmsValid = await this.identityClient.verifyWithdrawSmsCode(user.userId, dto.smsCode, token);
if (!isSmsValid) {
throw new HttpException('短信验证码错误,请重试', HttpStatus.BAD_REQUEST);
}
// 验证登录密码
if (!dto.password) {
throw new HttpException('请输入登录密码', HttpStatus.BAD_REQUEST);
}
const isPasswordValid = await this.identityClient.verifyPassword(user.userId, dto.password, token);
if (!isPasswordValid) {
throw new HttpException('登录密码错误,请重试', HttpStatus.BAD_REQUEST);
}
// 处理 toAddress: 如果是 accountSequence 格式,转换为区块链地址
let actualAddress = dto.toAddress;
if (dto.toAddress.startsWith('D') && dto.toAddress.length === 12) {
// accountSequence 格式,需要查询对应的区块链地址
const resolvedAddress = await this.identityClient.resolveAccountSequenceToAddress(
dto.toAddress,
dto.chainType,
token,
);
if (!resolvedAddress) {
throw new HttpException('无效的充值ID未找到对应地址', HttpStatus.BAD_REQUEST);
}
actualAddress = resolvedAddress;
}
const command = new RequestWithdrawalCommand(
user.userId,
dto.amount,
actualAddress,
dto.chainType,
);
return this.walletService.requestWithdrawal(command);
}
@Get('withdrawals')
@ApiOperation({ summary: '查询提现记录', description: '获取用户的提现订单列表' })
@ApiResponse({ status: 200, type: [WithdrawalListItemDTO] })
async getWithdrawals(
@CurrentUser() user: CurrentUserPayload,
): Promise<WithdrawalListItemDTO[]> {
return this.walletService.getWithdrawals(user.userId);
}
@Get('pending-rewards')
@ApiOperation({ summary: '查询待领取奖励列表', description: '获取用户的逐笔待领取奖励,包含过期时间' })
@ApiResponse({ status: 200, description: '待领取奖励列表' })
async getPendingRewards(
@CurrentUser() user: CurrentUserPayload,
): Promise<Array<{
id: string;
usdtAmount: number;
hashpowerAmount: number;
sourceOrderId: string;
allocationType: string;
expireAt: string;
status: string;
createdAt: string;
}>> {
return this.walletService.getPendingRewards(user.accountSequence);
}
@Get('settleable-rewards')
@ApiOperation({ summary: '查询可结算奖励列表', description: '获取用户的逐笔可结算奖励(已领取待结算)' })
@ApiResponse({ status: 200, description: '可结算奖励列表' })
async getSettleableRewards(
@CurrentUser() user: CurrentUserPayload,
): Promise<Array<{
id: string;
usdtAmount: number;
hashpowerAmount: number;
sourceOrderId: string;
allocationType: string;
settledAt: string;
createdAt: string;
}>> {
return this.walletService.getSettleableRewards(user.accountSequence);
}
@Get('expired-rewards')
@ApiOperation({ summary: '查询已过期奖励列表', description: '获取用户的逐笔已过期奖励24h未领取' })
@ApiResponse({ status: 200, description: '已过期奖励列表' })
async getExpiredRewards(
@CurrentUser() user: CurrentUserPayload,
): Promise<Array<{
id: string;
usdtAmount: number;
hashpowerAmount: number;
sourceOrderId: string;
allocationType: string;
expiredAt: string;
createdAt: string;
}>> {
return this.walletService.getExpiredRewards(user.accountSequence);
}
@Get('fee-config')
@ApiOperation({ summary: '查询提取手续费配置', description: '获取当前生效的提取手续费配置' })
@ApiResponse({ status: 200, type: WithdrawalFeeConfigResponseDTO })
async getFeeConfig(): Promise<WithdrawalFeeConfigResponseDTO> {
return this.feeConfigRepo.getActiveConfig();
}
@Get('calculate-fee')
@ApiOperation({ summary: '计算提取手续费', description: '根据提取金额计算手续费' })
@ApiQuery({ name: 'amount', type: Number, description: '提取金额' })
@ApiResponse({ status: 200, type: CalculateFeeResponseDTO })
async calculateFee(
@Query('amount') amountStr: string,
): Promise<CalculateFeeResponseDTO> {
const amount = parseFloat(amountStr);
if (isNaN(amount) || amount <= 0) {
throw new HttpException('无效的金额', HttpStatus.BAD_REQUEST);
}
const { fee, feeType, feeValue } = await this.feeConfigRepo.calculateFee(amount);
return {
amount,
fee,
totalRequired: amount + fee,
receiverGets: amount, // 接收方收到完整金额
feeType,
feeValue,
};
}
}