feat: 手动充值、邀请码注册与后台管理增强

新增玩家手动充值全流程(收款方式配置、充值下单/审核、钱包上分),
支持邀请码注册、邀请历史与专属返水率;完善后台代理/玩家管理与响应式操作栏,
并补充前台注册、充值页及多语言错误码。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 12:20:11 +08:00
parent 618fb49511
commit 10485ecfaf
98 changed files with 7908 additions and 856 deletions

View File

@@ -8,11 +8,19 @@ import {
Query,
Headers,
UseGuards,
UseInterceptors,
UploadedFile,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
import { randomUUID } from 'crypto';
import { mkdir, writeFile } from 'fs/promises';
import { extname, join } from 'path';
import { JwtAuthGuard, PlayerGuard } from '../../domains/identity/guards';
import { CurrentUser, Public } from '../../shared/common/decorators';
import { jsonResponse } from '../../shared/common/filters';
import { appBadRequest } from '../../shared/common/app-error';
import { getUploadRoot } from '../../shared/uploads/upload-paths';
import { UsersService } from '../../domains/identity/users.service';
import { SystemConfigService } from '../../shared/config/system-config.service';
import { WalletService } from '../../domains/ledger/wallet.service';
@@ -21,6 +29,7 @@ import { OutrightService } from '../../domains/catalog/outright.service';
import { BetsService } from '../../domains/betting/bets.service';
import { ContentService } from '../../domains/operations/content/content.service';
import { CashbackService } from '../../domains/operations/cashback/cashback.service';
import { DepositService } from '../../domains/deposit/deposit.service';
import { IsString, IsNumber, IsArray, ValidateNested, Min, IsOptional } from 'class-validator';
import { Type } from 'class-transformer';
@@ -98,6 +107,7 @@ export class PlayerController {
private content: ContentService,
private cashback: CashbackService,
private systemConfig: SystemConfigService,
private deposit: DepositService,
) {}
private async formatPlayerProfile(user: NonNullable<Awaited<ReturnType<UsersService['findById']>>>) {
@@ -290,4 +300,68 @@ export class PlayerController {
const items = await this.cashback.getUserCashbacks(userId);
return jsonResponse(items);
}
// ============ Deposit / Recharge ============
@Get('payment-methods')
async paymentMethods(
@Query('methodType') methodType?: string,
@CurrentUser('locale') userLocale?: string,
@Headers('x-locale') headerLocale?: string,
) {
const locale = userLocale || headerLocale || 'zh-CN';
const items = await this.deposit.listPlayerPaymentMethods(methodType || undefined, locale);
return jsonResponse(items);
}
@Post('deposit-orders')
@UseInterceptors(FileInterceptor('screenshot', { limits: { fileSize: 5 * 1024 * 1024 } }))
async createDepositOrder(
@CurrentUser('id') userId: bigint,
@UploadedFile() file: { originalname: string; mimetype: string; buffer: Buffer; size: number } | undefined,
@Body() body: { paymentMethodId: string; amount: string },
) {
if (!file) throw appBadRequest('SCREENSHOT_REQUIRED');
if (!file.mimetype.startsWith('image/')) throw appBadRequest('FILE_MUST_BE_IMAGE');
const amount = parseFloat(body.amount);
if (!amount || amount <= 0) throw appBadRequest('INVALID_AMOUNT');
if (!body.paymentMethodId) throw appBadRequest('PAYMENT_METHOD_REQUIRED');
// Save screenshot
const ext = extname(file.originalname || '.jpg').toLowerCase() || '.jpg';
const filename = `${Date.now()}-${randomUUID().slice(0, 8)}${ext}`;
const root = getUploadRoot();
const targetDir = join(root, 'deposits');
await mkdir(targetDir, { recursive: true });
await writeFile(join(targetDir, filename), file.buffer);
const screenshotUrl = `/uploads/deposits/${filename}`;
const order = await this.deposit.createDepositOrder(
userId,
BigInt(body.paymentMethodId),
amount,
screenshotUrl,
);
return jsonResponse({
id: order.id.toString(),
orderNo: order.orderNo,
amount: order.amount.toString(),
status: order.status,
createdAt: order.createdAt,
});
}
@Get('deposit-orders')
async myDepositOrders(
@CurrentUser('id') userId: bigint,
@Query('page') page?: string,
) {
const result = await this.deposit.getPlayerDepositOrders(
userId,
page ? parseInt(page, 10) : 1,
);
return jsonResponse(result);
}
}