feat: added beneficiary wise transaction limit
This commit is contained in:
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
33
src/middlewares/beneficiaryLimit.middleware.js
Normal file
33
src/middlewares/beneficiaryLimit.middleware.js
Normal 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 };
|
||||||
@@ -13,5 +13,6 @@ router.delete(
|
|||||||
'/:beneficiaryAccountNo',
|
'/:beneficiaryAccountNo',
|
||||||
beneficiaryController.deleteBeneficiary
|
beneficiaryController.deleteBeneficiary
|
||||||
);
|
);
|
||||||
|
router.patch('/update-limit', beneficiaryController.updateBeneficiaryLimit);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user