diff --git a/src/__test__/payment.test.ts b/src/__test__/payment.test.ts new file mode 100644 index 00000000..31c59875 --- /dev/null +++ b/src/__test__/payment.test.ts @@ -0,0 +1,101 @@ +import request from 'supertest'; +import app from '../app'; // Adjust the import based on your project structure +import { getBuyerToken } from './testSetup'; // Adjust the import based on your project structure +import { Order } from '../database/models/orderEntity'; +import dbConnection from '../database'; +import Stripe from 'stripe'; + + +jest.mock('stripe'); +const MockedStripe = Stripe as jest.Mocked; + + +describe('handlePayment', () => { + let token: string; + let order: Order; + + + beforeAll(async () => { + await dbConnection.initialize(); + await dbConnection.synchronize(true); // This will drop all tables + 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(); + }); + + + it('should process payment successfully', async () => { + const mockChargesCreate = jest.fn().mockResolvedValue({ + id: 'charge_id', + amount: 10000, + currency: 'usd', + } as Stripe.Charge); + + + MockedStripe.prototype.charges = { + create: mockChargesCreate, + } as unknown as Stripe.ChargesResource; + + + const response = await request(app) + .post('/api/v1/buyer/payment') + .set('Authorization', `Bearer ${token}`) + .send({ token: 'fake-token', orderId: order.id }); + + + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + expect(response.body.paid).toBe(true); + expect(response.body.charge.id).toBe('charge_id'); + expect(mockChargesCreate).toHaveBeenCalledWith({ + amount: 10000, + currency: 'usd', + description: 'Test Charge', + source: 'fake-token', + }); + }); + + + it('should return 404 if order not found', async () => { + const response = await request(app) + .post('/api/v1/buyer/payment') + .set('Authorization', `Bearer ${token}`) + .send({ token: 'fake-token', orderId: 999 }); + + + 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 () => { + // Set the order as paid + const orderRepository = dbConnection.getRepository(Order); + order.paid = true; + await orderRepository.save(order); + + + const response = await request(app) + .post('/api/v1/buyer/payment') + .set('Authorization', `Bearer ${token}`) + .send({ token: 'fake-token', orderId: order.id }); + + + expect(response.status).toBe(400); + expect(response.body.success).toBe(false); + expect(response.body.message).toBe('Order has already been paid'); + }); +}); diff --git a/src/controller/buyerController.ts b/src/controller/buyerController.ts index 839970a5..f47a90c0 100644 --- a/src/controller/buyerController.ts +++ b/src/controller/buyerController.ts @@ -2,24 +2,74 @@ import { Request, Response } from 'express'; import dbConnection from '../database'; import Product from '../database/models/productEntity'; import errorHandler from '../middlewares/errorHandler'; +import Stripe from 'stripe'; +import { Order } from '../database/models/orderEntity'; + +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', { apiVersion: '2024-04-10' }); const productRepository = dbConnection.getRepository(Product); +const orderRepository = dbConnection.getRepository(Order); + + export const getOneProduct = errorHandler( async (req: Request, res: Response) => { const productId = parseInt(req.params.id); - + + const product = await productRepository.findOne({ where: { id: productId }, relations: ['category'], }); - + + if (!product) { return res.status(404).json({ msg: 'Product not found' }); } - + + return res .status(200) .json({ msg: 'Product retrieved successfully', product }); } -); + ); + + + + + export const handlePayment = errorHandler( + async (req: Request, res: Response) => { + const { token, orderId } = req.body; + + + 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 amountInCents = order.totalAmount * 100; + + + const charge = await stripe.charges.create({ + amount: amountInCents, + currency: 'usd', + description: 'Test Charge', + source: token, + }); + + + order.paid = true; + await orderRepository.save(order); + + return res.status(200).json({ success: true, paid: true, charge}); + } + ); + \ No newline at end of file diff --git a/src/database/models/orderEntity.ts b/src/database/models/orderEntity.ts index 5ab8fe8d..bd2962a7 100644 --- a/src/database/models/orderEntity.ts +++ b/src/database/models/orderEntity.ts @@ -41,4 +41,8 @@ export class Order { @OneToMany(() => OrderDetails, orderDetails => orderDetails.order, { cascade: true }) orderDetails: OrderDetails[]; + + @Column({ type: 'boolean', default: false, nullable: true }) + paid: boolean | null; + } diff --git a/src/docs/buyerDocs.ts b/src/docs/buyerDocs.ts index 649d4f75..3c8aa4b2 100644 --- a/src/docs/buyerDocs.ts +++ b/src/docs/buyerDocs.ts @@ -20,3 +20,53 @@ * '500': * description: Internal Server Error */ + +/** +* @swagger +* /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: +* '200': +* 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 +*/ + + diff --git a/src/middlewares/errorHandler.ts b/src/middlewares/errorHandler.ts index 5472164e..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 fb54a29e..4159a39f 100644 --- a/src/routes/buyerRoutes.ts +++ b/src/routes/buyerRoutes.ts @@ -1,13 +1,18 @@ import { Router } from 'express'; import { checkRole } from '../middlewares/authorize'; import { getOneProduct } from '../controller/buyerController'; - import { IsLoggedIn } from '../middlewares/isLoggedIn'; +import { handlePayment } from '../controller/buyerController'; const buyerRouter = Router(); buyerRouter.use(IsLoggedIn, checkRole(['Buyer'])); + buyerRouter.get('/get_product/:id', getOneProduct); + +buyerRouter.post('/payment', handlePayment); + + export default buyerRouter;