This commit is contained in:
2025-08-11 12:32:22 +05:30
20 changed files with 471 additions and 104 deletions

View File

@@ -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'));

View File

@@ -1,8 +1,7 @@
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');
const randomName = require('../util/name.generator');
async function validateWithinBank(req, res) {
const { accountNumber } = req.query;
@@ -25,58 +24,75 @@ 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' });
//**IN PRODUCTION** poll the redis server continuously giving the refNo since the response from NPCI will be stored there
await delay(3000);
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' });
}
}
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]);
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' });
}
}
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' });
return;
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' });
}
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) {
res.status(404).json({ error: 'NOT_FOUND' });
return;
}
res.json({ message: 'FOUND' });
}
async function getIfscDetails(req, res) {
@@ -87,7 +103,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) {
@@ -107,7 +123,8 @@ function delay(ms) {
module.exports = {
validateWithinBank,
validateOutsideBank,
addBeneficiary,
checkBeneficiary,
getIfscDetails,
getBeneficiary,
};

View File

@@ -0,0 +1,35 @@
const axios = require('axios');
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) {
throw new Error(
'API call failed: ' + (error.response?.data?.message || error.message)
);
}
}
module.exports = { send };

View File

@@ -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;
}
}

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -5,12 +5,9 @@ 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('/ifsc-details', beneficiaryController.getIfscDetails);
router.post(
'/add',
newBeneficiaryValidator,
beneficiaryController.addBeneficiary
);
router.get('/', beneficiaryController.getBeneficiary);
router.post('/', newBeneficiaryValidator, beneficiaryController.addBeneficiary);
module.exports = router;

View File

@@ -6,6 +6,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');
const rtgsRoute = require('./rtgs.route');
const { npciResponse } = require('../controllers/npci.controller');
const router = express.Router();
@@ -15,6 +17,8 @@ router.use('/auth/admin',adminAuthRoute);
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);

49
src/routes/neft.route.js Normal file
View File

@@ -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.includes('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;

48
src/routes/rtgs.route.js Normal file
View File

@@ -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;

View File

@@ -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;

View File

@@ -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) => {

View File

@@ -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,
};

View File

@@ -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'],
ifscCode: row['ifsc_code'],
bankName: row['bank_name'],
branchName: row['branch_name'],
};
});
return list;
}
module.exports = {
validateWithinBank,
validateOutsideBank,
getAllBeneficiaries,
getSingleBeneficiary,
};

View File

@@ -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;

View File

@@ -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) {

View File

@@ -0,0 +1,36 @@
const neftValidator = (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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,7 +1,7 @@
const transferValidator = async (req, res, next) => {
const transferValidator = (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' });
}