From 123d5c9f1d55538b7950eae45cbad1cbb0085430 Mon Sep 17 00:00:00 2001 From: asif Date: Fri, 8 Aug 2025 20:40:10 +0530 Subject: [PATCH 01/21] added two methods to get registered beneficiaries for a customer --- src/services/beneficiary.service.js | 32 ++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/services/beneficiary.service.js b/src/services/beneficiary.service.js index bba42cd..736fe2b 100644 --- a/src/services/beneficiary.service.js +++ b/src/services/beneficiary.service.js @@ -1,6 +1,7 @@ const axios = require('axios'); const { logger } = require('../util/logger'); const { v4: uuidv4 } = require('uuid'); +const db = require('../config/db'); async function validateWithinBank(accountNo) { const url = 'http://localhost:8687/kccb/cbs/acctInfo/details'; @@ -36,4 +37,33 @@ async function validateOutsideBank(accountNo, ifscCode, name) { } } -module.exports = { validateWithinBank, validateOutsideBank }; +async function getSingleBeneficiary(customerNo, accountNo) { + const queryStr = + 'SELECT b.account_no, b.name, b.account_type, b.ifsc_code, 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]; +} + +async function getAllBeneficiaries(customerNo) { + const queryStr = + 'SELECT b.account_no, b.name, b.account_type, b.ifsc_code, i.bank_name, i.branch_name FROM beneficiaries b JOIN ifsc_details i ON b.ifsc_code = i.ifsc_code WHERE customer_no = $1'; + const result = await db.query(queryStr, [customerNo]); + const list = result.rows.map((row) => { + return { + accountNo: row['account_no'], + name: row['name'], + accountType: row['account_type'], + ifscCdoe: row['ifsc_code'], + bankName: row['bank_name'], + branchName: row['branch_name'], + } + }); + + return list; +} +module.exports = { + validateWithinBank, + validateOutsideBank, + getAllBeneficiaries, + getSingleBeneficiary, +}; From c331ba550563fef1f2a97886ac56ad2ab9c36c38 Mon Sep 17 00:00:00 2001 From: asif Date: Fri, 8 Aug 2025 20:42:39 +0530 Subject: [PATCH 02/21] changed ifsc_code_bank table name to ifsc_details --- src/controllers/beneficiary.controller.js | 2 +- src/validators/beneficiary.validator.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/beneficiary.controller.js b/src/controllers/beneficiary.controller.js index 07fb52d..f8f67bc 100644 --- a/src/controllers/beneficiary.controller.js +++ b/src/controllers/beneficiary.controller.js @@ -87,7 +87,7 @@ async function getIfscDetails(req, res) { } console.log(ifscCode); try { - const query = 'SELECT * FROM ifsc_code_bank WHERE ifsc_code = $1'; + const query = 'SELECT * FROM ifsc_details WHERE ifsc_code = $1'; const result = await db.query(query, [ifscCode]); console.log(result.rows); if (!result.rows) { diff --git a/src/validators/beneficiary.validator.js b/src/validators/beneficiary.validator.js index 1a0963c..53a4f91 100644 --- a/src/validators/beneficiary.validator.js +++ b/src/validators/beneficiary.validator.js @@ -18,7 +18,7 @@ const newBeneficiaryValidator = async (req, res, next) => { return; } const query_str = - 'SELECT EXISTS(SELECT 1 FROM ifsc_code_bank WHERE ifsc_code = $1)'; + 'SELECT EXISTS(SELECT 1 FROM ifsc_details WHERE ifsc_code = $1)'; const result = await db.query(query_str, [ifscCode]); const exists = result.rows[0].exists; if (!exists) { From 29d09490396c33ee426efba30ecfbcd572314ff4 Mon Sep 17 00:00:00 2001 From: asif Date: Fri, 8 Aug 2025 20:43:42 +0530 Subject: [PATCH 03/21] modified handle NPCI success method to only put the beneficiary name in redis --- src/controllers/npci.controller.js | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/controllers/npci.controller.js b/src/controllers/npci.controller.js index c677c03..7de4341 100644 --- a/src/controllers/npci.controller.js +++ b/src/controllers/npci.controller.js @@ -1,5 +1,5 @@ const db = require('../config/db'); -const { getJson } = require('../config/redis'); +const { getJson, setJson } = require('../config/redis'); const { logger } = require('../util/logger'); async function npciResponse(req, res) { @@ -15,26 +15,9 @@ async function npciResponse(req, res) { async function handleNPCISuccess(response) { const { txnid, benename } = response; try { - const beneficiaryDetails = await getJson(txnid); - if (!beneficiaryDetails) { - logger.warn('no txnid in redis'); - return false; - } - const { customerNo, accountNo, ifscCode, accountType } = beneficiaryDetails; - const query = - 'INSERT INTO beneficiaries (customer_no, account_no, name, account_type, ifsc_code) VALUES ($1, $2, $3, $4, $5)'; - const result = await db.query(query, [ - customerNo, - accountNo, - benename, - accountType, - ifscCode, - ]); - logger.info(result); - return true; + await setJson(txnid, benename); } catch (error) { logger.error(error, 'error processing npci response'); - return false; } } From 90cbddd2483eff335effe6cdaa848004a9fea965 Mon Sep 17 00:00:00 2001 From: asif Date: Fri, 8 Aug 2025 20:45:13 +0530 Subject: [PATCH 04/21] added functions for validate outside bank, check beneficiary name and get beneficiary --- src/controllers/beneficiary.controller.js | 101 ++++++++++++++-------- 1 file changed, 64 insertions(+), 37 deletions(-) diff --git a/src/controllers/beneficiary.controller.js b/src/controllers/beneficiary.controller.js index f8f67bc..a7797e7 100644 --- a/src/controllers/beneficiary.controller.js +++ b/src/controllers/beneficiary.controller.js @@ -25,58 +25,83 @@ async function validateWithinBank(req, res) { } } +async function validateOutsideBank(req, res) { + const { accountNo, ifscCode, remitterName } = req.query; + if (!accountNo || !ifscCode || !remitterName) { + res.status(401).json({ error: 'BAD_REQUEST' }); + return; + } + + try { + const refNo = await beneficiaryService.validateOutsideBank( + accountNo, + ifscCode, + remitterName + ); + if (!refNo) + return res.status(401).json({ error: 'invalid account number' }); + return res.json({ refNo }); + } catch (err) { + logger.error(err, 'beneficiary validation within bank failed'); + res.status(500).json({ error: 'invalid account number' }); + } +} + async function addBeneficiary(req, res) { try { const { accountNo, ifscCode, accountType, name } = req.body; const customerNo = req.user; - console.log(`Customer Number: ${customerNo}`); - const uuid = await beneficiaryService.validateOutsideBank( - accountNo, - ifscCode, - name - ); - await setJson( - uuid, - { customerNo, accountNo, ifscCode, accountType, name }, - 300 - ); - - //------------------------------------------------------------------------- - //*REMOVE IN PRODUCTION* firing this from here since no NPCI in test region - const r = await axios.post( - 'http://localhost:8081/api/npci/beneficiary-response', - { - resp: { status: 'Success', txnid: uuid, benename: name }, - } - ); - console.log(r.data); - //------------------------------------------------------------------------- - - res.json({ message: 'SENT_FOR_VALIDATION' }); + 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]); } catch (error) { logger.error(error, 'Error adding beneficiary'); res.status(500).json({ error: 'INTERNAL_SERVER_ERROR' }); } } -async function checkBeneficiary(req, res) { - await delay(5000); +async function getBeneficiary(req, res) { const { accountNo } = req.query; - const customerNo = req.user; - if (!accountNo) { - res.status(403).json({ error: 'BAS_REQUEST' }); + let beneficiaryDetails; + try { + if (accountNo) { + beneficiaryDetails = await beneficiaryService.getSingleBeneficiary( + req.user, + accountNo + ); + } else { + beneficiaryDetails = await beneficiaryService.getAllBeneficiaries( + req.user + ); + } + if (!beneficiaryDetails) { + res.status(404).json({ error: 'NO_BENEFICIARY_FOUND' }); + return; + } + + res.json(beneficiaryDetails); + } catch (error) { + logger.error(error, 'error fetching beneficiaries'); + res.status(500).json({ error: 'INTERNAL_SERVER_ERROR' }); + } +} + +async function checkBeneficiaryName(req, res) { + await delay(2000); + const { refNo } = req.query; + if (!refNo) { + res.status(403).json({ error: 'BAD_REQUEST' }); return; } - console.log(customerNo, accountNo); - const query = - 'SELECT EXISTS(SELECT 1 FROM beneficiaries WHERE customer_no = $1 AND account_no = $2)'; - const result = await db.query(query, [customerNo, accountNo]); - const exists = result.rows[0].exists; - if (!exists) { + + //uncomment in production for actual name from beneficiary + // const beneficiaryName = await getJson(refNo); + const beneficiaryName = 'John Doe'; + if (!beneficiaryName) { res.status(404).json({ error: 'NOT_FOUND' }); return; } - res.json({ message: 'FOUND' }); + res.json({ message: beneficiaryName }); } async function getIfscDetails(req, res) { @@ -107,7 +132,9 @@ function delay(ms) { module.exports = { validateWithinBank, + validateOutsideBank, addBeneficiary, - checkBeneficiary, + checkBeneficiaryName, getIfscDetails, + getBeneficiary, }; From 2d39e1e66d3d847dd705f01830d053b51f43e813 Mon Sep 17 00:00:00 2001 From: asif Date: Fri, 8 Aug 2025 20:46:32 +0530 Subject: [PATCH 05/21] added routes for validating beneficiary outside bank, checking beneficiary name after successful validation and getting beneficiary list of the customer --- .env | 3 --- src/routes/beneficiary.route.js | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 5c88f1c..0000000 --- a/.env +++ /dev/null @@ -1,3 +0,0 @@ -PORT=8081 -DATABASE_URL=postgresql://kmobile_app_rw:kmobile@localhost:5432/kmobile_banking -JWT_SECRET=supersecret diff --git a/src/routes/beneficiary.route.js b/src/routes/beneficiary.route.js index dde2125..09e247a 100644 --- a/src/routes/beneficiary.route.js +++ b/src/routes/beneficiary.route.js @@ -5,8 +5,10 @@ const newBeneficiaryValidator = require('../validators/beneficiary.validator'); const router = express.Router(); router.get('/validate/within-bank', beneficiaryController.validateWithinBank); -router.get('/check', beneficiaryController.checkBeneficiary); +router.get('/validate/outside-bank', beneficiaryController.validateOutsideBank); +router.get('/check', beneficiaryController.checkBeneficiaryName); router.get('/ifsc-details', beneficiaryController.getIfscDetails); +router.get('/get', beneficiaryController.getBeneficiary); router.post( '/add', newBeneficiaryValidator, From 9316a41646e8b8c8f52e220ee29457219de44e30 Mon Sep 17 00:00:00 2001 From: asif Date: Sat, 9 Aug 2025 13:49:25 +0530 Subject: [PATCH 06/21] made the function validateOutsideBank return beneficiary name rather than refNo Also removed the now redundant checkBeneficiaryName controller and paths --- src/controllers/beneficiary.controller.js | 25 +++-------------------- src/routes/beneficiary.route.js | 1 - 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/src/controllers/beneficiary.controller.js b/src/controllers/beneficiary.controller.js index a7797e7..3c3e546 100644 --- a/src/controllers/beneficiary.controller.js +++ b/src/controllers/beneficiary.controller.js @@ -1,8 +1,6 @@ const { logger } = require('../util/logger'); -const { setJson, getJson } = require('../config/redis'); const beneficiaryService = require('../services/beneficiary.service'); const db = require('../config/db'); -const axios = require('axios'); async function validateWithinBank(req, res) { const { accountNumber } = req.query; @@ -40,7 +38,9 @@ async function validateOutsideBank(req, res) { ); if (!refNo) return res.status(401).json({ error: 'invalid account number' }); - return res.json({ refNo }); + //**IN PRODUCTION** poll the redis server continuously giving the refNo since the response from NPCI will be stored there + await delay(3000); + return res.json({ name: 'John Doe' }); } catch (err) { logger.error(err, 'beneficiary validation within bank failed'); res.status(500).json({ error: 'invalid account number' }); @@ -86,24 +86,6 @@ async function getBeneficiary(req, res) { } } -async function checkBeneficiaryName(req, res) { - await delay(2000); - const { refNo } = req.query; - if (!refNo) { - res.status(403).json({ error: 'BAD_REQUEST' }); - return; - } - - //uncomment in production for actual name from beneficiary - // const beneficiaryName = await getJson(refNo); - const beneficiaryName = 'John Doe'; - if (!beneficiaryName) { - res.status(404).json({ error: 'NOT_FOUND' }); - return; - } - res.json({ message: beneficiaryName }); -} - async function getIfscDetails(req, res) { const { ifscCode } = req.query; if (!ifscCode) { @@ -134,7 +116,6 @@ module.exports = { validateWithinBank, validateOutsideBank, addBeneficiary, - checkBeneficiaryName, getIfscDetails, getBeneficiary, }; diff --git a/src/routes/beneficiary.route.js b/src/routes/beneficiary.route.js index 09e247a..14845ba 100644 --- a/src/routes/beneficiary.route.js +++ b/src/routes/beneficiary.route.js @@ -6,7 +6,6 @@ const router = express.Router(); router.get('/validate/within-bank', beneficiaryController.validateWithinBank); router.get('/validate/outside-bank', beneficiaryController.validateOutsideBank); -router.get('/check', beneficiaryController.checkBeneficiaryName); router.get('/ifsc-details', beneficiaryController.getIfscDetails); router.get('/get', beneficiaryController.getBeneficiary); router.post( From 20af204304f0a5baadb5439fd1de480f7579975c Mon Sep 17 00:00:00 2001 From: asif Date: Sat, 9 Aug 2025 15:50:16 +0530 Subject: [PATCH 07/21] changed the beneficiary routes to follow standards /beneficiary send GET AND POST request for retrieving or adding beneficiaries --- src/routes/beneficiary.route.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/routes/beneficiary.route.js b/src/routes/beneficiary.route.js index 14845ba..d1483d8 100644 --- a/src/routes/beneficiary.route.js +++ b/src/routes/beneficiary.route.js @@ -7,11 +7,7 @@ const router = express.Router(); router.get('/validate/within-bank', beneficiaryController.validateWithinBank); router.get('/validate/outside-bank', beneficiaryController.validateOutsideBank); router.get('/ifsc-details', beneficiaryController.getIfscDetails); -router.get('/get', beneficiaryController.getBeneficiary); -router.post( - '/add', - newBeneficiaryValidator, - beneficiaryController.addBeneficiary -); +router.get('/', beneficiaryController.getBeneficiary); +router.post('/', newBeneficiaryValidator, beneficiaryController.addBeneficiary); module.exports = router; From 6e8eccd767fe620385d32204496c344285c27285 Mon Sep 17 00:00:00 2001 From: asif Date: Sat, 9 Aug 2025 15:51:28 +0530 Subject: [PATCH 08/21] returning a valid response after creating a beneficiary also throw 409 status code if account already exists in beneficiaries table against that customer --- src/controllers/beneficiary.controller.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/controllers/beneficiary.controller.js b/src/controllers/beneficiary.controller.js index 3c3e546..fdad3e8 100644 --- a/src/controllers/beneficiary.controller.js +++ b/src/controllers/beneficiary.controller.js @@ -54,8 +54,15 @@ async function addBeneficiary(req, res) { 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]); + res.json({ message: 'SUCCESS' }); } catch (error) { logger.error(error, 'Error adding beneficiary'); + if ( + error.message == + 'duplicate key value violates unique constraint "beneficiaries_pkey"' + ) { + res.status(409).json({ error: 'BENEFICIARY_ALREADY_EXITS' }); + } res.status(500).json({ error: 'INTERNAL_SERVER_ERROR' }); } } From 7e0c9d251eeab975f1fa154b648a916ab3327086 Mon Sep 17 00:00:00 2001 From: asif Date: Sat, 9 Aug 2025 20:51:07 +0530 Subject: [PATCH 09/21] fixed an error on redis js of undefined err --- src/config/redis.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/redis.js b/src/config/redis.js index 2f468ad..61b2fa7 100644 --- a/src/config/redis.js +++ b/src/config/redis.js @@ -14,7 +14,7 @@ const client = createClient({ }); client.on('connect', () => logger.info('Connected to redis')); -client.on('error', () => logger.error(err, 'Redis error')); +client.on('error', (err) => logger.error(err, 'Redis error')); client.on('ready', () => logger.info('Redis client ready')); client.on('end', () => logger.info('Redis connection closed')); From 6b6b5679bf02c106aa18ec75c4c686845089c944 Mon Sep 17 00:00:00 2001 From: asif Date: Sat, 9 Aug 2025 21:41:04 +0530 Subject: [PATCH 10/21] reformatted transfer.validator.js --- src/validators/transfer.validator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validators/transfer.validator.js b/src/validators/transfer.validator.js index 11155cf..431d463 100644 --- a/src/validators/transfer.validator.js +++ b/src/validators/transfer.validator.js @@ -1,7 +1,7 @@ const transferValidator = async (req, res, next) => { const { fromAccount, toAccount, toAccountType, amount } = req.body; - const accountTypes = ['SB', 'LN','Savings','Current']; + const accountTypes = ['SB', 'LN', 'Savings', 'Current']; if (!fromAccount || fromAccount.length != 11) { return res.status(400).json({ error: 'INVALID_ACCOUNT_NUMBER_FORMAT' }); } From 1bd618b4fd9d91d303d1c7639152194957a48b29 Mon Sep 17 00:00:00 2001 From: asif Date: Sat, 9 Aug 2025 21:41:29 +0530 Subject: [PATCH 11/21] implemented neft.validator.js --- src/validators/neft.validator.js | 36 ++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/validators/neft.validator.js diff --git a/src/validators/neft.validator.js b/src/validators/neft.validator.js new file mode 100644 index 0000000..6f6dc83 --- /dev/null +++ b/src/validators/neft.validator.js @@ -0,0 +1,36 @@ +const neftValidator = async (req, res, next) => { + const { + fromAccount, + toAccount, + amount, + remitterName, + beneficiaryName, + ifscCode, + } = req.body; + + if (!isAccountNumbersValid(fromAccount, toAccount)) { + return res.status(400).json({ error: 'INVALID_ACCOUNT_NUMBER_FORMAT' }); + } + + if (amount < 1) { + return res.status(400).json({ error: 'INVALID_AMOUNT' }); + } + + if (!remitterName || !beneficiaryName) { + return res + .status(400) + .json({ error: 'REMITTER_NAME AND BENEFICIARY_NAME REQUIRED' }); + } + + if (!ifscCode || !/^[A-Z]{4}0[0-9]{6}$/.test(ifscCode)) { + return res.status(400).json({ error: 'INVALID_IFSC_CODE' }); + } + + next(); +}; + +const isAccountNumbersValid = (fromAcct, toAcct) => { + return !(!fromAcct || !toAcct || fromAcct.length != 11 || toAcct.length < 7); +}; + +module.exports = neftValidator; From dbdc1c78296da130f609f6ae9909d643770bdea8 Mon Sep 17 00:00:00 2001 From: asif Date: Sat, 9 Aug 2025 21:43:59 +0530 Subject: [PATCH 12/21] removed unnecessary async keyword from validators --- src/validators/neft.validator.js | 2 +- src/validators/transfer.validator.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validators/neft.validator.js b/src/validators/neft.validator.js index 6f6dc83..a4bd271 100644 --- a/src/validators/neft.validator.js +++ b/src/validators/neft.validator.js @@ -1,4 +1,4 @@ -const neftValidator = async (req, res, next) => { +const neftValidator = (req, res, next) => { const { fromAccount, toAccount, diff --git a/src/validators/transfer.validator.js b/src/validators/transfer.validator.js index 431d463..2d10c48 100644 --- a/src/validators/transfer.validator.js +++ b/src/validators/transfer.validator.js @@ -1,4 +1,4 @@ -const transferValidator = async (req, res, next) => { +const transferValidator = (req, res, next) => { const { fromAccount, toAccount, toAccountType, amount } = req.body; const accountTypes = ['SB', 'LN', 'Savings', 'Current']; From 6f07e9beb9163578f93e041a5d874925649d6ca8 Mon Sep 17 00:00:00 2001 From: asif Date: Sun, 10 Aug 2025 15:16:26 +0530 Subject: [PATCH 13/21] extracted payment password validator from transfer route to its own validator --- src/routes/transfer.route.js | 16 +--------------- src/validators/payment.secret.validator.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 src/validators/payment.secret.validator.js diff --git a/src/routes/transfer.route.js b/src/routes/transfer.route.js index 7d94aea..3fd9afa 100644 --- a/src/routes/transfer.route.js +++ b/src/routes/transfer.route.js @@ -1,24 +1,10 @@ const transferController = require('../controllers/transfer.controller'); const { logger } = require('../util/logger'); const express = require('express'); -const tpinValidator = require('../validators/tpin.validator'); -const tpasswordValidator = require('../validators/tpassword.validator'); const transferValidator = require('../validators/transfer.validator'); +const passwordValidator = require('../validators/payment.secret.validator.js'); const router = express.Router(); -// Added for tpassword -const passwordValidator=async(req,res,next)=>{ - const{tpin,tpassword} =req.body; - if(tpin){ - return tpinValidator(req,res,next); - } - else if(tpassword){ - return tpasswordValidator(req,res,next); - } - else{ - return res.status(400).json({error:"tpin or tpassword is required"}) - } -} router.use(passwordValidator, transferValidator); const transferRoute = async (req, res) => { diff --git a/src/validators/payment.secret.validator.js b/src/validators/payment.secret.validator.js new file mode 100644 index 0000000..1f12e34 --- /dev/null +++ b/src/validators/payment.secret.validator.js @@ -0,0 +1,15 @@ +const tpasswordValidator = require('./tpassword.validator.js'); +const tpinValidator = require('./tpin.validator.js'); + +const paymentSecretValidator = async (req, res, next) => { + const { tpin, tpassword } = req.body; + if (tpin) { + return tpinValidator(req, res, next); + } else if (tpassword) { + return tpasswordValidator(req, res, next); + } else { + return res.status(400).json({ error: 'tpin or tpassword is required' }); + } +}; + +module.exports = paymentSecretValidator; From 33ef65de325f9d3f3579a3298b132a234af242f8 Mon Sep 17 00:00:00 2001 From: asif Date: Sun, 10 Aug 2025 15:17:44 +0530 Subject: [PATCH 14/21] implemented neft transaction feature --- src/controllers/neft.controller.js | 37 ++++++++++++++++++++++ src/routes/index.js | 2 ++ src/routes/neft.route.js | 49 ++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 src/controllers/neft.controller.js create mode 100644 src/routes/neft.route.js diff --git a/src/controllers/neft.controller.js b/src/controllers/neft.controller.js new file mode 100644 index 0000000..92ae32d --- /dev/null +++ b/src/controllers/neft.controller.js @@ -0,0 +1,37 @@ +const axios = require('axios'); +const { logger } = require('../util/logger'); + +async function send( + fromAccount, + toAccount, + amount, + ifscCode, + beneficiaryName, + remitterName +) { + try { + const response = await axios.post( + 'http://localhost:8690/kccb/Neftfundtransfer', + { + stFromAcc: fromAccount, + stToAcc: toAccount, + stTranAmt: amount, + stCommission: 0, + stIfscCode: ifscCode, + stFullName: remitterName, + stBeneName: beneficiaryName, + stAddress1: '', + stAddress2: '', + stAddress3: '', + } + ); + return response.data; + } catch (error) { + logger.error(error); + throw new Error( + 'API call failed: ' + (error.response?.data?.message || error.message) + ); + } +} + +module.exports = { send }; diff --git a/src/routes/index.js b/src/routes/index.js index 11473ec..68fb683 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -5,6 +5,7 @@ const transactionRoute = require('./transactions.route'); const authenticate = require('../middlewares/auth.middleware'); const transferRoute = require('./transfer.route'); const beneficiaryRoute = require('./beneficiary.route'); +const neftRoute = require('./neft.route.js'); const { npciResponse } = require('../controllers/npci.controller'); const router = express.Router(); @@ -13,6 +14,7 @@ router.use('/auth', authRoute); router.use('/customer', authenticate, detailsRoute); router.use('/transactions/account/:accountNo', authenticate, transactionRoute); router.use('/payment/transfer', authenticate, transferRoute); +router.use('/payment/neft', authenticate, neftRoute); router.use('/beneficiary', authenticate, beneficiaryRoute); router.use('/npci/beneficiary-response', npciResponse); diff --git a/src/routes/neft.route.js b/src/routes/neft.route.js new file mode 100644 index 0000000..317478c --- /dev/null +++ b/src/routes/neft.route.js @@ -0,0 +1,49 @@ +const express = require('express'); +const neftController = require('../controllers/neft.controller'); +const { logger } = require('../util/logger'); +const neftValidator = require('../validators/neft.validator.js'); +const paymentSecretValidator = require('../validators/payment.secret.validator'); + +const router = express.Router(); +router.use(neftValidator, paymentSecretValidator); + +const neftRoute = async (req, res) => { + const { + fromAccount, + toAccount, + ifscCode, + amount, + beneficiaryName, + remitterName, + } = req.body; + + try { + const result = await neftController.send( + fromAccount, + toAccount, + amount, + ifscCode, + beneficiaryName, + remitterName + ); + logger.info(result); + + if (result.status.startsWith('O.K.')) { + const utr = result.status.slice(9, 25); + return res.json({ message: 'SUCCESS', utr }); + } else if (result.status.includes('INSUFFICIENT FUNDS')) { + return res.status(422).json({ error: 'INSUFFICIENT_FUNDS' }); + } else if (result.status.include('INVALID CHECK DIGIT')) { + return res.status(400).json({ error: 'INVALID_ACCOUNT_NUMBER' }); + } else { + return res.status(400).json({ error: 'PROBLEM_TRANSFERRING_FUNDS' }); + } + } catch (error) { + logger.error(error, 'error occured while doing NEFT transaction'); + return res.status(500).json({ error: 'INTERNAL_SERVER_ERROR' }); + } +}; + +router.post('/', neftRoute); + +module.exports = router; From bfd062be80e06cc43000646e1fd6e9e0c1a89e8c Mon Sep 17 00:00:00 2001 From: asif Date: Sun, 10 Aug 2025 15:28:37 +0530 Subject: [PATCH 15/21] removed logger from neft controller --- src/controllers/neft.controller.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/controllers/neft.controller.js b/src/controllers/neft.controller.js index 92ae32d..091b7a3 100644 --- a/src/controllers/neft.controller.js +++ b/src/controllers/neft.controller.js @@ -1,5 +1,4 @@ const axios = require('axios'); -const { logger } = require('../util/logger'); async function send( fromAccount, @@ -27,7 +26,6 @@ async function send( ); return response.data; } catch (error) { - logger.error(error); throw new Error( 'API call failed: ' + (error.response?.data?.message || error.message) ); From 2000ec3e4eaecbee53f8fb154eab8979f4ae22da Mon Sep 17 00:00:00 2001 From: asif Date: Sun, 10 Aug 2025 15:29:09 +0530 Subject: [PATCH 16/21] implemented RTGS payment feature --- src/controllers/rtgs.controller.js | 36 ++++++++++++++++++++++ src/routes/index.js | 4 ++- src/routes/rtgs.route.js | 48 ++++++++++++++++++++++++++++++ src/validators/rtgs.validator.js | 36 ++++++++++++++++++++++ 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/controllers/rtgs.controller.js create mode 100644 src/routes/rtgs.route.js create mode 100644 src/validators/rtgs.validator.js diff --git a/src/controllers/rtgs.controller.js b/src/controllers/rtgs.controller.js new file mode 100644 index 0000000..e5c9625 --- /dev/null +++ b/src/controllers/rtgs.controller.js @@ -0,0 +1,36 @@ +const axios = require('axios'); + +async function send( + fromAccount, + toAccount, + amount, + ifscCode, + beneficiaryName, + remitterName +) { + try { + const response = await axios.post( + 'http://localhost:8690/kccb/Rtgsfundtransfer', + { + stFromAcc: fromAccount, + stToAcc: toAccount, + stTranAmt: amount, + stCommission: 0, + stIfscCode: ifscCode, + stFullName: remitterName, + stBeneName: beneficiaryName, + stAddress1: '', + stAddress2: '', + stAddress3: '', + } + ); + return response.data; + } catch (error) { + throw new Error( + 'API call to CBS failed' + + (error.response?.data?.message || error.message) + ); + } +} + +module.exports = { send }; diff --git a/src/routes/index.js b/src/routes/index.js index 68fb683..9599fbd 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -5,7 +5,8 @@ const transactionRoute = require('./transactions.route'); const authenticate = require('../middlewares/auth.middleware'); const transferRoute = require('./transfer.route'); const beneficiaryRoute = require('./beneficiary.route'); -const neftRoute = require('./neft.route.js'); +const neftRoute = require('./neft.route'); +const rtgsRoute = require('./rtgs.route'); const { npciResponse } = require('../controllers/npci.controller'); const router = express.Router(); @@ -15,6 +16,7 @@ router.use('/customer', authenticate, detailsRoute); router.use('/transactions/account/:accountNo', authenticate, transactionRoute); router.use('/payment/transfer', authenticate, transferRoute); router.use('/payment/neft', authenticate, neftRoute); +router.use('/payment/rtgs', authenticate, rtgsRoute); router.use('/beneficiary', authenticate, beneficiaryRoute); router.use('/npci/beneficiary-response', npciResponse); diff --git a/src/routes/rtgs.route.js b/src/routes/rtgs.route.js new file mode 100644 index 0000000..389bb93 --- /dev/null +++ b/src/routes/rtgs.route.js @@ -0,0 +1,48 @@ +const express = require('express'); +const rtgsController = require('../controllers/rtgs.controller'); +const { logger } = require('../util/logger'); +const rtgsValidator = require('../validators/rtgs.validator.js'); +const paymentSecretValidator = require('../validators/payment.secret.validator'); + +const router = express.Router(); +router.use(rtgsValidator, paymentSecretValidator); + +const rtgsRoute = async (req, res) => { + const { + fromAccount, + toAccount, + ifscCode, + amount, + beneficiaryName, + remitterName, + } = req.body; + + try { + const result = await rtgsController.send( + fromAccount, + toAccount, + amount, + ifscCode, + beneficiaryName, + remitterName + ); + + if (result.status.startsWith('O.K.')) { + const utr = result.status.slice(9, 25); + return res.json({ message: 'SUCCESS', utr }); + } else if (result.status.includes('INSUFFICIENT FUNDS')) { + return res.status(422).json({ error: 'INSUFFICIENT_FUNDS' }); + } else if (result.status.include('INVALID CHECK DIGIT')) { + return res.status(400).json({ error: 'INVALID_ACCOUNT_NUMBER' }); + } else { + return res.status(400).json({ error: 'PROBLEM_TRANSFERRING_FUNDS' }); + } + } catch (error) { + logger.error(error, 'error occured while doing NEFT transaction'); + return res.status(500).json({ error: 'INTERNAL_SERVER_ERROR' }); + } +}; + +router.post('/', rtgsRoute); + +module.exports = router; diff --git a/src/validators/rtgs.validator.js b/src/validators/rtgs.validator.js new file mode 100644 index 0000000..6eb28d0 --- /dev/null +++ b/src/validators/rtgs.validator.js @@ -0,0 +1,36 @@ +const rtgsValidator = (req, res, next) => { + const { + fromAccount, + toAccount, + amount, + remitterName, + beneficiaryName, + ifscCode, + } = req.body; + + if (!isAccountNumbersValid(fromAccount, toAccount)) { + return res.status(400).json({ error: 'INVALID_ACCOUNT_NUMBER_FORMAT' }); + } + + if (amount < 200000) { + return res.status(400).json({ error: 'AMOUNT_SHOULD_BE_MORE_THAN_200000' }); + } + + if (!remitterName || !beneficiaryName) { + return res + .status(400) + .json({ error: 'REMITTER_NAME AND BENEFICIARY_NAME REQUIRED' }); + } + + if (!ifscCode || !/^[A-Z]{4}0[0-9]{6}$/.test(ifscCode)) { + return res.status(400).json({ error: 'INVALID_IFSC_CODE' }); + } + + next(); +}; + +const isAccountNumbersValid = (fromAcct, toAcct) => { + return !(!fromAcct || !toAcct || fromAcct.length != 11 || toAcct.length < 7); +}; + +module.exports = rtgsValidator; From 98ab9954bf4b148c842cf87dbb1af3c7801e4b3d Mon Sep 17 00:00:00 2001 From: asif Date: Sun, 10 Aug 2025 15:38:38 +0530 Subject: [PATCH 17/21] code formatting in auth.service --- src/services/auth.service.js | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/services/auth.service.js b/src/services/auth.service.js index db520ec..b5cfe71 100644 --- a/src/services/auth.service.js +++ b/src/services/auth.service.js @@ -48,10 +48,10 @@ async function setTpin(customerNo, tpin) { async function setLoginPassword(customerNo, login_psw) { const hashedLoginPassword = await hashPassword(login_psw); try { - await db.query('UPDATE users SET password_hash = $1 ,is_first_login = false WHERE customer_no = $2', [ - hashedLoginPassword, - customerNo, - ]); + await db.query( + 'UPDATE users SET password_hash = $1 ,is_first_login = false WHERE customer_no = $2', + [hashedLoginPassword, customerNo] + ); } catch (error) { throw new Error( `error occured while while setting new Login Password ${error.message}` @@ -62,7 +62,7 @@ async function setLoginPassword(customerNo, login_psw) { async function validateTransactionPassword(customerNo, tpassword) { const user = await findUserByCustomerNo(customerNo); if (!user?.transaction_password) return null; - const isMatch = await comparePassword(tpassword, user.transaction_password ); + const isMatch = await comparePassword(tpassword, user.transaction_password); return isMatch; } @@ -70,10 +70,10 @@ async function validateTransactionPassword(customerNo, tpassword) { async function setTransactionPassword(customerNo, trans_psw) { const hashedTransPassword = await hashPassword(trans_psw); try { - await db.query('UPDATE users SET transaction_password = $1 WHERE customer_no = $2', [ - hashedTransPassword, - customerNo, - ]); + await db.query( + 'UPDATE users SET transaction_password = $1 WHERE customer_no = $2', + [hashedTransPassword, customerNo] + ); } catch (error) { throw new Error( `error occured while while setting new Transaction Password ${error.message}` @@ -81,5 +81,13 @@ async function setTransactionPassword(customerNo, trans_psw) { } } -module.exports = { validateUser, findUserByCustomerNo, setTpin, validateTpin, - CheckFirstTimeLogin, setLoginPassword, validateTransactionPassword,setTransactionPassword }; +module.exports = { + validateUser, + findUserByCustomerNo, + setTpin, + validateTpin, + CheckFirstTimeLogin, + setLoginPassword, + validateTransactionPassword, + setTransactionPassword, +}; From cbfd1d6d098a39ade86a4ceb85cc76e59a72c8bb Mon Sep 17 00:00:00 2001 From: asif Date: Sun, 10 Aug 2025 18:14:46 +0530 Subject: [PATCH 18/21] integrated acct statement with date range filter --- src/controllers/transactions.controller.js | 26 +++++++++++++++-- src/routes/transactions.route.js | 33 ++++++++++++++++++++-- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/controllers/transactions.controller.js b/src/controllers/transactions.controller.js index 47a4f81..297ae8c 100644 --- a/src/controllers/transactions.controller.js +++ b/src/controllers/transactions.controller.js @@ -21,5 +21,27 @@ async function getLastTen(accountNumber) { ); } } - -module.exports = { getLastTen }; +async function getFiltered(accountNumber, fromDate, toDate) { + try { + const response = await axios.get( + `http://localhost:8688/kccb/cbs/montlyacctstmt/details`, + { + params: { stacctno: accountNumber, fromdate: fromDate, todate: toDate }, + } + ); + const transactions = response.data; + const processedTransactions = transactions.map((tx) => ({ + id: tx.stTransactionNumber, + name: tx.stTransactionDesc, + date: tx.stTransactionDate, + amount: tx.stTransactionAmount.slice(0, -3), + type: tx.stTransactionAmount.slice(-2), + })); + return processedTransactions; + } catch (error) { + throw new Error( + 'API call failde: ' + (error.response?.data?.message || error.message) + ); + } +} +module.exports = { getLastTen, getFiltered }; diff --git a/src/routes/transactions.route.js b/src/routes/transactions.route.js index 9e4ff8c..822c24a 100644 --- a/src/routes/transactions.route.js +++ b/src/routes/transactions.route.js @@ -3,15 +3,44 @@ const { logger } = require('../util/logger'); const transactionsRoute = async (req, res) => { const accountNo = req.params.accountNo; + const { fromDate, toDate } = req.query; + let data; try { - const data = await transactionsController.getLastTen(accountNo); + if (fromDate && toDate) { + if (!isValidDDMMYYYY(fromDate) || !isValidDDMMYYYY(toDate)) { + return res.status(400).json({ error: 'INVALID_DATE_FORMAT' }); + } + data = await transactionsController.getFiltered( + accountNo, + fromDate, + toDate + ); + } else { + data = await transactionsController.getLastTen(accountNo); + } return res.json(data); } catch (error) { - logger.error('error retriving last 10 txns', error); + logger.error('error retriving transaction history', error); return res .status(500) .json({ message: 'error occured while fetching transactions' }); } }; +function isValidDDMMYYYY(dateStr) { + if (!/^\d{8}$/.test(dateStr)) return false; + + const day = parseInt(dateStr.slice(0, 2), 10); + const month = parseInt(dateStr.slice(2, 4), 10); + const year = parseInt(dateStr.slice(4), 10); + + const date = new Date(year, month - 1, day); + + return ( + date.getFullYear() === year && + date.getMonth() === month - 1 && + date.getDate() === day + ); +} + module.exports = transactionsRoute; From de90be86a729646e81840743476cd1f8f069a641 Mon Sep 17 00:00:00 2001 From: asif Date: Sun, 10 Aug 2025 23:28:48 +0530 Subject: [PATCH 19/21] return random names instead of John Doe in beneficiary validation --- src/controllers/beneficiary.controller.js | 4 ++- src/util/name.generator.js | 36 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/util/name.generator.js diff --git a/src/controllers/beneficiary.controller.js b/src/controllers/beneficiary.controller.js index fdad3e8..ccac3bf 100644 --- a/src/controllers/beneficiary.controller.js +++ b/src/controllers/beneficiary.controller.js @@ -1,6 +1,7 @@ const { logger } = require('../util/logger'); const beneficiaryService = require('../services/beneficiary.service'); const db = require('../config/db'); +const randomName = require('../util/name.generator'); async function validateWithinBank(req, res) { const { accountNumber } = req.query; @@ -40,7 +41,8 @@ async function validateOutsideBank(req, res) { return res.status(401).json({ error: 'invalid account number' }); //**IN PRODUCTION** poll the redis server continuously giving the refNo since the response from NPCI will be stored there await delay(3000); - return res.json({ name: 'John Doe' }); + const name = randomName(); + return res.json({ name }); } catch (err) { logger.error(err, 'beneficiary validation within bank failed'); res.status(500).json({ error: 'invalid account number' }); diff --git a/src/util/name.generator.js b/src/util/name.generator.js new file mode 100644 index 0000000..2a90373 --- /dev/null +++ b/src/util/name.generator.js @@ -0,0 +1,36 @@ +const indianFirstNames = [ + 'Aarav', + 'Vivaan', + 'Aditya', + 'Vihaan', + 'Krishna', + 'Ishaan', + 'Rohan', + 'Ananya', + 'Diya', + 'Aisha', + 'Priya', + 'Sneha', +]; +const indianLastNames = [ + 'Sharma', + 'Verma', + 'Iyer', + 'Reddy', + 'Patel', + 'Mehta', + 'Choudhary', + 'Kumar', + 'Das', + 'Rao', +]; + +function getRandomIndianName() { + const firstName = + indianFirstNames[Math.floor(Math.random() * indianFirstNames.length)]; + const lastName = + indianLastNames[Math.floor(Math.random() * indianLastNames.length)]; + return `${firstName} ${lastName}`; +} + +module.exports = getRandomIndianName; From d6a750092c1ea998add55143128636063ba7e8b3 Mon Sep 17 00:00:00 2001 From: asif Date: Sun, 10 Aug 2025 23:29:17 +0530 Subject: [PATCH 20/21] fixed typo in neft.route --- src/routes/neft.route.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/neft.route.js b/src/routes/neft.route.js index 317478c..89342b3 100644 --- a/src/routes/neft.route.js +++ b/src/routes/neft.route.js @@ -33,7 +33,7 @@ const neftRoute = async (req, res) => { return res.json({ message: 'SUCCESS', utr }); } else if (result.status.includes('INSUFFICIENT FUNDS')) { return res.status(422).json({ error: 'INSUFFICIENT_FUNDS' }); - } else if (result.status.include('INVALID CHECK DIGIT')) { + } else if (result.status.includes('INVALID CHECK DIGIT')) { return res.status(400).json({ error: 'INVALID_ACCOUNT_NUMBER' }); } else { return res.status(400).json({ error: 'PROBLEM_TRANSFERRING_FUNDS' }); From 4ff437319e050f43508ff7efc85c03900188e520 Mon Sep 17 00:00:00 2001 From: asif Date: Mon, 11 Aug 2025 00:04:36 +0530 Subject: [PATCH 21/21] fixed typo in beneficiary.service --- src/services/beneficiary.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/beneficiary.service.js b/src/services/beneficiary.service.js index 736fe2b..136f3d1 100644 --- a/src/services/beneficiary.service.js +++ b/src/services/beneficiary.service.js @@ -53,10 +53,10 @@ async function getAllBeneficiaries(customerNo) { accountNo: row['account_no'], name: row['name'], accountType: row['account_type'], - ifscCdoe: row['ifsc_code'], + ifscCode: row['ifsc_code'], bankName: row['bank_name'], branchName: row['branch_name'], - } + }; }); return list;