This commit is contained in:
2025-09-01 13:45:39 +05:30
13 changed files with 238 additions and 10 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,2 @@
node_modules/
.vscode/
.env/
.env

View File

@@ -1,6 +1,6 @@
const express = require('express');
const cors = require('cors');
const { logger } = require('./util/logger');
const { logger, requestLogger } = require('./util/logger');
const routes = require('./routes');
const app = express();
@@ -9,6 +9,26 @@ app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
requestLogger.info(
{
ip: req.ip || req.connection.remoteAddress,
method: req.method,
url: req.originalUrl,
headers: req.headers,
body: req.body,
status: res.statusCode,
responseTime: `${Date.now() - start}ms`,
},
'Incoming request'
);
});
next();
});
app.use('/api', routes);
app.get('/health', (_, res) => res.send('server is healthy'));
app.use((err, _req, res, _next) => {
@@ -17,3 +37,4 @@ app.use((err, _req, res, _next) => {
});
module.exports = app;

View File

@@ -14,7 +14,8 @@ async function login(req, res) {
const currentTime = new Date().toISOString();
try {
const user = await authService.validateUser(customerNo, password);
if (!user) return res.status(401).json({ error: 'invalid credentials' });
if (!user || !password)
return res.status(401).json({ error: 'invalid credentials' });
const token = generateToken(user.customer_no, '1d');
const FirstTimeLogin = await authService.CheckFirstTimeLogin(customerNo);
await db.query('UPDATE users SET last_login = $1 WHERE customer_no = $2', [
@@ -34,7 +35,6 @@ async function fetchUserDetails(req, res) {
const user = await authService.findUserByCustomerNo(customerNo);
if (!user) return res.status(404).json({ message: 'USER_NOT_FOUND' });
return res.json(user);
} catch (err) {
logger.error(err, 'error occured while fetching user details');
res.status(500).json({ error: 'something went wrong' });
@@ -102,4 +102,11 @@ async function setTransactionPassword(req, res) {
}
}
module.exports = { login, tpin, setTpin, setLoginPassword, setTransactionPassword,fetchUserDetails };
module.exports = {
login,
tpin,
setTpin,
setLoginPassword,
setTransactionPassword,
fetchUserDetails,
};

View File

@@ -95,6 +95,24 @@ async function getBeneficiary(req, res) {
}
}
async function deleteBeneficiary(req, res) {
const { beneficiaryAccountNo } = req.params;
try {
await beneficiaryService.deleteBeneficiary(req.user, beneficiaryAccountNo);
res.status(204).send();
} 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' });
}
}
}
async function getIfscDetails(req, res) {
const { ifscCode } = req.query;
if (!ifscCode) {
@@ -127,4 +145,5 @@ module.exports = {
addBeneficiary,
getIfscDetails,
getBeneficiary,
deleteBeneficiary,
};

View File

@@ -30,6 +30,8 @@ async function send(
'API call failed: ' + (error.response?.data?.message || error.message)
);
}
}
module.exports = { send };

View File

@@ -0,0 +1,98 @@
const { setJson, getJson } = require('../config/redis');
const { generateOTP } = require('../otpgenerator');
const { logger } = require('../util/logger');
const axios = require('axios');
const templates = require('../util/sms_template');
// Send OTP
async function SendOtp(req, res) {
const { mobileNumber, type, amount, beneficiary, ifsc, acctFrom, acctTo, ref, date } = req.body;
if (!mobileNumber || !type) {
return res.status(400).json({ error: 'Mobile number and type are required' });
}
try {
const otp = generateOTP(6);
let message;
// Pick template based on type
switch (type) {
case 'IMPS':
message = templates.IMPS(otp);
break;
case 'NEFT':
message = templates.NEFT(otp, amount, beneficiary);
break;
case 'RTGS':
message = templates.RTGS(otp, amount, beneficiary);
break;
case 'BENEFICIARY_ADD':
message = templates.BENEFICIARY_ADD(otp, beneficiary, ifsc);
break;
case 'BENEFICIARY_SUCCESS':
message = templates.BENEFICIARY_SUCCESS(beneficiary);
break;
case 'NOTIFICATION':
message = templates.NOTIFICATION(acctFrom, acctTo, amount, ref, date);
break;
case 'FORGOT_PASSWORD':
message = templates.FORGOT_PASSWORD(otp);
break;
default:
return res.status(400).json({ error: 'Invalid OTP type' });
}
// Call SMS API
const response = await axios.post('http://localhost:9999/api/SendtoMessage', {
mobileNumber,
stMessage: message,
});
if (response.data) {
// Save OTP only if it's OTP based (skip notifications without OTP)
if (message.includes('OTP')) {
await setJson(`otp:${mobileNumber}`, otp, 300);
}
logger.info(`Sent OTP [${otp}] for type [${type}] to ${mobileNumber}`);
}
return res.status(200).json({ message: 'Message sent successfully' });
} catch (err) {
logger.error(err, 'Error sending OTP');
return res.status(500).json({ error: 'Internal server error' });
}
}
// Verify OTP
async function VerifyOtp(req, res) {
const { mobileNumber } = req.query;
const {otp} =req.body
if (!mobileNumber || !otp) {
return res.status(400).json({ error: 'Phone number and OTP are required' });
}
try {
const storedOtp = await getJson(`otp:${mobileNumber}`);
if (!storedOtp) {
return res.status(400).json({ error: 'OTP expired or not found' });
}
if (parseInt(otp, 10) !== parseInt(storedOtp, 10)) {
return res.status(400).json({ error: 'Invalid OTP' });
}
return res.status(200).json({ message: 'OTP verified successfully' });
} catch (err) {
logger.error(err, 'Error verifying OTP');
return res.status(500).json({ error: 'Internal server error' });
}
}
module.exports = { SendOtp, VerifyOtp };

12
src/otpgenerator.js Normal file
View File

@@ -0,0 +1,12 @@
function generateOTP(length) {
const digits = '0123456789';
let otp = '';
otp += digits[Math.floor(Math.random() * 9) + 1]; // first digit cannot be zero
for (let i = 1; i < length; i++) {
otp += digits[Math.floor(Math.random() * digits.length)];
}
return otp;
}
module.exports = { generateOTP };

View File

@@ -9,5 +9,9 @@ router.get('/validate/outside-bank', beneficiaryController.validateOutsideBank);
router.get('/ifsc-details', beneficiaryController.getIfscDetails);
router.get('/', beneficiaryController.getBeneficiary);
router.post('/', newBeneficiaryValidator, beneficiaryController.addBeneficiary);
router.delete(
'/:beneficiaryAccountNo',
beneficiaryController.deleteBeneficiary
);
module.exports = router;

View File

@@ -10,6 +10,7 @@ const neftRoute = require('./neft.route');
const rtgsRoute = require('./rtgs.route');
const impsRoute = require('./imps.route');
const { npciResponse } = require('../controllers/npci.controller');
const otp = require('./otp.route');
const router = express.Router();
@@ -23,6 +24,7 @@ router.use('/payment/rtgs', authenticate, rtgsRoute);
router.use('/payment/imps', authenticate, impsRoute);
router.use('/beneficiary', authenticate, beneficiaryRoute);
router.use('/npci/beneficiary-response', npciResponse);
router.use('/otp', otp);
module.exports = router;

13
src/routes/otp.route.js Normal file
View File

@@ -0,0 +1,13 @@
const express = require('express');
const otpController = require('../controllers/otp.controller');
const router = express.Router();
// Send OTP (POST request with body)
router.post('/send', otpController.SendOtp);
// Verify OTP (GET request with query params ?mobileNumber=xxx&otp=123456)
router.post('/verify', otpController.VerifyOtp);
module.exports = router;

View File

@@ -44,6 +44,16 @@ async function getSingleBeneficiary(customerNo, accountNo) {
return result.rows[0];
}
async function deleteBeneficiary(customerNo, beneficiaryAccountNo) {
const queryStr =
'DELETE FROM beneficiaries WHERE customer_no = $1 AND account_no = $2';
const result = await db.query(queryStr, [customerNo, beneficiaryAccountNo]);
if (result.rowCount == 0) {
throw new Error('ACCOUNT_NOT_FOUND');
}
return;
}
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';
@@ -66,4 +76,5 @@ module.exports = {
validateOutsideBank,
getAllBeneficiaries,
getSingleBeneficiary,
deleteBeneficiary,
};

View File

@@ -1,6 +1,19 @@
const pino = require('pino');
const fs = require('fs');
const path = require('path');
const isDev = process.env.NODE_ENV !== 'production';
const logDir = path.join(__dirname, '../..', 'logs');
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
const requestLoggerStream = pino.destination({
dest: path.join(logDir, 'requests.log'),
sync: false,
});
const logger = pino({
transport: isDev
? {
@@ -15,8 +28,12 @@ const logger = pino({
level: isDev ? 'debug' : 'info',
});
const requestLogger = (req, _res, next) => {
logger.info(`${req.method} ${req.url}`);
next();
};
const requestLogger = pino(
{
level: 'info',
base: null,
},
requestLoggerStream
);
module.exports = { logger, requestLogger };

23
src/util/sms_template.js Normal file
View File

@@ -0,0 +1,23 @@
const templates = {
IMPS: (otp) => `Dear Customer, Please complete the fund transfer with OTP ${otp} -KCCB`,
NEFT: (otp, amount, beneficiary) =>
`Dear Customer, Please complete the NEFT of Rs.${amount} to ${beneficiary} with OTP:${otp} -KCCB`,
RTGS: (otp, amount, beneficiary) =>
`Dear Customer, Please complete the RTGS of Rs.${amount} to ${beneficiary} with OTP:${otp} -KCCB`,
BENEFICIARY_ADD: (otp, beneficiary, ifsc) =>
`Dear Customer, You have added beneficiary ${beneficiary} ${ifsc} for NEFT/RTGS. Please endorse the beneficiary with OTP ${otp} -KCCB`,
BENEFICIARY_SUCCESS: (beneficiary) =>
`Dear Customer, Your Beneficiary: ${beneficiary} for Net Banking is added successfully -KCCB`,
NOTIFICATION: (acctFrom, acctTo, amount, ref, date) =>
`Your A/c ${acctFrom} is debited for Rs. ${amount} to the credit of A/c ${acctTo} thru Net Banking - ref: ${ref} - ${date} - Kangra Central Co-Operative Bank -KCCB`,
FORGOT_PASSWORD: (otp) =>
`Dear Customer, Forgot Password OTP is ${otp} -KCCB`,
};
module.exports = templates;