diff --git a/src/controllers/beneficiary.controller.js b/src/controllers/beneficiary.controller.js index abaea38..1b55954 100644 --- a/src/controllers/beneficiary.controller.js +++ b/src/controllers/beneficiary.controller.js @@ -50,11 +50,19 @@ async function validateOutsideBank(req, res) { async function addBeneficiary(req, res) { try { - const { accountNo, ifscCode, accountType, name } = req.body; + const { accountNo, ifscCode, accountType, name, transactionLimit } = + req.body; const customerNo = req.user; const query = - 'INSERT INTO beneficiaries (customer_no, account_no, account_type, ifsc_code, name) VALUES ($1, $2, $3, $4, $5)'; - await db.query(query, [customerNo, accountNo, accountType, ifscCode, name]); + '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, + transactionLimit, + ]); res.json({ message: 'SUCCESS' }); } catch (error) { 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) { return new Promise((resolve) => setTimeout(resolve, ms)); } @@ -171,4 +218,5 @@ module.exports = { getIfscDetails, getBeneficiary, deleteBeneficiary, + updateBeneficiaryLimit, }; diff --git a/src/middlewares/beneficiaryLimit.middleware.js b/src/middlewares/beneficiaryLimit.middleware.js new file mode 100644 index 0000000..73886b4 --- /dev/null +++ b/src/middlewares/beneficiaryLimit.middleware.js @@ -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 }; diff --git a/src/routes/beneficiary.route.js b/src/routes/beneficiary.route.js index 47e7eba..06ab407 100644 --- a/src/routes/beneficiary.route.js +++ b/src/routes/beneficiary.route.js @@ -13,5 +13,6 @@ router.delete( '/:beneficiaryAccountNo', beneficiaryController.deleteBeneficiary ); +router.patch('/update-limit', beneficiaryController.updateBeneficiaryLimit); module.exports = router; diff --git a/src/routes/imps.route.js b/src/routes/imps.route.js index dc4ecaf..c86c72f 100644 --- a/src/routes/imps.route.js +++ b/src/routes/imps.route.js @@ -4,6 +4,9 @@ const { logger } = require('../util/logger'); const impsValidator = require('../validators/imps.validator'); const paymentSecretValidator = require('../validators/payment.secret.validator'); const { checkLimit } = require('../middlewares/limitCheck.middleware'); +const { + checkBeneficiaryLimit, +} = require('../middlewares/beneficiaryLimit.middleware'); const { checkBeneficiaryCooldown, } = require('../middlewares/cooldown.middleware'); @@ -13,7 +16,8 @@ router.use( impsValidator, paymentSecretValidator, checkLimit, - checkBeneficiaryCooldown + checkBeneficiaryCooldown, + checkBeneficiaryLimit ); const impsRoute = async (req, res) => { diff --git a/src/routes/neft.route.js b/src/routes/neft.route.js index d62d6b8..6e7685b 100644 --- a/src/routes/neft.route.js +++ b/src/routes/neft.route.js @@ -4,6 +4,9 @@ const { logger } = require('../util/logger'); const neftValidator = require('../validators/neft.validator.js'); const paymentSecretValidator = require('../validators/payment.secret.validator'); const { checkLimit } = require('../middlewares/limitCheck.middleware'); +const { + checkBeneficiaryLimit, +} = require('../middlewares/beneficiaryLimit.middleware'); const { checkBeneficiaryCooldown, } = require('../middlewares/cooldown.middleware'); @@ -13,7 +16,8 @@ router.use( neftValidator, paymentSecretValidator, checkLimit, - checkBeneficiaryCooldown + checkBeneficiaryCooldown, + checkBeneficiaryLimit ); const neftRoute = async (req, res) => { diff --git a/src/routes/rtgs.route.js b/src/routes/rtgs.route.js index 9a8c189..8ee8e55 100644 --- a/src/routes/rtgs.route.js +++ b/src/routes/rtgs.route.js @@ -4,6 +4,9 @@ const { logger } = require('../util/logger'); const rtgsValidator = require('../validators/rtgs.validator.js'); const paymentSecretValidator = require('../validators/payment.secret.validator'); const { checkLimit } = require('../middlewares/limitCheck.middleware'); +const { + checkBeneficiaryLimit, +} = require('../middlewares/beneficiaryLimit.middleware'); const { checkBeneficiaryCooldown, } = require('../middlewares/cooldown.middleware'); @@ -13,7 +16,8 @@ router.use( rtgsValidator, paymentSecretValidator, checkLimit, - checkBeneficiaryCooldown + checkBeneficiaryCooldown, + checkBeneficiaryLimit ); const rtgsRoute = async (req, res) => { diff --git a/src/services/beneficiary.service.js b/src/services/beneficiary.service.js index 7068daf..453b90f 100644 --- a/src/services/beneficiary.service.js +++ b/src/services/beneficiary.service.js @@ -36,7 +36,7 @@ async function validateOutsideBank(accountNo, ifscCode, name) { async function getSingleBeneficiary(customerNo, accountNo) { 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]); return result.rows[0]; } @@ -53,7 +53,7 @@ async function deleteBeneficiary(customerNo, beneficiaryAccountNo) { async function getAllBeneficiaries(customerNo) { 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 list = result.rows.map((row) => { const details = { @@ -61,6 +61,7 @@ async function getAllBeneficiaries(customerNo) { name: row['name'], accountType: row['account_type'], createdAt: row['created_at'], + transactionLimit: row['transaction_limit'], }; if (row['ifsc_code'] === '_') { details['bankName'] = 'THE KANGRA CENTRAL COOPERATIVE BANK LIMITED'; @@ -74,10 +75,29 @@ async function getAllBeneficiaries(customerNo) { 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 = { validateWithinBank, validateOutsideBank, getAllBeneficiaries, getSingleBeneficiary, deleteBeneficiary, + updateBeneficiaryLimit, }; diff --git a/src/services/paymentLimit.service.js b/src/services/paymentLimit.service.js index b4acba7..464c984 100644 --- a/src/services/paymentLimit.service.js +++ b/src/services/paymentLimit.service.js @@ -20,6 +20,14 @@ async function getUsedLimit(customerNo, clientType) { 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) { let query = ''; if (clientType === 'IB') { @@ -35,4 +43,9 @@ async function setDailyLimit(customerNo, clientType, amount) { logger.info(`set new limit: ${result.rowCount} rows affected`); } -module.exports = { getDailyLimit, getUsedLimit, setDailyLimit }; +module.exports = { + getDailyLimit, + getUsedLimit, + setDailyLimit, + getBeneficiaryUsedLimit, +};