212 lines
5.5 KiB
TypeScript
212 lines
5.5 KiB
TypeScript
import {
|
|
Injectable,
|
|
NotFoundException,
|
|
BadRequestException,
|
|
} from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository, DataSource } from 'typeorm';
|
|
import { CreditBalance } from './entities/credit-balance.entity';
|
|
import {
|
|
CreditTransaction,
|
|
TransactionType,
|
|
} from './entities/credit-transaction.entity';
|
|
import { CreditPackage } from './entities/credit-package.entity';
|
|
|
|
@Injectable()
|
|
export class CreditsService {
|
|
constructor(
|
|
@InjectRepository(CreditBalance)
|
|
private readonly balanceRepository: Repository<CreditBalance>,
|
|
@InjectRepository(CreditTransaction)
|
|
private readonly transactionRepository: Repository<CreditTransaction>,
|
|
@InjectRepository(CreditPackage)
|
|
private readonly packageRepository: Repository<CreditPackage>,
|
|
private readonly dataSource: DataSource,
|
|
) {}
|
|
|
|
async getBalance(userId: string): Promise<CreditBalance> {
|
|
let balance = await this.balanceRepository.findOne({ where: { userId } });
|
|
|
|
if (!balance) {
|
|
// Create initial balance for new users
|
|
balance = this.balanceRepository.create({
|
|
userId,
|
|
balance: 0,
|
|
totalPurchased: 0,
|
|
totalConsumed: 0,
|
|
totalFromReferrals: 0,
|
|
});
|
|
await this.balanceRepository.save(balance);
|
|
}
|
|
|
|
return balance;
|
|
}
|
|
|
|
async hasMinimumCredits(userId: string, minimum: number): Promise<boolean> {
|
|
const balance = await this.getBalance(userId);
|
|
return balance.balance >= minimum;
|
|
}
|
|
|
|
async addCredits(
|
|
userId: string,
|
|
amount: number,
|
|
type: TransactionType,
|
|
description: string,
|
|
referenceId?: string,
|
|
referenceType?: string,
|
|
): Promise<CreditTransaction> {
|
|
return this.dataSource.transaction(async (manager) => {
|
|
const balanceRepo = manager.getRepository(CreditBalance);
|
|
const transactionRepo = manager.getRepository(CreditTransaction);
|
|
|
|
// Get or create balance
|
|
let balance = await balanceRepo.findOne({ where: { userId } });
|
|
if (!balance) {
|
|
balance = balanceRepo.create({
|
|
userId,
|
|
balance: 0,
|
|
totalPurchased: 0,
|
|
totalConsumed: 0,
|
|
totalFromReferrals: 0,
|
|
});
|
|
}
|
|
|
|
// Update balance
|
|
balance.balance += amount;
|
|
|
|
if (type === TransactionType.PURCHASE) {
|
|
balance.totalPurchased += amount;
|
|
} else if (type === TransactionType.REFERRAL_BONUS) {
|
|
balance.totalFromReferrals += amount;
|
|
}
|
|
|
|
await balanceRepo.save(balance);
|
|
|
|
// Create transaction record
|
|
const transaction = transactionRepo.create({
|
|
userId,
|
|
type,
|
|
amount,
|
|
balanceAfter: balance.balance,
|
|
description,
|
|
referenceId,
|
|
referenceType,
|
|
});
|
|
|
|
return transactionRepo.save(transaction);
|
|
});
|
|
}
|
|
|
|
async consume(
|
|
userId: string,
|
|
amount: number,
|
|
description: string,
|
|
referenceId?: string,
|
|
referenceType?: string,
|
|
): Promise<CreditTransaction> {
|
|
return this.dataSource.transaction(async (manager) => {
|
|
const balanceRepo = manager.getRepository(CreditBalance);
|
|
const transactionRepo = manager.getRepository(CreditTransaction);
|
|
|
|
const balance = await balanceRepo.findOne({ where: { userId } });
|
|
if (!balance || balance.balance < amount) {
|
|
throw new BadRequestException('Creditos insuficientes');
|
|
}
|
|
|
|
// Deduct credits
|
|
balance.balance -= amount;
|
|
balance.totalConsumed += amount;
|
|
await balanceRepo.save(balance);
|
|
|
|
// Create transaction record
|
|
const transaction = transactionRepo.create({
|
|
userId,
|
|
type: TransactionType.CONSUMPTION,
|
|
amount: -amount,
|
|
balanceAfter: balance.balance,
|
|
description,
|
|
referenceId,
|
|
referenceType,
|
|
});
|
|
|
|
return transactionRepo.save(transaction);
|
|
});
|
|
}
|
|
|
|
async getTransactions(
|
|
userId: string,
|
|
page = 1,
|
|
limit = 20,
|
|
): Promise<{ transactions: CreditTransaction[]; total: number }> {
|
|
const [transactions, total] = await this.transactionRepository.findAndCount(
|
|
{
|
|
where: { userId },
|
|
order: { createdAt: 'DESC' },
|
|
skip: (page - 1) * limit,
|
|
take: limit,
|
|
},
|
|
);
|
|
|
|
return { transactions, total };
|
|
}
|
|
|
|
async getPackages(): Promise<CreditPackage[]> {
|
|
return this.packageRepository.find({
|
|
where: { isActive: true },
|
|
order: { sortOrder: 'ASC' },
|
|
});
|
|
}
|
|
|
|
async getPackageById(packageId: string): Promise<CreditPackage> {
|
|
const pkg = await this.packageRepository.findOne({
|
|
where: { id: packageId, isActive: true },
|
|
});
|
|
|
|
if (!pkg) {
|
|
throw new NotFoundException('Paquete no encontrado');
|
|
}
|
|
|
|
return pkg;
|
|
}
|
|
|
|
async grantWelcomeCredits(userId: string): Promise<void> {
|
|
const welcomeCredits = 50; // Free credits for new users
|
|
|
|
await this.addCredits(
|
|
userId,
|
|
welcomeCredits,
|
|
TransactionType.PROMO,
|
|
'Creditos de bienvenida',
|
|
);
|
|
}
|
|
|
|
async grantReferralBonus(
|
|
referrerId: string,
|
|
referredId: string,
|
|
referralId: string,
|
|
): Promise<void> {
|
|
const referrerBonus = 100;
|
|
const referredBonus = 50;
|
|
|
|
// Give bonus to referrer
|
|
await this.addCredits(
|
|
referrerId,
|
|
referrerBonus,
|
|
TransactionType.REFERRAL_BONUS,
|
|
'Bonus por referido',
|
|
referralId,
|
|
'referral',
|
|
);
|
|
|
|
// Give bonus to referred user
|
|
await this.addCredits(
|
|
referredId,
|
|
referredBonus,
|
|
TransactionType.REFERRAL_BONUS,
|
|
'Bonus por usar codigo de referido',
|
|
referralId,
|
|
'referral',
|
|
);
|
|
}
|
|
}
|