|
|
import { AppDataSource } from '../config/database'; |
|
|
import { Wallet } from '../entities/Wallet'; |
|
|
import { Transaction, TransactionType, TransactionStatus } from '../entities/Transaction'; |
|
|
import { Agent } from '../entities/Agent'; |
|
|
|
|
|
const walletRepository = AppDataSource.getRepository(Wallet); |
|
|
const transactionRepository = AppDataSource.getRepository(Transaction); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export class WalletService { |
|
|
private readonly POINTS_PER_DOLLAR = 20; |
|
|
private readonly FREE_TASKS_PER_AGENT = 2; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getOrCreateWallet(userId: string): Promise<Wallet> { |
|
|
let wallet = await walletRepository.findOne({ |
|
|
where: { userId }, |
|
|
}); |
|
|
|
|
|
if (!wallet) { |
|
|
wallet = walletRepository.create({ |
|
|
userId, |
|
|
balance: 0, |
|
|
freeTasksUsed: 0, |
|
|
}); |
|
|
wallet = await walletRepository.save(wallet); |
|
|
} |
|
|
|
|
|
return wallet; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getBalance(userId: string): Promise<number> { |
|
|
const wallet = await this.getOrCreateWallet(userId); |
|
|
return Number(wallet.balance); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async addPoints( |
|
|
userId: string, |
|
|
points: number, |
|
|
paymentReference: string, |
|
|
metadata?: Record<string, any> |
|
|
): Promise<Transaction> { |
|
|
|
|
|
const existingTx = await transactionRepository.findOne({ |
|
|
where: { paymentReference, type: TransactionType.PURCHASE }, |
|
|
}); |
|
|
|
|
|
if (existingTx && existingTx.status === TransactionStatus.COMPLETED) { |
|
|
console.log(`Transaction ${paymentReference} already processed`); |
|
|
return existingTx; |
|
|
} |
|
|
|
|
|
const wallet = await this.getOrCreateWallet(userId); |
|
|
|
|
|
const newBalance = Number(wallet.balance) + points; |
|
|
|
|
|
|
|
|
const transaction = transactionRepository.create({ |
|
|
walletId: wallet.id, |
|
|
userId, |
|
|
type: TransactionType.PURCHASE, |
|
|
status: TransactionStatus.COMPLETED, |
|
|
amount: points, |
|
|
balanceAfter: newBalance, |
|
|
paymentReference, |
|
|
description: `Purchased ${points} points`, |
|
|
metadata, |
|
|
}); |
|
|
|
|
|
await transactionRepository.save(transaction); |
|
|
|
|
|
|
|
|
wallet.balance = newBalance; |
|
|
await walletRepository.save(wallet); |
|
|
|
|
|
return transaction; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getTransactionByReference(reference: string): Promise<Transaction | null> { |
|
|
return await transactionRepository.findOne({ |
|
|
where: { paymentReference: reference }, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async chargeForTask( |
|
|
userId: string, |
|
|
agentId: string, |
|
|
agent: Agent |
|
|
): Promise<{ success: boolean; transaction?: Transaction; isFree: boolean }> { |
|
|
const wallet = await this.getOrCreateWallet(userId); |
|
|
const pointsRequired = Number(agent.pointsPerTask) || 0; |
|
|
|
|
|
if (pointsRequired === 0) { |
|
|
|
|
|
const transaction = transactionRepository.create({ |
|
|
walletId: wallet.id, |
|
|
userId, |
|
|
agentId, |
|
|
type: TransactionType.FREE, |
|
|
status: TransactionStatus.COMPLETED, |
|
|
amount: 0, |
|
|
balanceAfter: Number(wallet.balance), |
|
|
description: `Free task with ${agent.name}`, |
|
|
}); |
|
|
|
|
|
await transactionRepository.save(transaction); |
|
|
return { success: true, transaction, isFree: true }; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const freeTasksCount = await transactionRepository.count({ |
|
|
where: { |
|
|
userId, |
|
|
type: TransactionType.FREE, |
|
|
}, |
|
|
}); |
|
|
|
|
|
if (freeTasksCount < this.FREE_TASKS_PER_AGENT) { |
|
|
|
|
|
const transaction = transactionRepository.create({ |
|
|
walletId: wallet.id, |
|
|
userId, |
|
|
agentId, |
|
|
type: TransactionType.FREE, |
|
|
status: TransactionStatus.COMPLETED, |
|
|
amount: 0, |
|
|
balanceAfter: Number(wallet.balance), |
|
|
description: `Free task ${freeTasksCount + 1}/${this.FREE_TASKS_PER_AGENT} with ${agent.name}`, |
|
|
metadata: { agentId, agentName: agent.name }, |
|
|
}); |
|
|
|
|
|
await transactionRepository.save(transaction); |
|
|
return { success: true, transaction, isFree: true }; |
|
|
} |
|
|
|
|
|
|
|
|
const currentBalance = Number(wallet.balance); |
|
|
if (currentBalance < pointsRequired) { |
|
|
return { success: false, isFree: false }; |
|
|
} |
|
|
|
|
|
|
|
|
const newBalance = currentBalance - pointsRequired; |
|
|
|
|
|
const transaction = transactionRepository.create({ |
|
|
walletId: wallet.id, |
|
|
userId, |
|
|
agentId, |
|
|
type: TransactionType.CHARGE, |
|
|
status: TransactionStatus.COMPLETED, |
|
|
amount: -pointsRequired, |
|
|
balanceAfter: newBalance, |
|
|
description: `Charged ${pointsRequired} points for ${agent.name} task`, |
|
|
metadata: { agentId, agentName: agent.name, pointsPerTask: pointsRequired }, |
|
|
}); |
|
|
|
|
|
await transactionRepository.save(transaction); |
|
|
|
|
|
|
|
|
wallet.balance = newBalance; |
|
|
await walletRepository.save(wallet); |
|
|
|
|
|
return { success: true, transaction, isFree: false }; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
dollarsToPoints(dollars: number): number { |
|
|
return Math.round(dollars * this.POINTS_PER_DOLLAR); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pointsToDollars(points: number): number { |
|
|
return points / this.POINTS_PER_DOLLAR; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getTransactionHistory( |
|
|
userId: string, |
|
|
limit: number = 50 |
|
|
): Promise<Transaction[]> { |
|
|
const wallet = await this.getOrCreateWallet(userId); |
|
|
|
|
|
return await transactionRepository.find({ |
|
|
where: { walletId: wallet.id }, |
|
|
relations: ['agent'], |
|
|
order: { createdAt: 'DESC' }, |
|
|
take: limit, |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getFreeTasksRemaining(userId: string): Promise<number> { |
|
|
const freeTasksCount = await transactionRepository.count({ |
|
|
where: { |
|
|
userId, |
|
|
type: TransactionType.FREE, |
|
|
}, |
|
|
}); |
|
|
|
|
|
return Math.max(0, this.FREE_TASKS_PER_AGENT - freeTasksCount); |
|
|
} |
|
|
} |
|
|
|