Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A buyer is now to pay via stripe #106

Merged
merged 1 commit into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions src/__test__/payment.test.ts
Original file line number Diff line number Diff line change
@@ -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<typeof Stripe>;


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');
});
});
58 changes: 54 additions & 4 deletions src/controller/buyerController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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});
}
);

4 changes: 4 additions & 0 deletions src/database/models/orderEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

}
50 changes: 50 additions & 0 deletions src/docs/buyerDocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/


1 change: 0 additions & 1 deletion src/middlewares/errorHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 6 additions & 1 deletion src/routes/buyerRoutes.ts
Original file line number Diff line number Diff line change
@@ -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;
Loading