feat: added beneficiary wise transaction limit

This commit is contained in:
2026-02-25 13:23:13 +05:30
parent fc32e9c785
commit 68d0bf0151
8 changed files with 136 additions and 9 deletions

View File

@@ -50,11 +50,19 @@ async function validateOutsideBank(req, res) {
async function addBeneficiary(req, res) { async function addBeneficiary(req, res) {
try { try {
const { accountNo, ifscCode, accountType, name } = req.body; const { accountNo, ifscCode, accountType, name, transactionLimit } =
req.body;
const customerNo = req.user; const customerNo = req.user;
const query = const query =
'INSERT INTO beneficiaries (customer_no, account_no, account_type, ifsc_code, name) VALUES ($1, $2, $3, $4, $5)'; 'INSERT INTO beneficiaries (customer_no, account_no, account_type, ifsc_code, name, transaction_limit) VALUES ($1, $2, $3, $4, $5, $6)';
await db.query(query, [customerNo, accountNo, accountType, ifscCode, name]); await db.query(query, [
customerNo,
accountNo,
accountType,
ifscCode,
name,
transactionLimit,
]);
res.json({ message: 'SUCCESS' }); res.json({ message: 'SUCCESS' });
} catch (error) { } catch (error) {
logger.error(error, 'Error adding beneficiary'); logger.error(error, 'Error adding beneficiary');
@@ -132,6 +140,45 @@ async function getIfscDetails(req, res) {
} }
} }
async function updateBeneficiaryLimit(req, res) {
const { beneficiaryAccountNo, newLimit } = req.body;
if (newLimit === undefined) {
return res.status(400).json({ error: 'newLimit is required' });
}
if (typeof newLimit !== 'number') {
return res.status(400).json({ error: 'Limit must be a numeric value' });
}
if (newLimit < 0 || newLimit > 1000000) {
return res
.status(400)
.json({ error: 'Limit must be between 10 and 1000000' });
}
try {
const updatedBeneficiary = await beneficiaryService.updateBeneficiaryLimit(
req.user,
beneficiaryAccountNo,
newLimit
);
return res.status(200).json({
message: `limit for beneficiary ${updatedBeneficiary.account_no} updated successfully to ${updatedBeneficiary.transaction_limit}`,
});
} catch (error) {
if (error.message === 'ACCOUNT_NOT_FOUND') {
logger.warn(
`beneficiary ${beneficiaryAccountNo} does not exist for the customer ${req.user}`
);
return res.status(400).json({ error: 'INVALID_BENEFICIARY_ACCOUNT_NO' });
} else {
logger.error(error, 'error deleting beneficiary');
return res.status(500).json({ error: 'INTERNAL_SERVER_ERROR' });
}
}
}
function delay(ms) { function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
@@ -171,4 +218,5 @@ module.exports = {
getIfscDetails, getIfscDetails,
getBeneficiary, getBeneficiary,
deleteBeneficiary, deleteBeneficiary,
updateBeneficiaryLimit,
}; };

View File

@@ -0,0 +1,33 @@
const { getSingleBeneficiary } = require('../services/beneficiary.service');
const { getBeneficiaryUsedLimit } = require('../services/paymentLimit.service');
const { logger } = require('../util/logger');
async function checkBeneficiaryLimit(req, res, next) {
const { amount, toAccount } = req.body;
const { user } = req;
const beneficiary = await getSingleBeneficiary(user, toAccount);
if (!beneficiary) {
return res
.status(403)
.json({ error: 'No beneficiary added for this account' });
}
const beneficiaryLimit = beneficiary.transaction_limit;
if (!beneficiary.transactionLimit) {
logger.info('NO LIMIT SET FOR BENEFICIARY. ALLOWING TRANSACTIONS');
next();
}
const usedLimit = await getBeneficiaryUsedLimit(user, toAccount);
const remainingLimit = beneficiaryLimit - usedLimit;
if (amount > remainingLimit) {
const midnight = new Date();
midnight.setHours(24, 0, 0, 0);
res.set('Retry-After', midnight.toUTCString());
return res.status(403).json({ error: 'Beneficairy limit exhausted' });
}
next();
}
module.exports = { checkBeneficiaryLimit };

View File

@@ -13,5 +13,6 @@ router.delete(
'/:beneficiaryAccountNo', '/:beneficiaryAccountNo',
beneficiaryController.deleteBeneficiary beneficiaryController.deleteBeneficiary
); );
router.patch('/update-limit', beneficiaryController.updateBeneficiaryLimit);
module.exports = router; module.exports = router;

View File

@@ -4,6 +4,9 @@ const { logger } = require('../util/logger');
const impsValidator = require('../validators/imps.validator'); const impsValidator = require('../validators/imps.validator');
const paymentSecretValidator = require('../validators/payment.secret.validator'); const paymentSecretValidator = require('../validators/payment.secret.validator');
const { checkLimit } = require('../middlewares/limitCheck.middleware'); const { checkLimit } = require('../middlewares/limitCheck.middleware');
const {
checkBeneficiaryLimit,
} = require('../middlewares/beneficiaryLimit.middleware');
const { const {
checkBeneficiaryCooldown, checkBeneficiaryCooldown,
} = require('../middlewares/cooldown.middleware'); } = require('../middlewares/cooldown.middleware');
@@ -13,7 +16,8 @@ router.use(
impsValidator, impsValidator,
paymentSecretValidator, paymentSecretValidator,
checkLimit, checkLimit,
checkBeneficiaryCooldown checkBeneficiaryCooldown,
checkBeneficiaryLimit
); );
const impsRoute = async (req, res) => { const impsRoute = async (req, res) => {

View File

@@ -4,6 +4,9 @@ const { logger } = require('../util/logger');
const neftValidator = require('../validators/neft.validator.js'); const neftValidator = require('../validators/neft.validator.js');
const paymentSecretValidator = require('../validators/payment.secret.validator'); const paymentSecretValidator = require('../validators/payment.secret.validator');
const { checkLimit } = require('../middlewares/limitCheck.middleware'); const { checkLimit } = require('../middlewares/limitCheck.middleware');
const {
checkBeneficiaryLimit,
} = require('../middlewares/beneficiaryLimit.middleware');
const { const {
checkBeneficiaryCooldown, checkBeneficiaryCooldown,
} = require('../middlewares/cooldown.middleware'); } = require('../middlewares/cooldown.middleware');
@@ -13,7 +16,8 @@ router.use(
neftValidator, neftValidator,
paymentSecretValidator, paymentSecretValidator,
checkLimit, checkLimit,
checkBeneficiaryCooldown checkBeneficiaryCooldown,
checkBeneficiaryLimit
); );
const neftRoute = async (req, res) => { const neftRoute = async (req, res) => {

View File

@@ -4,6 +4,9 @@ const { logger } = require('../util/logger');
const rtgsValidator = require('../validators/rtgs.validator.js'); const rtgsValidator = require('../validators/rtgs.validator.js');
const paymentSecretValidator = require('../validators/payment.secret.validator'); const paymentSecretValidator = require('../validators/payment.secret.validator');
const { checkLimit } = require('../middlewares/limitCheck.middleware'); const { checkLimit } = require('../middlewares/limitCheck.middleware');
const {
checkBeneficiaryLimit,
} = require('../middlewares/beneficiaryLimit.middleware');
const { const {
checkBeneficiaryCooldown, checkBeneficiaryCooldown,
} = require('../middlewares/cooldown.middleware'); } = require('../middlewares/cooldown.middleware');
@@ -13,7 +16,8 @@ router.use(
rtgsValidator, rtgsValidator,
paymentSecretValidator, paymentSecretValidator,
checkLimit, checkLimit,
checkBeneficiaryCooldown checkBeneficiaryCooldown,
checkBeneficiaryLimit
); );
const rtgsRoute = async (req, res) => { const rtgsRoute = async (req, res) => {

View File

@@ -36,7 +36,7 @@ async function validateOutsideBank(accountNo, ifscCode, name) {
async function getSingleBeneficiary(customerNo, accountNo) { async function getSingleBeneficiary(customerNo, accountNo) {
const queryStr = const queryStr =
'SELECT b.account_no, b.name, b.account_type, b.ifsc_code, b.created_at, i.bank_name, i.branch_name FROM beneficiaries b JOIN ifsc_details i ON b.ifsc_code = i.ifsc_code WHERE customer_no = $1 AND account_no = $2'; 'SELECT b.account_no, b.name, b.account_type, b.ifsc_code, b.created_at, b.transaction_limit, i.bank_name, i.branch_name FROM beneficiaries b JOIN ifsc_details i ON b.ifsc_code = i.ifsc_code WHERE customer_no = $1 AND account_no = $2';
const result = await db.query(queryStr, [customerNo, accountNo]); const result = await db.query(queryStr, [customerNo, accountNo]);
return result.rows[0]; return result.rows[0];
} }
@@ -53,7 +53,7 @@ async function deleteBeneficiary(customerNo, beneficiaryAccountNo) {
async function getAllBeneficiaries(customerNo) { async function getAllBeneficiaries(customerNo) {
const queryStr = const queryStr =
'SELECT b.account_no, b.name, b.account_type, b.ifsc_code, b.created_at, i.bank_name, i.branch_name FROM beneficiaries b JOIN LATERAL( SELECT * FROM ifsc_details i WHERE i.ifsc_code = b.ifsc_code LIMIT 1 ) i ON true WHERE customer_no = $1'; 'SELECT b.account_no, b.name, b.account_type, b.ifsc_code, b.created_at, b.transaction_limit, i.bank_name, i.branch_name FROM beneficiaries b JOIN LATERAL( SELECT * FROM ifsc_details i WHERE i.ifsc_code = b.ifsc_code LIMIT 1 ) i ON true WHERE customer_no = $1';
const result = await db.query(queryStr, [customerNo]); const result = await db.query(queryStr, [customerNo]);
const list = result.rows.map((row) => { const list = result.rows.map((row) => {
const details = { const details = {
@@ -61,6 +61,7 @@ async function getAllBeneficiaries(customerNo) {
name: row['name'], name: row['name'],
accountType: row['account_type'], accountType: row['account_type'],
createdAt: row['created_at'], createdAt: row['created_at'],
transactionLimit: row['transaction_limit'],
}; };
if (row['ifsc_code'] === '_') { if (row['ifsc_code'] === '_') {
details['bankName'] = 'THE KANGRA CENTRAL COOPERATIVE BANK LIMITED'; details['bankName'] = 'THE KANGRA CENTRAL COOPERATIVE BANK LIMITED';
@@ -74,10 +75,29 @@ async function getAllBeneficiaries(customerNo) {
return list; return list;
} }
async function updateBeneficiaryLimit(
customerNo,
beneficiaryAccountNo,
newLimit
) {
const queryStr =
'UPDATE beneficiaries set transaction_limit = $1 WHERE customer_no = $2 AND account_no = $3 RETURNING *';
const result = await db.query(queryStr, [
newLimit,
customerNo,
beneficiaryAccountNo,
]);
if (result.rowCount == 0) {
throw new Error('ACCOUNT_NOT_FOUND');
}
return result.rows[0];
}
module.exports = { module.exports = {
validateWithinBank, validateWithinBank,
validateOutsideBank, validateOutsideBank,
getAllBeneficiaries, getAllBeneficiaries,
getSingleBeneficiary, getSingleBeneficiary,
deleteBeneficiary, deleteBeneficiary,
updateBeneficiaryLimit,
}; };

View File

@@ -20,6 +20,14 @@ async function getUsedLimit(customerNo, clientType) {
return Number(usedLimit); return Number(usedLimit);
} }
async function getBeneficiaryUsedLimit(customerNo, beneficiaryAccountNo) {
const query =
"SELECT SUM(amount::numeric) AS used_limit FROM transactions WHERE created_at BETWEEN CURRENT_DATE AND (CURRENT_DATE + INTERVAL '1 day') AND customer_no = $1 AND to_account = $2";
const result = await db.query(query, [customerNo, beneficiaryAccountNo]);
const usedLimit = result.rows[0].used_limit;
return Number(usedLimit);
}
async function setDailyLimit(customerNo, clientType, amount) { async function setDailyLimit(customerNo, clientType, amount) {
let query = ''; let query = '';
if (clientType === 'IB') { if (clientType === 'IB') {
@@ -35,4 +43,9 @@ async function setDailyLimit(customerNo, clientType, amount) {
logger.info(`set new limit: ${result.rowCount} rows affected`); logger.info(`set new limit: ${result.rowCount} rows affected`);
} }
module.exports = { getDailyLimit, getUsedLimit, setDailyLimit }; module.exports = {
getDailyLimit,
getUsedLimit,
setDailyLimit,
getBeneficiaryUsedLimit,
};