feat: 手动充值、邀请码注册与后台管理增强
新增玩家手动充值全流程(收款方式配置、充值下单/审核、钱包上分), 支持邀请码注册、邀请历史与专属返水率;完善后台代理/玩家管理与响应式操作栏, 并补充前台注册、充值页及多语言错误码。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../../../shared/prisma/prisma.service';
|
||||
import { WalletService } from '../../ledger/wallet.service';
|
||||
import { SystemConfigService } from '../../../shared/config/system-config.service';
|
||||
import { Decimal } from '@prisma/client/runtime/library';
|
||||
import { generateBatchNo } from '../../../shared/common/decorators';
|
||||
import { appBadRequest } from '../../../shared/common/app-error';
|
||||
@@ -33,6 +34,7 @@ export class CashbackService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private wallet: WalletService,
|
||||
private systemConfig: SystemConfigService,
|
||||
) {}
|
||||
|
||||
/** 已被待发放/已发放返水批次占用的注单 */
|
||||
@@ -55,14 +57,15 @@ export class CashbackService {
|
||||
eligibleBetCount: number;
|
||||
skippedClaimedCount: number;
|
||||
}> {
|
||||
const [settledBets, rules, agentProfiles, claimedBetIds] = await Promise.all([
|
||||
const [settledBets, rules, agentProfiles, claimedBetIds, platformDirectRateRaw] =
|
||||
await Promise.all([
|
||||
this.prisma.bet.findMany({
|
||||
where: {
|
||||
status: { in: ['WON', 'LOST'] },
|
||||
settledAt: { gte: periodStart, lte: periodEnd },
|
||||
},
|
||||
include: {
|
||||
user: { select: { id: true, parentId: true } },
|
||||
user: { select: { id: true, parentId: true, inviteSponsorId: true } },
|
||||
selections: { select: { marketType: true } },
|
||||
},
|
||||
}),
|
||||
@@ -71,11 +74,46 @@ export class CashbackService {
|
||||
select: { userId: true, cashbackRate: true },
|
||||
}),
|
||||
this.loadClaimedBetIds(),
|
||||
this.systemConfig.getPlatformDirectCashbackSettings(),
|
||||
]);
|
||||
|
||||
const platformDirectDefaultRate = new Decimal(platformDirectRateRaw.platformDirectRate);
|
||||
const adminInviteDefaultRate = new Decimal(platformDirectRateRaw.adminInviteRate);
|
||||
|
||||
const agentRateById = new Map(
|
||||
agentProfiles.map((p) => [p.userId.toString(), new Decimal(p.cashbackRate)]),
|
||||
);
|
||||
const sponsorTypeById = new Map<string, string>();
|
||||
const sponsorIds = [
|
||||
...new Set(
|
||||
settledBets
|
||||
.map((b) => b.user.inviteSponsorId)
|
||||
.filter((id): id is bigint => id != null),
|
||||
),
|
||||
];
|
||||
if (sponsorIds.length > 0) {
|
||||
const sponsors = await this.prisma.user.findMany({
|
||||
where: { id: { in: sponsorIds } },
|
||||
select: { id: true, userType: true },
|
||||
});
|
||||
for (const sponsor of sponsors) {
|
||||
sponsorTypeById.set(sponsor.id.toString(), sponsor.userType);
|
||||
}
|
||||
}
|
||||
|
||||
const resolveDefaultRate = (user: {
|
||||
parentId: bigint | null;
|
||||
inviteSponsorId: bigint | null;
|
||||
}) => {
|
||||
if (user.parentId) {
|
||||
return agentRateById.get(user.parentId.toString()) ?? new Decimal(0);
|
||||
}
|
||||
if (user.inviteSponsorId) {
|
||||
const sponsorType = sponsorTypeById.get(user.inviteSponsorId.toString());
|
||||
if (sponsorType === 'ADMIN') return adminInviteDefaultRate;
|
||||
}
|
||||
return platformDirectDefaultRate;
|
||||
};
|
||||
const ruleRows: CashbackRuleRow[] = rules.map((r) => ({
|
||||
targetType: r.targetType,
|
||||
targetId: r.targetId,
|
||||
@@ -93,9 +131,7 @@ export class CashbackService {
|
||||
|
||||
for (const bet of settledBets) {
|
||||
const agentId = bet.user.parentId;
|
||||
const agentDefaultRate = agentId
|
||||
? agentRateById.get(agentId.toString()) ?? new Decimal(0)
|
||||
: new Decimal(0);
|
||||
const agentDefaultRate = resolveDefaultRate(bet.user);
|
||||
const marketTypes = bet.selections.map((s) => s.marketType);
|
||||
const rate = resolveCashbackRateForBet({
|
||||
userId: bet.userId,
|
||||
@@ -538,4 +574,64 @@ export class CashbackService {
|
||||
createdAt: item.createdAt,
|
||||
}));
|
||||
}
|
||||
|
||||
async getPlayerCustomCashbackRate(userId: bigint): Promise<Decimal | null> {
|
||||
const rule = await this.prisma.cashbackRule.findFirst({
|
||||
where: {
|
||||
targetType: 'USER',
|
||||
targetId: userId,
|
||||
isActive: true,
|
||||
marketType: null,
|
||||
},
|
||||
orderBy: { updatedAt: 'desc' },
|
||||
});
|
||||
if (!rule) return null;
|
||||
const rate = new Decimal(rule.rate);
|
||||
return rate.gt(0) ? rate : null;
|
||||
}
|
||||
|
||||
async setPlayerCustomCashbackRate(userId: bigint, rate: Decimal | null) {
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
await tx.cashbackRule.updateMany({
|
||||
where: { targetType: 'USER', targetId: userId },
|
||||
data: { isActive: false },
|
||||
});
|
||||
if (rate && rate.gt(0)) {
|
||||
await tx.cashbackRule.create({
|
||||
data: {
|
||||
name: `Player ${userId.toString()}`,
|
||||
targetType: 'USER',
|
||||
targetId: userId,
|
||||
rate,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async resolvePlayerDefaultCashbackRate(params: {
|
||||
parentId: bigint | null;
|
||||
inviteSponsorId?: bigint | null;
|
||||
}): Promise<Decimal> {
|
||||
if (params.parentId) {
|
||||
const profile = await this.prisma.agentProfile.findUnique({
|
||||
where: { userId: params.parentId },
|
||||
select: { cashbackRate: true },
|
||||
});
|
||||
return profile ? new Decimal(profile.cashbackRate) : new Decimal(0);
|
||||
}
|
||||
|
||||
const settings = await this.systemConfig.getPlatformDirectCashbackSettings();
|
||||
if (params.inviteSponsorId) {
|
||||
const sponsor = await this.prisma.user.findUnique({
|
||||
where: { id: params.inviteSponsorId },
|
||||
select: { userType: true },
|
||||
});
|
||||
if (sponsor?.userType === 'ADMIN') {
|
||||
return new Decimal(settings.adminInviteRate);
|
||||
}
|
||||
}
|
||||
return new Decimal(settings.platformDirectRate);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user