saksh-wallet
Version:
A Node.js library for managing user wallets, including functionalities for crediting, debiting, and converting currencies, as well as retrieving balances and transaction reports.
193 lines (153 loc) • 7.06 kB
JavaScript
const EventEmitter = require('events');
const mongoose = require('mongoose');
const Transaction = require('./models/Transaction');
const WalletUser = require('./models/WalletUser');
const RecurringPayment = require('./models/RecurringPayment');
class SakshTransactionManagement {
constructor(userManagement, transactionModel = Transaction) {
this.userManagement = userManagement;
this.Transaction = transactionModel;
}
async sakshUpdateDailyLimit(user, amount) {
const today = new Date().setHours(0, 0, 0, 0);
if (new Date(user.lastTransactionDate).setHours(0, 0, 0, 0) !== today) {
user.dailyTotal = 0;
user.lastTransactionDate = Date.now();
}
if (user.dailyTotal + amount > user.dailyLimit) {
throw new Error('Daily transaction limit exceeded');
}
user.dailyTotal += amount;
}
async sakshUpdateBalance(userId, amount, currency, type, description, referenceNumber, category) {
if (amount <= 0) {
throw new Error('Amount must be greater than zero');
}
const session = await mongoose.startSession();
session.startTransaction();
try {
let user = await this.userManagement.User.findOne({ userId }).session(session);
if (!user) {
user = new this.userManagement.User({ userId });
}
if (!this.nolimit && user.applyDailyLimit) {
await this.sakshUpdateDailyLimit(user, amount);
}
const currentBalance = await this.userManagement.sakshGetBalance(userId, currency);
if (type === 'debit' && currentBalance < amount) {
throw new Error('Insufficient funds');
}
let newBalance;
if (type === 'credit') {
newBalance = currentBalance + amount;
} else if (type === 'debit') {
newBalance = currentBalance - amount;
} else {
throw new Error('Invalid transaction type');
}
user.balances.set(currency, newBalance);
await user.save({ session });
const transaction = new this.Transaction({
userId,
type,
amount,
currency,
description,
referenceNumber,
category
});
await transaction.save({ session });
await session.commitTransaction();
session.endSession();
return newBalance;
} catch (error) {
await session.abortTransaction();
session.endSession();
throw error;
}
}
async sakshCredit(userId, amount, currency, description, referenceNumber, category) {
return this.sakshUpdateBalance(userId, amount, currency, 'credit', description, referenceNumber, category);
}
async sakshDebit(userId, amount, currency, description, referenceNumber, category) {
await this.sakshCheckBudget(userId, category, amount);
return this.sakshUpdateBalance(userId, amount, currency, 'debit', description, referenceNumber, category);
}
async sakshTransferFunds(fromUserId, toUserId, amount, currency, description, referenceNumber, category, customFeeCallback = null) {
const session = await mongoose.startSession();
session.startTransaction();
try {
let feeAmount = 0;
if (customFeeCallback) {
feeAmount = customFeeCallback(amount);
}
const totalDebitAmount = amount + feeAmount;
await this.sakshDebit(fromUserId, totalDebitAmount, currency, `${description}${feeAmount > 0 ? ' (including fee)' : ''}`, referenceNumber, category);
await this.sakshCredit(toUserId, amount, currency, description, referenceNumber, category);
if (feeAmount > 0) {
await this.sakshCredit(this.adminUserId, feeAmount, currency, `Transfer fee from ${fromUserId} to ${toUserId}`, referenceNumber, category);
}
await session.commitTransaction();
session.endSession();
return { fromUserId, toUserId, amount, currency, feeAmount, adminUserId: this.adminUserId };
} catch (error) {
await session.abortTransaction();
session.endSession();
throw error;
}
}
async sakshGetTransactionReport(userId) {
return await this.Transaction.find({ userId });
}
async sakshReverseTransaction(transactionId, session) {
const originalTransaction = await this.Transaction.findById(transactionId).session(session);
if (!originalTransaction) {
throw new Error('Transaction not found');
}
const { userId, type, amount, currency, description, referenceNumber } = originalTransaction;
let user = await this.userManagement.User.findOne({ userId }).session(session);
if (!user) {
user = new this.userManagement.User({ userId });
}
if (type === 'credit') {
if (!user.balances.has(currency) || user.balances.get(currency) < amount) {
throw new Error('Insufficient funds to reverse the transaction');
}
user.balances.set(currency, user.balances.get(currency) - amount);
} else if (type === 'debit') {
user.balances.set(currency, (user.balances.get(currency) || 0) + amount);
} else {
throw new Error('Invalid transaction type');
}
await user.save({ session });
const reversedTransaction = new this.Transaction({
userId,
type: type === 'credit' ? 'debit' : 'credit',
amount,
currency,
description: `Reversal of transaction ${transactionId}`,
referenceNumber: `REV-${referenceNumber}`,
});
await reversedTransaction.save({ session });
return reversedTransaction;
}
async sakshReverseMultipleTransactions(transactionIds) {
const session = await mongoose.startSession();
session.startTransaction();
try {
const reversedTransactions = [];
for (const transactionId of transactionIds) {
const reversedTransaction = await this.sakshReverseTransaction(transactionId, session);
reversedTransactions.push(reversedTransaction);
}
await session.commitTransaction();
session.endSession();
return reversedTransactions;
} catch (error) {
await session.abortTransaction();
session.endSession();
throw error;
}
}
}
module.exports = SakshTransactionManagement;