feat: 手动充值、邀请码注册与后台管理增强
新增玩家手动充值全流程(收款方式配置、充值下单/审核、钱包上分), 支持邀请码注册、邀请历史与专属返水率;完善后台代理/玩家管理与响应式操作栏, 并补充前台注册、充值页及多语言错误码。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user