Skip to content

Commit

Permalink
added security checks and enhanced barter init and resp logic
Browse files Browse the repository at this point in the history
  • Loading branch information
abhiraj-ku committed Oct 5, 2024
1 parent 99147c9 commit fa48eac
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 30 deletions.
97 changes: 67 additions & 30 deletions src/controllers/barterSettlement.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ const validateBarterPayInput = require('../helpers/validateBarterPay');
const queueBarterNotification = require('../services/emailQueueProducer');

// Redis functional imports
const redisClient = require('./redisServer');
const BarterPayment = require('../models/barterModel');
const mailOptions = require('../utils/mailOptions');

const redisClient = require('./redisServer');
const setAsync = promisify(redisClient.set).bind(redisClient);
const expireAsync = promisify(redisClient.expire).bind(redisClient);
const getAsync = promisify(redisClient.get).bind(redisClient);
const delAsync = promisify(redisClient.del).bind(redisClient);

async function tempUserDataRedis(barterId, debtorId, creditorId, groupId, amount, barterType) {
try {
Expand Down Expand Up @@ -59,10 +62,10 @@ async function tempUserDataRedis(barterId, debtorId, creditorId, groupId, amount
// Debtor route
// Debtor initiates the barter option
module.exports.initiateBarterPayment = async (req, res) => {
const { debtorId, groupId, amount, barterType } = req.body;
const creditorId = req.user._id; // the person to whom payment is made
const { creditorId, groupId, amount, barterType } = req.body;
const debtorId = req.user._id; // the person initiating the payment (debtor)

// Custom validation function to valid input data -> helper/validateBarterPay.js
// Custom validation function to validate input data -> helper/validateBarterPay.js
const validationErrors = validateBarterPayInput(
debtorId,
creditorId,
Expand All @@ -71,12 +74,12 @@ module.exports.initiateBarterPayment = async (req, res) => {
barterType
);

if (validateInput.size > 0) {
return res.status(400).json({ message: 'Valiation error in bPay', error: validationErrors });
if (validationErrors.size > 0) {
return res.status(400).json({ message: 'Validation error in bPay', error: validationErrors });
}
try {
if (amount > req.user.riskApetite) {
return res.status(400).json({ message: 'Barter amount exceeds your risk apetite' });
return res.status(400).json({ message: 'Barter amount exceeds your risk appetite' });
}

// Fetch group and creditor from the database
Expand All @@ -96,24 +99,38 @@ module.exports.initiateBarterPayment = async (req, res) => {
message: "Amount exceeds group's barter cap.",
});
}

// Unique id for each barter payment
const barterId = uuidv4();

// save the initial barter request
const barterRequest = await BarterPayment.create({
barterId, // Store the UUID
debtorId,
creditorId,
groupId,
amount,
barterType,
agreementStatus: 'none',
status: 'pending',
});
await barterRequest.save();

// Store temp data to redis also
await tempUserDataRedis(barterId, debtorId, creditorId, groupId, amount, barterType);

// Mail the creditor to approve the payment
const mailOptions = {
const mailOptionsData = mailOptions({
from: `"SplitBhai Team" <backend.team@splitbhai.com>`,
to: creditor.email,
to: creditor,
subject: 'New Barter Request',
text: `You have a new barter request from ${req.user.name} for ${amount}. Barter Type: ${barterType}`,
};
await queueBarterNotification(mailOptions);
});
await queueBarterNotification(mailOptionsData);

res.status(201).json({ message: 'Barter request initiated successfully.' });
res.status(201).json({ message: 'Barter request initiated successfully.', barterId });
} catch (error) {
res.status(500).json({ message: 'Server error.', error: err.message });
console.error('Error initiating barter payment:', error);
res.status(500).json({ message: 'Server error.', error: error.message });
}
};

Expand All @@ -126,36 +143,56 @@ module.exports.respBarter = async (req, res) => {
const barterPaymentInfo = await getAsync(barterId);

if (!barterPaymentInfo) {
res.status(400).json({
return res.status(400).json({
message: 'The barter request has expired, Please ask the debtor to initiate payment again',
});
}

// Parse the data from redis ttl
const { debtorId, groupId, amount, barterType } = JSON.parse(barterPaymentInfo);
const {
debtorId,
groupId,
amount,
barterType,
creditorId: storedCreditor,
} = JSON.parse(barterPaymentInfo);

// check if the reposding creditor is the one whose id is stored in data
if (creditorId.toString() != creditorId) {
res.status(403).json({ message: 'You are not authorized creditor for this barter payment' });
if (creditorId.toString() != storedCreditor.toString()) {
return res
.status(403)
.json({ message: 'You are not authorized creditor for this barter payment' });
}

// Check for status "approve" & "reject" one by one
if (status == 'approve') {
const barterPayment = await BarterPayment.create({
debtorId,
debtorId,
groupId,
amount,
barterType,
status: 'approved',
});
// delete barter request from redis
await delAsync(barterId);

await barterPayment.save();
// Check for status "creditor_approved" & "reject" one by one
if (status == 'approve') {
let barterRequest = await BarterPayment.findOne({ barterId });
if (!barterRequest) {
await BarterPayment.create({
debtorId,
debtorId,
groupId,
amount,
barterType,
agreementStatus: 'creditor_approved',
status: 'approved',
});
} else {
barterRequest.agreementStatus = 'creditor_approved';
await barterRequest.save();
}

// Delete from redis cache since it's updated into mongodb
await delAsync(barterId);

return res
.status(200)
.json({ message: 'Barter request approved successfully', barterPayment });
.json({ message: 'Barter request approved successfully', barterRequest });
} else if (status == 'reject') {
await delAsync(barterRequest);
return res.status(200).json({ message: 'Barter request declined' });
} else {
return res.status(400).json({
Expand Down
9 changes: 9 additions & 0 deletions src/models/barterModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ const { Schema } = mongoose;

const barterPaymentsSchema = new Schema(
{
barterId: {
type: String,
required: true,
},
debtor: {
type: Schema.Types.ObjectId,
ref: 'User',
Expand Down Expand Up @@ -32,6 +36,11 @@ const barterPaymentsSchema = new Schema(
enum: ['pending', 'approved', 'rejected'],
default: 'pending',
},
agreementStatus: {
type: String,
enum: ['none', 'debtor_approved', 'creditor_approved', 'both_approved'],
default: 'none',
},
settlementdate: { type: Date, default: Date.now },
},
{ timestamps: true }
Expand Down
29 changes: 29 additions & 0 deletions src/utils/mailOptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// custom mail options based on the params passed for different use cases
// separation of concerns

function mailOptions({ from, to, subject, text }) {
const defaultFrom = 'noreply@splitbhai.com';
if (!from || typeof from !== 'string') {
from = defaultFrom;
}
if (!to || !to.email || typeof to.email !== 'string') {
throw new Error('Invalid recipient email address.');
}

if (!subject || typeof subject !== 'string') {
throw new Error('Invalid subject.');
}
if (!text || typeof text !== 'string') {
throw new Error('Invalid email body.');
}

// Safe email data to be returned
return {
from: from,
to: to.email,
subject: subject,
text: text,
};
}

module.exports = mailOptions;

0 comments on commit fa48eac

Please sign in to comment.