Skip to content

Commit

Permalink
Feat: integrated payhere
Browse files Browse the repository at this point in the history
Closes #1
  • Loading branch information
Akalanka47000 committed Nov 20, 2023
1 parent 5f82d0f commit 282ccd9
Show file tree
Hide file tree
Showing 25 changed files with 405 additions and 22 deletions.
7 changes: 6 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@ SCOREKEEPER_REPO_OWNER=
SCOREKEEPER_REPO_NAME=

AZURE_CHALLENGE_UPLOAD_SAS_TOKEN=
AZURE_SOLUTION_DOWNLOAD_SAS_TOKEN=
AZURE_SOLUTION_DOWNLOAD_SAS_TOKEN=

PAYHERE_BASE_URL=
PAYHERE_MERCHANT_SECRET=
PAYHERE_MERCHANT_ID=
PAYHERE_AUTHORIZATION_CODE=
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@sliit-foss/functions": "2.2.4",
"@sliit-foss/http-logger": "1.1.2",
"@sliit-foss/module-logger": "1.1.5",
"@sliit-foss/service-connector": "1.2.4",
"@sliit-foss/service-connector": "2.0.0",
"bcryptjs": "2.4.3",
"celebrate": "15.0.1",
"compression": "1.7.4",
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ export const AZURE = {
SOLUTION_DOWNLOAD_SAS_TOKEN: process.env.AZURE_SOLUTION_DOWNLOAD_SAS_TOKEN
};

export const PAYHERE = {
BASE_URL: process.env.PAYHERE_BASE_URL,
MERCHANT_ID: process.env.PAYHERE_MERCHANT_ID,
MERCHANT_SECRET: process.env.PAYHERE_MERCHANT_SECRET,
AUTHORIZATION_CODE: process.env.PAYHERE_AUTHORIZATION_CODE
};

export const PORT = process.env.PORT || 3000;

export const APP_ENV = process.env.APP_ENV;
Expand All @@ -44,6 +51,7 @@ export default {
SCOREKEEPER,
JWT,
AZURE,
PAYHERE,
PORT,
APP_ENV,
MONGO_URI,
Expand Down
24 changes: 24 additions & 0 deletions src/controllers/event/ticket.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { default as createError } from 'http-errors';
import * as eventService from '@/services/event';
import { makeResponse } from '@/utils/response';

Expand All @@ -15,3 +16,26 @@ export const approveUserTicket = async (req, res) => {
await eventService.approveTicket(req.params.ticket_id, req.user);
return makeResponse({ res, message: 'User ticket approved successfully' });
};

export const initiateTicketPayment = async (req, res) => {
const ticket = await eventService.initiateTicketPayment(req.params.ticket_id, req.body.coupon_code, req.user);
return makeResponse({ res, data: ticket, message: 'Ticket payment initiated successfully' });
};

export const verifyTicketPayment = async (req, res) => {
const ticket = await eventService.verifyTicketPayment(req.params.ticket_id, req.user);
return makeResponse({ res, data: ticket, message: 'Ticket payment verified successfully' });
};

export const cancelTicketPayment = async (req, res) => {
await eventService.cancelTicketPayment(req.params.ticket_id, req.user);
return makeResponse({ res, message: 'Ticket payment cancelled successfully' });
};

export const transferTicket = async (req, res) => {
if (req.body.email === req.user.email) {
throw new createError(400, 'You cannot transfer your ticket to yourself');
}
await eventService.transferTicket(req.params.ticket_id, req.body.email, req.user);
return makeResponse({ res, message: 'Ticket transferred successfully' });
};
15 changes: 15 additions & 0 deletions src/controllers/webhook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { default as createError } from 'http-errors';
import { webhookSignature } from '@/services/payhere/util';
import * as webhookService from '@/services/webhook';
import { makeResponse } from '@/utils/response';

export const handlePayment = async (req, res) => {
if (
req.body?.md5sig !==
webhookSignature(req.body.order_id, req.body.payhere_amount, req.body.payhere_currency, req.body.status_code)
) {
throw new createError(401, 'Invalid signature');
}
await webhookService.paymentStatusHandler(req.body);
return makeResponse({ res, message: 'Webhook received successfully' });
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
async up(db) {
await db.collection('events').updateMany({}, { $rename: { capacity: 'seats' } });
},

async down(db) {
await db.collection('events').updateMany({}, { $rename: { seats: 'capacity' } });
}
};
2 changes: 1 addition & 1 deletion src/models/event/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const EventSchema = new mongoose.Schema(
type: String,
required: true
},
capacity: {
seats: {
type: Number,
required: true
},
Expand Down
18 changes: 18 additions & 0 deletions src/models/event/setting.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ const PaymentSettingsSchema = new mongoose.Schema({
type: Number,
default: 0
}
},
premium_tickets: {
enabled: {
type: Boolean,
default: false
},
cost: {
type: Number,
default: 0
},
seats: {
type: Number,
default: 0
}
}
});

