| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- /**
- * 钱包服务 - 余额查询、扣费(原子事务)、充值、入账
- */
- import { Injectable, Logger } from '@nestjs/common';
- import { PrismaService } from '../prisma/prisma.service';
- import { InsufficientBalanceException } from './exceptions/insufficient-balance.exception';
- @Injectable()
- export class WalletService {
- private readonly logger = new Logger(WalletService.name);
- constructor(private readonly prisma: PrismaService) {}
- async getWallet(userId: string) {
- const user = await this.prisma.user.findUnique({
- where: { id: userId },
- select: { balanceCents: true, currency: true },
- });
- if (!user) {
- throw new Error('用户不存在');
- }
- const transactions = await this.prisma.balanceTransaction.findMany({
- where: { userId },
- orderBy: { createdAt: 'desc' },
- take: 20,
- select: {
- id: true,
- amountCents: true,
- type: true,
- refId: true,
- createdAt: true,
- },
- });
- return {
- balanceCents: Number(user.balanceCents),
- currency: user.currency,
- transactions: transactions.map((t) => ({
- ...t,
- amountCents: Number(t.amountCents),
- refId: t.refId ?? null,
- createdAt: t.createdAt.toISOString(),
- })),
- };
- }
- /**
- * 原子扣费 - 使用 UPDATE WHERE 保证余额充足
- * 返回值 { count: 0 } 表示余额不足,抛出 InsufficientBalanceException
- * 插入 BalanceTransaction type='usage'
- */
- async deduct(
- userId: string,
- amountCents: bigint,
- refId: string,
- _model?: string,
- ): Promise<{ newBalance: bigint }> {
- const result = await this.prisma.$transaction(async (tx) => {
- // 原子扣减:仅当 balanceCents >= amountCents 时更新
- const updateResult = await tx.user.updateMany({
- where: {
- id: userId,
- balanceCents: { gte: amountCents },
- },
- data: {
- balanceCents: { decrement: amountCents },
- },
- });
- if (updateResult.count === 0) {
- const user = await tx.user.findUnique({
- where: { id: userId },
- select: { balanceCents: true },
- });
- throw new InsufficientBalanceException(
- '余额不足,无法完成扣费',
- amountCents,
- user?.balanceCents ?? 0n,
- );
- }
- // 记录交易
- await tx.balanceTransaction.create({
- data: {
- userId,
- amountCents: -amountCents,
- type: 'usage',
- refId,
- },
- });
- // 读取新余额
- const updated = await tx.user.findUniqueOrThrow({
- where: { id: userId },
- select: { balanceCents: true },
- });
- return { newBalance: updated.balanceCents };
- });
- this.logger.debug(`Deducted user=${userId} amount=${amountCents} ref=${refId}`);
- return result;
- }
- /**
- * 入账(充值/退款)- 直接增加余额并记录交易
- */
- async credit(
- userId: string,
- amountCents: bigint,
- refId: string,
- type: string,
- ): Promise<{ newBalance: bigint }> {
- const result = await this.prisma.$transaction(async (tx) => {
- const updated = await tx.user.update({
- where: { id: userId },
- data: {
- balanceCents: { increment: amountCents },
- },
- select: { balanceCents: true },
- });
- await tx.balanceTransaction.create({
- data: {
- userId,
- amountCents,
- type,
- refId,
- },
- });
- return { newBalance: updated.balanceCents };
- });
- this.logger.debug(`Credited user=${userId} amount=${amountCents} ref=${refId} type=${type}`);
- return result;
- }
- }
|