File size: 6,601 Bytes
b5e5eac 4a285d2 b5e5eac 4a285d2 b5e5eac 4a285d2 b5e5eac |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
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);
/**
* Wallet Service
* Manages user wallet points and transactions
* 1 point = $0.05
*/
export class WalletService {
private readonly POINTS_PER_DOLLAR = 20; // 1 point = $0.05, so $1 = 20 points
private readonly FREE_TASKS_PER_AGENT = 2;
/**
* Get or create wallet for user
*/
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;
}
/**
* Get wallet balance
*/
async getBalance(userId: string): Promise<number> {
const wallet = await this.getOrCreateWallet(userId);
return Number(wallet.balance);
}
/**
* Add points to wallet (purchase)
* Idempotent - checks for existing transaction by reference
*/
async addPoints(
userId: string,
points: number,
paymentReference: string,
metadata?: Record<string, any>
): Promise<Transaction> {
// Check if transaction already exists (idempotency)
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;
// Create transaction
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);
// Update wallet balance
wallet.balance = newBalance;
await walletRepository.save(wallet);
return transaction;
}
/**
* Get transaction by payment reference (for idempotency checks)
*/
async getTransactionByReference(reference: string): Promise<Transaction | null> {
return await transactionRepository.findOne({
where: { paymentReference: reference },
});
}
/**
* Charge points for agent task
* Returns true if charge successful, false if insufficient balance or free task
*/
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) {
// Free agent, create free transaction
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 };
}
// Check free tasks used for this agent
// For simplicity, we track per agent in metadata or use a separate table
// For now, using first 2 tasks free per user (across all agents)
const freeTasksCount = await transactionRepository.count({
where: {
userId,
type: TransactionType.FREE,
},
});
if (freeTasksCount < this.FREE_TASKS_PER_AGENT) {
// Free task
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 };
}
// Check balance
const currentBalance = Number(wallet.balance);
if (currentBalance < pointsRequired) {
return { success: false, isFree: false };
}
// Charge points
const newBalance = currentBalance - pointsRequired;
const transaction = transactionRepository.create({
walletId: wallet.id,
userId,
agentId,
type: TransactionType.CHARGE,
status: TransactionStatus.COMPLETED,
amount: -pointsRequired, // Negative for charges
balanceAfter: newBalance,
description: `Charged ${pointsRequired} points for ${agent.name} task`,
metadata: { agentId, agentName: agent.name, pointsPerTask: pointsRequired },
});
await transactionRepository.save(transaction);
// Update wallet balance
wallet.balance = newBalance;
await walletRepository.save(wallet);
return { success: true, transaction, isFree: false };
}
/**
* Convert dollars to points
*/
dollarsToPoints(dollars: number): number {
return Math.round(dollars * this.POINTS_PER_DOLLAR);
}
/**
* Convert points to dollars
*/
pointsToDollars(points: number): number {
return points / this.POINTS_PER_DOLLAR;
}
/**
* Get user's transaction history
*/
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,
});
}
/**
* Get free tasks remaining for user
*/
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);
}
}
|