Expand All @@ -52,6 +66,10 @@ const SettingSchema = new mongoose.Schema({
type: Boolean,
default: true
},
ticket_transfer_enabled: {
type: Boolean,
default: false
},
registration_start: {
type: Date,
required: true
Expand Down
4 changes: 4 additions & 0 deletions src/models/ticket.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ const TicketSchema = new mongoose.Schema(
utilized: {
type: Boolean,
default: false
},
premium: {
type: Boolean,
default: false
}
},
{
Expand Down
2 changes: 2 additions & 0 deletions src/repository/coupon.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export const findAll = ({ sort = {}, filter = {}, page, limit = 10 }) => {

export const findById = (id) => Coupon.findById(id).lean();

export const findByCode = (code) => Coupon.findOne({ code }).populate('ticket').lean();

export const findOne = (filters, options = {}) => Coupon.findOne(filters, options).lean();

export const findOneAndUpdate = (filters, data) => Coupon.findOneAndUpdate(filters, data, { new: true }).lean();
Expand Down
2 changes: 2 additions & 0 deletions src/repository/speaker.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export const findAll = ({ sort = {}, filter = {}, page, limit = 10 }) => {

export const findById = (id) => Speaker.findById(id).lean();

export const findByEmail = (email) => Speaker.findOne({ email }).lean();

export const findOne = (filters, options = {}) => Speaker.findOne(filters, options).lean();

export const findOneAndUpdate = (filters, data) => Speaker.findOneAndUpdate(filters, data, { new: true }).lean();
Expand Down
15 changes: 14 additions & 1 deletion src/repository/ticket.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import createError from 'http-errors';
import { omit } from 'lodash';
import mongoose from 'mongoose';
import Ticket, { payments } from '@/models/ticket';
Expand All @@ -21,7 +22,12 @@ export const findOne = (filters, filterFields = false) => {
return query.exec();
};

export const findById = (id) => Ticket.findById(id).lean();
export const findById = async (id, throwError = false) => {
const ticket = await Ticket.findById(id).lean();
if (!ticket && throwError) throw new createError(404, "Ticket doesn't exist");
};

export const findByReference = (reference) => Ticket.findOne({ reference }).lean();

export const findWithApprovedUser = (id) => Ticket.findById(id).populate('approved_by').lean();

Expand All @@ -31,6 +37,12 @@ export const updateById = (id, data) => findOneAndUpdate({ _id: id }, data);

export const deleteById = (id) => Ticket.deleteOne({ _id: id });

export const getPremiumTicketCount = (eventId) =>
Ticket.countDocuments({ event: eventId, premium: true, payment_status: payments.success }).lean();

export const getPaidTicketCount = (eventId) =>
Ticket.countDocuments({ event: eventId, payment_status: payments.success }).lean();

export const getTicketStats = async (eventId) => {
const pipeline = [
{
Expand All @@ -40,6 +52,7 @@ export const getTicketStats = async (eventId) => {
approved_count: { $sum: { $cond: [{ $eq: ['$approved', true] }, 1, 0] } },
transferred_count: { $sum: { $cond: [{ $eq: ['$transferred', true] }, 1, 0] } },
utilized_count: { $sum: { $cond: [{ $eq: ['$utilized', true] }, 1, 0] } },
premium_count: { $sum: { $cond: [{ $eq: ['$premium', true] }, 1, 0] } },
unpaid: {
$sum: {
$cond: [{ $and: [{ $eq: ['$approved', true] }, { $eq: ['$payment_status', payments.pending] }] }, 1, 0]
Expand Down
2 changes: 2 additions & 0 deletions src/repository/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ export const findOne = async (filters, returnPassword = false) => {
return user;
};

export const findByEmail = (email) => User.findOne({ email }).lean();

export const findOneAndUpdate = async (filters, data) => {
const user = await User.findOneAndUpdate(filters, dot(data), { new: true }).lean();
if (!user) return null;
Expand Down
32 changes: 31 additions & 1 deletion src/routes/event.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,26 @@ import { tracedAsyncHandler } from '@sliit-foss/functions';
import { Segments, celebrate } from 'celebrate';
import {
approveUserTicket,
cancelTicketPayment,
createEvent,
deleteEvent,
getAllEvents,
getEventById,
getUserEventTicket,
initiateTicketPayment,
requestEventTicket,
updateEvent
transferTicket,
updateEvent,
verifyTicketPayment
} from '@/controllers/event';
import { adminProtect, identify, protect } from '@/middleware/auth';
import {
addEventSchema,
eventIdSchema,
eventTicketIdSchema,
initiateTicketPaymentSchema,
requestTicketSchema,
transferTicketSchema,
updateEventSchema
} from '@/validations/event';

Expand Down Expand Up @@ -64,5 +70,29 @@ events.patch(
celebrate({ [Segments.PARAMS]: eventTicketIdSchema }),
tracedAsyncHandler(approveUserTicket)
);
events.post(
'/:event_id/tickets/:ticket_id/payment',
protect,
celebrate({ [Segments.PARAMS]: eventTicketIdSchema, [Segments.BODY]: initiateTicketPaymentSchema }),
tracedAsyncHandler(initiateTicketPayment)
);
events.get(
'/:event_id/tickets/:ticket_id/payment/verify',
protect,
celebrate({ [Segments.PARAMS]: eventTicketIdSchema }),
tracedAsyncHandler(verifyTicketPayment)
);
events.patch(
'/:event_id/tickets/:ticket_id/payment/cancel',
protect,
celebrate({ [Segments.PARAMS]: eventTicketIdSchema }),
tracedAsyncHandler(cancelTicketPayment)
);
events.patch(
'/:event_id/tickets/:ticket_id/transfer',
protect,
celebrate({ [Segments.PARAMS]: eventTicketIdSchema, [Segments.BODY]: transferTicketSchema }),
tracedAsyncHandler(transferTicket)
);

export default events;
2 changes: 2 additions & 0 deletions src/routes/index.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import speakerRouter from './speaker.routes';
import storageRouter from './storage.routes';
import submissionRouter from './submission.routes';
import userRouter from './user.routes';
import webhookRouter from './webhook.routes';

const router = express.Router();

Expand All @@ -25,5 +26,6 @@ router.use('/leaderboard', leaderboardRouter);
router.use('/settings', protect, settingRouter);
router.use('/speakers', protect, adminProtect, speakerRouter);
router.use('/storage', protect, adminProtect, storageRouter);
router.use('/webhooks', webhookRouter);

export default router;
9 changes: 9 additions & 0 deletions src/routes/webhook.routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import express from 'express';
import { tracedAsyncHandler } from '@sliit-foss/functions';
import { handlePayment } from '@/controllers/webhook';

const webhooks = express.Router();

webhooks.post('/payments', tracedAsyncHandler(handlePayment));

export default webhooks;
Loading

0 comments on commit 282ccd9

Please sign in to comment.