核销代码
This commit is contained in:
@@ -100,7 +100,10 @@ export class BenefitService {
|
||||
const coupon = await tx.benefitCoupon.findUniqueOrThrow({
|
||||
where: { id: BigInt(alloc.couponId) },
|
||||
});
|
||||
const allocAmount = alloc.amount;
|
||||
const allocAmount = Number(alloc.amount);
|
||||
if (!Number.isFinite(allocAmount) || allocAmount <= 0) {
|
||||
throw new Error('BENEFIT_ALLOC_INVALID');
|
||||
}
|
||||
const updated = await tx.benefitCoupon.updateMany({
|
||||
where: { id: coupon.id, version: coupon.version, balance: { gte: allocAmount } },
|
||||
data: {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { RedeemService } from '../redeem/redeem.service';
|
||||
import type {
|
||||
@@ -13,9 +13,17 @@ export class AdminRedeemDebugService {
|
||||
private readonly redeemService: RedeemService,
|
||||
) {}
|
||||
|
||||
private parseId(value: string, label: string): bigint {
|
||||
const normalized = value?.trim();
|
||||
if (!normalized || !/^\d+$/.test(normalized)) {
|
||||
throw new BadRequestException(`${label}格式无效`);
|
||||
}
|
||||
return BigInt(normalized);
|
||||
}
|
||||
|
||||
private async resolveStoreAccountId(storeId: string): Promise<bigint> {
|
||||
const account = await this.prisma.storeAccount.findFirst({
|
||||
where: { storeId: BigInt(storeId), status: 'ACTIVE' },
|
||||
where: { storeId: this.parseId(storeId, '门店 ID'), status: 'ACTIVE' },
|
||||
orderBy: { id: 'asc' },
|
||||
select: { id: true, store: { select: { name: true } } },
|
||||
});
|
||||
@@ -26,7 +34,7 @@ export class AdminRedeemDebugService {
|
||||
}
|
||||
|
||||
async createToken(dto: AdminRedeemDebugCreateTokenDto) {
|
||||
return this.redeemService.createToken(BigInt(dto.userId), {
|
||||
return this.redeemService.createToken(this.parseId(dto.userId, '用户 ID'), {
|
||||
amount: dto.amount,
|
||||
couponId: dto.couponId,
|
||||
storeId: dto.storeId,
|
||||
|
||||
@@ -150,23 +150,38 @@ export class RedeemService {
|
||||
throw new BadRequestException('核销码数据异常');
|
||||
}
|
||||
|
||||
const allocSum = allocations.reduce((sum, item) => sum + item.amount, 0);
|
||||
if (Math.abs(allocSum - cached.amount) > 0.001) {
|
||||
const normalizedAllocations = allocations.map((item) => ({
|
||||
couponId: String(item.couponId),
|
||||
amount: Number(item.amount),
|
||||
}));
|
||||
const tokenAmount = Number(cached.amount);
|
||||
if (!Number.isFinite(tokenAmount) || tokenAmount <= 0) {
|
||||
throw new BadRequestException('核销码数据异常');
|
||||
}
|
||||
|
||||
for (const alloc of allocations) {
|
||||
const allocSum = normalizedAllocations.reduce((sum, item) => sum + item.amount, 0);
|
||||
if (Math.abs(allocSum - tokenAmount) > 0.001) {
|
||||
throw new BadRequestException('核销码数据异常');
|
||||
}
|
||||
|
||||
for (const alloc of normalizedAllocations) {
|
||||
let couponId: bigint;
|
||||
try {
|
||||
couponId = BigInt(alloc.couponId);
|
||||
} catch {
|
||||
throw new BadRequestException('核销码数据异常');
|
||||
}
|
||||
const coupon = await this.prisma.benefitCoupon.findUnique({
|
||||
where: { id: BigInt(alloc.couponId) },
|
||||
where: { id: couponId },
|
||||
});
|
||||
if (!coupon) throw new BadRequestException('券不存在');
|
||||
const check = validateRedeemAmount(Number(coupon.balance), alloc.amount, Number(coupon.balance));
|
||||
if (!check.ok) throw new BadRequestException(check.message);
|
||||
}
|
||||
|
||||
const amount = Number(cached.amount);
|
||||
const cityRule = await this.prisma.commonCityCommissionRule.findFirst({
|
||||
where: { city: { stores: { some: { id: account.storeId } } } },
|
||||
const amount = tokenAmount;
|
||||
const cityRule = await this.prisma.commonCityCommissionRule.findUnique({
|
||||
where: { cityId: account.store.cityId },
|
||||
});
|
||||
const settlementRate = cityRule ? Number(cityRule.storeSettlementRate) : 0.6;
|
||||
const settleAmount = calcRedeemSettleAmount(amount, settlementRate);
|
||||
@@ -174,29 +189,40 @@ export class RedeemService {
|
||||
let record;
|
||||
try {
|
||||
record = await this.prisma.$transaction(async (tx) => {
|
||||
await this.benefitService.deductCoupons(tx, allocations, 'STORE', account.storeId);
|
||||
await this.benefitService.deductCoupons(tx, normalizedAllocations, 'STORE', account.storeId);
|
||||
|
||||
const redeemRecord = await tx.redeemRecord.create({
|
||||
data: {
|
||||
redeemNo: generateRedeemNo(),
|
||||
userId: BigInt(cached.userId),
|
||||
couponId: BigInt(allocations[0].couponId),
|
||||
couponId: BigInt(normalizedAllocations[0].couponId),
|
||||
storeId: account.storeId,
|
||||
amount,
|
||||
settleAmount,
|
||||
},
|
||||
});
|
||||
|
||||
await this.settlementService.createStorePayout(
|
||||
redeemRecord.id,
|
||||
account.storeId,
|
||||
amount,
|
||||
settleAmount,
|
||||
settlementRate,
|
||||
tx,
|
||||
);
|
||||
|
||||
return redeemRecord;
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message === 'BENEFIT_DEDUCT_CONFLICT') {
|
||||
throw new BadRequestException('核销失败,请重试');
|
||||
}
|
||||
if (e instanceof Error && e.message === 'BENEFIT_ALLOC_INVALID') {
|
||||
throw new BadRequestException('核销码数据异常');
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
await this.settlementService.createStorePayout(record.id, account.storeId, amount, settleAmount, settlementRate);
|
||||
await this.redis.del(`redeem:token:${body.token}`);
|
||||
|
||||
this.analyticsService.trackOneSafe(BigInt(cached.userId), 'SHOP_H5', {
|
||||
|
||||
@@ -17,10 +17,12 @@ export class SettlementService {
|
||||
redeemAmount: number,
|
||||
payoutAmount: number,
|
||||
settlementRate: number,
|
||||
tx?: Prisma.TransactionClient,
|
||||
) {
|
||||
const expectedPayAt = new Date();
|
||||
expectedPayAt.setDate(expectedPayAt.getDate() + 1);
|
||||
const payout = await this.prisma.storePayout.create({
|
||||
const client = tx ?? this.prisma;
|
||||
const payout = await client.storePayout.create({
|
||||
data: {
|
||||
redeemRecordId,
|
||||
storeId,
|
||||
|
||||
Reference in New Issue
Block a user