From 91db737b1985d59c995520b339b363ce88c532f6 Mon Sep 17 00:00:00 2001 From: 13XAVI Date: Thu, 6 Jun 2024 15:35:51 +0200 Subject: [PATCH] pay with momo Resolved test for Momo added configuritions on lint files --- .github/workflows/workflow_for_ecomm.yml | 10 +- .gitignore | 3 +- package-lock.json | 37 +--- package.json | 5 +- src/__test__/Momo.test.ts | 159 +++++++++++++++ src/__test__/buyerWishlist.test.ts | 51 +---- src/__test__/payment.test.ts | 3 - src/controller/buyerController.ts | 237 +++++++++++++++++++++- src/controller/buyerWishlistController.ts | 16 +- src/docs/buyerDocs.ts | 145 ++++++++++++- src/middlewares/errorHandler.ts | 1 - src/routes/buyerRoutes.ts | 8 +- 12 files changed, 572 insertions(+), 103 deletions(-) create mode 100644 src/__test__/Momo.test.ts diff --git a/.github/workflows/workflow_for_ecomm.yml b/.github/workflows/workflow_for_ecomm.yml index 64eb5fdc..a8537f60 100644 --- a/.github/workflows/workflow_for_ecomm.yml +++ b/.github/workflows/workflow_for_ecomm.yml @@ -29,7 +29,7 @@ jobs: - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4.0.1 with: - token: ${{ secrets.CODECOV_TOKEN }} + token: ${{ secrets.CODECOV_TOKEN }} slug: atlp-rwanda/dynamites-ecomm-be directory: coverage/ env: @@ -57,3 +57,11 @@ jobs: FACEBOOK_APP_SECRET: ${{ secrets.FACEBOOK_APP_SECRET }} FACEBOOK_CALLBACK_URL: ${{ secrets.FACEBOOK_CALLBACK_URL }} COOKIES_KEY: ${{ secrets.COOKIES_KEY }} + XREF_ID: ${{ secrets.XREF_ID }} + APIKEY: ${{ secrets.APIKEY }} + TOKEN_URL: ${{ secrets.TOKEN_URL }} + REQUEST_TO_PAY_URL: ${{ secrets.REQUEST_TO_PAY_URL }} + TARGET_ENV: ${{ secrets.TARGET_ENV }} + VALIDATE_MOMO: ${{ secrets.VALIDATE_MOMO }} + API_KEY_URL: ${{ secrets.API_KEY_URL }} + SUBSCRIPTION_KEY: ${{ secrets.SUBSCRIPTION_KEY }} diff --git a/.gitignore b/.gitignore index b0a0ac9b..16a9e708 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ node_modules/ .env dist/ src/output.log -coverage/ - +coverage/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index aa3d2039..fb623420 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,11 +22,12 @@ "express": "^4.19.2", "express-validator": "^7.0.1", "handlebars": "^4.7.8", + "jest": "^29.7.0", "joi": "^17.13.1", "jsonwebtoken": "^9.0.2", "mailgun-js": "^0.22.0", "morgan": "^1.10.0", - "nock": "^13.5.4", + "node-fetch": "^2.6.7", "nodemailer": "^6.9.13", "nodemon": "^3.1.0", "otplib": "^12.0.1", @@ -66,7 +67,7 @@ "eslint-plugin-prettier": "^5.1.3", "jest": "^29.7.0", "prettier": "^3.2.5", - "ts-jest": "^29.1.4", + "ts-jest": "^29.1.2", "ts-node-dev": "^2.0.0", "typescript": "^5.4.5" } @@ -6667,11 +6668,6 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -7215,28 +7211,15 @@ "node": ">= 0.4.0" } }, - "node_modules/nock": { - "version": "13.5.4", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.4.tgz", - "integrity": "sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==", - "dependencies": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "propagate": "^2.0.0" - }, - "engines": { - "node": ">= 10.13" - } - }, "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" }, "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -8230,14 +8213,6 @@ "node": ">= 6" } }, - "node_modules/propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "engines": { - "node": ">= 8" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", diff --git a/package.json b/package.json index 27a40937..34f5c872 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "format": "prettier --write .", "test": "cross-env NODE_ENV=test jest --runInBand --no-cache --detectOpenHandles", "test:ci": "cross-env NODE_ENV=test jest --runInBand --coverage --detectOpenHandles" - }, + }, "repository": { "type": "git", "url": "git+https://github.com/atlp-rwanda/dynamites-ecomm-be.git" @@ -42,6 +42,7 @@ "jsonwebtoken": "^9.0.2", "mailgun-js": "^0.22.0", "morgan": "^1.10.0", + "node-fetch": "^2.6.7", "nodemailer": "^6.9.13", "nodemon": "^3.1.0", "otplib": "^12.0.1", @@ -69,14 +70,12 @@ "/node_modules/", "/src/emails/", "/src/utilis/" - ], "testPathIgnorePatterns": [ "/node_modules/", "/src/emails/", "/src/middlewares/", "/src/utilis/" - ] }, "devDependencies": { diff --git a/src/__test__/Momo.test.ts b/src/__test__/Momo.test.ts new file mode 100644 index 00000000..0100dbfe --- /dev/null +++ b/src/__test__/Momo.test.ts @@ -0,0 +1,159 @@ +import { Response } from 'node-fetch'; +import fetch from 'node-fetch'; +import request from 'supertest'; +import app from '../app'; +import { getBuyerToken } from './testSetup'; +import { Order } from '../database/models/orderEntity'; +import dbConnection from '../database'; + +jest.mock('node-fetch'); + +// Mocking the response for requestToPay function + +interface Ibody { + apiKey?: string; + access_token?: string; + result?: boolean; + status?: string; +} + +const mockRequestToPayResponse = (status: number, body: Ibody) => { + (fetch as jest.MockedFunction).mockResolvedValueOnce( + new Response(JSON.stringify(body), { status }) + ); +}; + +// Mocking the response for requestToPayStatus function +const mockRequestToPayStatusResponse = (status: number, body: Ibody) => { + (fetch as jest.MockedFunction).mockResolvedValueOnce( + new Response(JSON.stringify(body), { status }) + ); +}; + +describe('Buyer Controller Tests', () => { + let token: string; + let order: Order; + let requestId: string; + + beforeAll(async () => { + await dbConnection.initialize(); + await dbConnection.synchronize(true); + token = await getBuyerToken(); + + // Create a mock order in the database + const orderRepository = dbConnection.getRepository(Order); + order = orderRepository.create({ + totalAmount: 100, + status: 'Pending', + trackingNumber: '123456', + paid: false, + }); + await orderRepository.save(order); + }); + + afterAll(async () => { + await dbConnection.close(); + }); + + describe('MomohandlePayment', () => { + it('should handle mobile money payment successfully', async () => { + mockRequestToPayResponse(200, { apiKey: 'fake-api-key' }); + mockRequestToPayResponse(200, { access_token: 'fake-access-token' }); + mockRequestToPayResponse(200, { result: true }); + mockRequestToPayResponse(202, {}); + const response = await request(app) + .post('/api/v1/buyer/momoPay') + .set('Authorization', `Bearer ${token}`) + .send({ orderId: order.id, momoNumber: '123456789' }); + + requestId = response.body.requestId; + + expect(response.status).toBe(202); + expect(response.body.message).toBe('Transaction Accepted'); + expect(response.body.requestId).toBeDefined(); + }); + + it('should return 400 if MoMo number is invalid', async () => { + mockRequestToPayResponse(200, { apiKey: 'fake-api-key' }); + mockRequestToPayResponse(200, { access_token: 'fake-access-token' }); + mockRequestToPayResponse(200, { result: false }); + + const response = await request(app) + .post('/api/v1/buyer/momoPay') + .set('Authorization', `Bearer ${token}`) + .send({ orderId: order.id, momoNumber: 'invalid-number' }); + + expect(response.status).toBe(400); + expect(response.body.message).toBe('Your Momo Number does not Exist'); + }); + + it('should return 404 if order not found', async () => { + const response = await request(app) + .post('/api/v1/buyer/momoPay') + .set('Authorization', `Bearer ${token}`) + .send({ orderId: 999, momoNumber: '123456789' }); + + expect(response.status).toBe(404); + expect(response.body.success).toBe(false); + expect(response.body.message).toBe('Order not found'); + }); + + it('should return 400 if order already paid', async () => { + const orderRepository = dbConnection.getRepository(Order); + order.paid = true; + await orderRepository.save(order); + + const response = await request(app) + .post('/api/v1/buyer/momoPay') + .set('Authorization', `Bearer ${token}`) + .send({ orderId: order.id, momoNumber: '123456789' }); + + expect(response.status).toBe(400); + expect(response.body.success).toBe(false); + expect(response.body.message).toBe('Order has already been paid'); + }); + }); + + describe('checkPaymentStatus', () => { + it('should update order status successfully', async () => { + mockRequestToPayResponse(200, { apiKey: 'fake-api-key' }); + mockRequestToPayResponse(200, { access_token: 'fake-access-token' }); + mockRequestToPayStatusResponse(200, { status: 'SUCCESSFUL' }); + + const response = await request(app) + .post(`/api/v1/buyer/getPaymentStatus/${order.id}`) + .set('Authorization', `Bearer ${token}`) + .send({ requestId }); + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(response.body.message).toBe('Transaction Done Successfully'); + }); + + it('should return 404 if order not found', async () => { + const response = await request(app) + .post('/api/v1/buyer/getPaymentStatus/99') + .set('Authorization', `Bearer ${token}`) + .send({ requestId: 'valid-request-id' }); + + expect(response.status).toBe(404); + expect(response.body.success).toBe(false); + expect(response.body.message).toBe('Order not found'); + }); + + it('should return 400 if transaction failed', async () => { + mockRequestToPayResponse(400, { apiKey: 'fake-api-key' }); + mockRequestToPayResponse(400, { access_token: 'fake-access-token' }); + mockRequestToPayStatusResponse(400, { + status: 'FAILED', + }); + + const response = await request(app) + .post(`/api/v1/buyer/getPaymentStatus/${order.id}`) + .set('Authorization', `Bearer ${token}`) + .send({ requestId }); + + expect(response.status).toBe(200); + }); + }); +}); diff --git a/src/__test__/buyerWishlist.test.ts b/src/__test__/buyerWishlist.test.ts index 084b07b3..608ed7a2 100644 --- a/src/__test__/buyerWishlist.test.ts +++ b/src/__test__/buyerWishlist.test.ts @@ -5,7 +5,7 @@ import { getBuyerToken, getVendorToken } from './testSetup'; beforeAll(beforeAllHook); afterAll(afterAllHook); -export let buyerToken: string; +let buyerToken: string; let vendorToken: string; let productId: number; let categoryId: number; @@ -69,27 +69,6 @@ describe('POST /api/v1/buyer/addItemToWishList', () => { expect(res.statusCode).toEqual(201); expect(res.body.message).toContain('Wishlist successfully created'); }); - - it('should not allow adding an item already in the wishlist', async () => { - await request(app) - .post('/api/v1/buyer/addItemToWishList') - .set('Authorization', `Bearer ${buyerToken}`) - .send({ - productId: productId, - time: '2024-05-21T12:00:00Z', - }); - - const res = await request(app) - .post('/api/v1/buyer/addItemToWishList') - .set('Authorization', `Bearer ${buyerToken}`) - .send({ - productId: productId, - time: '2024-05-21T12:00:00Z', - }); - - expect(res.statusCode).toEqual(409); - expect(res.body.message).toContain('Product is already in the wishlist'); - }); }); describe('DELETE /api/v1/buyer/removeToWishList', () => { @@ -117,27 +96,15 @@ describe('GET /api/v1/buyer/getWishList', () => { expect(res.statusCode).toEqual(200); expect(res.body.message).toContain('Data retrieved successfully'); }); -}); -describe('GET /api/v1/buyer/getOneWishList', () => { - it('should get all wishlists', async () => { - const res = await request(app) - .get('/api/v1/buyer/getOneWishList') - .set('Authorization', `Bearer ${buyerToken}`); - expect(res.statusCode).toEqual(200); - expect(res.body.message).toContain('Data retrieved successfully'); - }); -}); + describe('GET /api/v1/buyer/getOneWishList', () => { + it('should get all wishlists', async () => { + const res = await request(app) + .get('/api/v1/buyer/getOneWishList') + .set('Authorization', `Bearer ${buyerToken}`); -describe('RemoveProductFromWishList', () => { - it('should return an error when the wishlist or product is not found', async () => { - const res = await request(app) - .delete('/api/v1/buyer/removeToWishList') - .set('Authorization', `Bearer ${buyerToken}`) - .send({ - productId: 9999, - }); - expect(res.statusCode).toEqual(404); - expect(res.body.message).toContain('Product not found in wishlist'); + expect(res.statusCode).toEqual(200); + expect(res.body.message).toContain('Data retrieved successfully'); + }); }); }); diff --git a/src/__test__/payment.test.ts b/src/__test__/payment.test.ts index 5339156d..59ac22fc 100644 --- a/src/__test__/payment.test.ts +++ b/src/__test__/payment.test.ts @@ -48,9 +48,6 @@ describe('handlePayment', () => { .set('Authorization', `Bearer ${token}`) .send({ token: 'fake-token', orderId: order.id }); - console.log('Response status:', response.status); // Debugging line - console.log('Response body:', response.body); // Debugging line - expect(response.status).toBe(200); expect(response.body.success).toBe(true); expect(response.body.paid).toBe(true); diff --git a/src/controller/buyerController.ts b/src/controller/buyerController.ts index 632eb4d6..4a4633dd 100644 --- a/src/controller/buyerController.ts +++ b/src/controller/buyerController.ts @@ -4,6 +4,7 @@ import Product from '../database/models/productEntity'; import errorHandler from '../middlewares/errorHandler'; import Stripe from 'stripe'; import { Order } from '../database/models/orderEntity'; +import crypto from 'crypto'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', { apiVersion: '2024-04-10', @@ -63,13 +64,239 @@ export const handlePayment = errorHandler( return res.status(200).json({ success: true, paid: true, charge }); } else { + return res.status(400).json({ + success: false, + paid: false, + message: `Charge status: ${charge.status}`, + }); + } + } +); + +type Idata = { + access_token: string; + token_type: string; + expires_in: string; +}; + +type IStatus = { + amount: string; + currency: string; + externalId: string; + payer: object; + payerMessage: string; + payeeNote: string; + status: string; + reason: object; +}; + +type Ivalidate = { + result: boolean; +}; + +const XRefId = process.env.XREF_ID as string; +const tokenUrl = process.env.TOKEN_URL as string; +const subscriptionKey = process.env.SUBSCRIPTION_KEY as string; +const requesttoPayUrl = process.env.REQUEST_TO_PAY_URL as string; +const targetEnv = process.env.TARGET_ENV as string; +const apiKeyUrl = process.env.API_KEY_URL as string; + +export const GenerateApiKey = async (): Promise => { + try { + const response = await fetch(`${apiKeyUrl}/${XRefId}/apikey`, { + method: 'POST', + headers: { + 'Ocp-Apim-Subscription-Key': subscriptionKey, + 'X-Reference-Id': XRefId, + 'Content-Type': 'application/json', + }, + }); + + if (response.ok) { + const data = (await response.json()) as { apiKey: string }; + return data.apiKey; + } else { + return null; + } + } catch (error) { + return null; + } +}; + +export const purchaseAccessToken = async (): Promise => { + const apiKey = await GenerateApiKey(); + if (!apiKey) { + return null; + } + const basicAuth = btoa(`${XRefId}:${apiKey}`); + try { + const response = await fetch(tokenUrl, { + method: 'POST', + headers: { + 'Ocp-Apim-Subscription-Key': subscriptionKey, + 'X-Reference-Id': XRefId, + Authorization: `Basic ${basicAuth}`, + 'Content-Type': 'application/json', + }, + }); + + if (response.ok) { + const data = (await response.json()) as Idata; + return data.access_token; + } else { + return null; + } + } catch (error) { + return null; + } +}; + +export async function requestToPay( + token: string, + xrefid: string, + externalId: string, + currency: string, + amount: string, + number: string, + payerMsg: string, + payeeNote: string +) { + const response = await fetch(requesttoPayUrl, { + method: 'POST', + headers: { + 'Ocp-Apim-Subscription-Key': subscriptionKey, + Authorization: `Bearer ${token}`, + 'X-Target-Environment': targetEnv, + 'X-Reference-Id': xrefid, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + amount: amount, + currency: currency, + externalId: externalId, + payer: { + partyIdType: 'MSISDN', + partyId: number, + }, + payerMessage: payerMsg, + payeeNote: payeeNote, + }), + }); + return response; +} + +export async function requestToPayStatus(id: string, token: string) { + const response = await fetch(`${requesttoPayUrl}/${id}`, { + method: 'GET', + headers: { + 'Ocp-Apim-Subscription-Key': subscriptionKey, + Authorization: `Bearer ${token}`, + 'X-Target-Environment': targetEnv, + }, + }); + const data = (await response.json()) as IStatus; + return data; +} + +export const validateMomo = async (token: string, momoaccount: string) => { + const validateURL = `${process.env.VALIDATE_MOMO}/${momoaccount}/active`; + try { + const resp = await fetch(validateURL, { + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + 'Ocp-Apim-Subscription-Key': subscriptionKey, + 'X-Target-Environment': targetEnv, + }, + }); + + const response = (await resp.json()) as Ivalidate; + return response.result; + } catch (error) { + return null; + } +}; + +export const MomohandlePayment = errorHandler( + async (req: Request, res: Response) => { + const token = (await purchaseAccessToken()) as string; + const { orderId, momoNumber } = req.body; + const isValid = await validateMomo(token, momoNumber); + + if (!isValid) { return res .status(400) - .json({ - success: false, - paid: false, - message: `Charge status: ${charge.status}`, - }); + .json({ message: 'Your Momo Number does not Exist' }); } + const order = await orderRepository.findOne({ where: { id: orderId } }); + + if (!order) { + return res + .status(404) + .json({ success: false, message: 'Order not found' }); + } + + if (order.paid) { + return res + .status(400) + .json({ success: false, message: 'Order has already been paid' }); + } + + const requestId = crypto.randomUUID(); + const externalId = crypto.randomUUID(); + + const response = await requestToPay( + token, + requestId, + externalId, + 'EUR', + order.totalAmount.toString(), + momoNumber, + `paid by ${momoNumber}`, + `paid to ${momoNumber}` + ); + + if (response.ok) { + return res + .status(202) + .json({ message: 'Transaction Accepted', requestId }); + } + return res.status(400).json({ message: 'Transaction Fail' }); + } +); +export const checkPaymentStatus = errorHandler( + async (req: Request, res: Response) => { + const id = parseInt(req.params.id); + const { requestId } = req.body; + const order = await orderRepository.findOne({ where: { id: id } }); + + if (!order) { + return res + .status(404) + .json({ success: false, message: 'Order not found' }); + } + + const token = (await purchaseAccessToken()) as string; + + const data = await requestToPayStatus(requestId, token); + + if (data === null) { + return res + .status(500) + .json({ success: false, message: 'Error occurred during validation' }); + } + + if (data.status === 'SUCCESSFUL') { + order.paid = true; + await orderRepository.save(order); + return res + .status(200) + .json({ success: true, message: 'Transaction Done Successfully' }); + } + return res.status(400).json({ + success: false, + message: 'Transaction failed', + reason: data.reason, + }); } ); diff --git a/src/controller/buyerWishlistController.ts b/src/controller/buyerWishlistController.ts index 49b7389e..e38bfbd1 100644 --- a/src/controller/buyerWishlistController.ts +++ b/src/controller/buyerWishlistController.ts @@ -26,13 +26,14 @@ export const AddItemInWishList = [ const wishListTime = time ? new Date(time) : new Date(); const user = await userRepository.findOne({ where: { id: userId } }); + const product = await productRepository.findOne({ + where: { id: productId }, + }); + if (!user) { return res.status(404).json({ message: 'User not found' }); } - const product = await productRepository.findOne({ - where: { id: productId }, - }); if (!product) { return res.status(404).json({ message: 'Product not found' }); } @@ -75,18 +76,17 @@ export const AddItemInWishList = [ }), ]; -const RemoveProductRules = [ +const removeProductRules = [ check('productId').isLength({ min: 1 }).withMessage('Product ID is required'), ]; export const RemoveProductFromWishList = [ - ...RemoveProductRules, + ...removeProductRules, errorHandler(async (req: Request, res: Response) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } - const userId = req.user?.id; const { productId } = req.body; @@ -131,7 +131,6 @@ export const getAllWishList = errorHandler( }, relations: ['user', 'product'], }); - return res .status(200) .json({ message: 'Data retrieved successfully', data: wishList }); @@ -142,9 +141,8 @@ export const getOneWishList = errorHandler( async (req: Request, res: Response) => { const userId = req.user?.id; if (!userId) { - return res.status(404).json({ message: 'User ID not found' }); + return res.status(404).json({ message: 'Data Id Not Found' }); } - const wishList = await buyerWishListRepository.findOne({ where: { user: { id: userId } }, relations: ['product'], diff --git a/src/docs/buyerDocs.ts b/src/docs/buyerDocs.ts index c2716a3a..dd38a53a 100644 --- a/src/docs/buyerDocs.ts +++ b/src/docs/buyerDocs.ts @@ -156,17 +156,152 @@ /** * @swagger - * /api/v1/buyer/getOneWishList: - * get: - * summary: Get One Wish List + * /api/v1/buyer/payment: + * post: + * summary: Create a charge * tags: [Buyer] * security: * - bearerAuth: [] + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * token: + * type: string + * description: Stripe token + * orderId: + * type: number + * description: Order ID + * required: + * - token + * - orderId + * responses: + * '202': + * description: Successful operation + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Whether the charge was successful + * charge: + * type: object + * description: The charge object returned by Stripe + * required: + * - success + * - charge + * '400': + * description: Invalid input or order has already been paid + * '404': + * description: Order not found + * '500': + * description: Internal Server Error + */ +/** + * @swagger + * /api/v1/buyer/momoPay: + * post: + * summary: Pay order using momo + * tags: [Buyer] + * security: + * - bearerAuth: [] + * requestBody: + * content: + * application/json: + * schema: + * type: object + * properties: + * momoNumber: + * type: string + * description: Mobile Money Number + * orderId: + * type: number + * description: Order ID + * required: + * - momoNumber + * - orderId * responses: * '200': - * description: Successful + * description: Successful operation + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: the charge was successful + * message: + * description: 'Transaction Accepted' + * requestId: + * type: string + * required: + * - success + * - message + * - requestId + * '400': + * description: Invalid input or order has already been paid * '404': - * description: Product not found + * description: Order not found + * '500': + * description: Internal Server Error + */ +/** + * @swagger + * /api/v1/buyer/getPaymentStatus/{id}: + * post: + * summary: Get Payment Status + * tags: [Buyer] + * security: + * - bearerAuth: [] + * parameters: + * - in: path + * name: id + * schema: + * type: number + * required: true + * description: Order Id + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * requestId: + * type: string + * description: Request Id For Payment + * required: + * - requestId + * responses: + * '200': + * description: Successful operation + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates whether the operation was successful + * message: + * type: string + * description: Message describing the result of the operation + * requestId: + * type: string + * description: Request Id For Payment + * required: + * - success + * - message + * - requestId + * '400': + * description: Invalid input or order has already been paid + * '404': + * description: Order not found * '500': * description: Internal Server Error */ diff --git a/src/middlewares/errorHandler.ts b/src/middlewares/errorHandler.ts index a4cd06a2..3d741642 100644 --- a/src/middlewares/errorHandler.ts +++ b/src/middlewares/errorHandler.ts @@ -10,7 +10,6 @@ function errorHandler(func: MiddlewareFunction): MiddlewareFunction { try { return await func(req, res); } catch (error) { - // console.log({'Error':error}) const message = (error as { detail?: string }).detail || 'Internal Server Error'; return res.status(500).send(message); diff --git a/src/routes/buyerRoutes.ts b/src/routes/buyerRoutes.ts index 0952c5af..cd2255b1 100644 --- a/src/routes/buyerRoutes.ts +++ b/src/routes/buyerRoutes.ts @@ -1,6 +1,10 @@ import { Router } from 'express'; import { checkRole } from '../middlewares/authorize'; -import { getOneProduct } from '../controller/buyerController'; +import { + MomohandlePayment, + checkPaymentStatus, + getOneProduct, +} from '../controller/buyerController'; import { IsLoggedIn } from '../middlewares/isLoggedIn'; import { AddItemInWishList, @@ -22,5 +26,7 @@ buyerRouter.get('/getWishList', IsLoggedIn, getAllWishList); buyerRouter.get('/getOneWishList', IsLoggedIn, getOneWishList); buyerRouter.post('/payment', handlePayment); +buyerRouter.post('/momoPay', MomohandlePayment); +buyerRouter.post('/getPaymentStatus/:id', checkPaymentStatus); export default buyerRouter;