Skip to content

Commit

Permalink
Merge pull request #103 from atlp-rwanda/ft-stripe-payment
Browse files Browse the repository at this point in the history
Stripe Payment system Integration
  • Loading branch information
faid-terence authored May 30, 2024
2 parents 5346a98 + f494e11 commit bb96156
Show file tree
Hide file tree
Showing 11 changed files with 105 additions and 9 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ env:
GOOGLE_CLIENT_ID: ${{secrets.GOOGLE_CLIENT_ID}}
GOOGLE_CLIENT_SECRET: ${{secrets.GOOGLE_CLIENT_SECRET}}

STRIPE_SECRET_KEY: ${{secrets.STRIPE_SECRET_KEYT}}

jobs:
build-lint-test-coverage:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"reflect-metadata": "^0.2.2",
"socket.io": "^4.7.5",
"source-map-support": "^0.5.21",
"stripe": "^15.8.0",
"superagent": "^9.0.1",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0",
Expand Down
40 changes: 37 additions & 3 deletions src/__test__/getProduct.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import { User, UserInterface } from '../entities/User';
import { v4 as uuid } from 'uuid';
import { Product } from '../entities/Product';
import { Category } from '../entities/Category';
import { Cart } from '../entities/Cart';

Check warning on line 10 in src/__test__/getProduct.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'Cart' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 10 in src/__test__/getProduct.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'Cart' is defined but never used

Check warning on line 10 in src/__test__/getProduct.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'Cart' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 10 in src/__test__/getProduct.test.ts

View workflow job for this annotation

GitHub Actions / build-lint-test-coverage

'Cart' is defined but never used
import { cleanDatabase } from './test-assets/DatabaseCleanup';

const vendor1Id = uuid();
const BuyerID = uuid();
const product1Id = uuid();
const Invalidproduct = '11278df2-d026-457a-9471-4749f038df68';
const catId = uuid();
Expand Down Expand Up @@ -37,6 +39,18 @@ const sampleVendor1: UserInterface = {
photoUrl: 'https://example.com/photo.jpg',
role: 'VENDOR',
};
const sampleBuyer1: UserInterface = {
id: BuyerID,
firstName: 'vendor1o',
lastName: 'user',
email: '[email protected]',
password: 'password',
userType: 'Vendor',
gender: 'Male',
phoneNumber: '000380996348',
photoUrl: 'https://example.com/photo.jpg',
role: 'BUYER',
};

const sampleCat = {
id: catId,
Expand All @@ -53,23 +67,23 @@ const sampleProduct1 = {
vendor: sampleVendor1,
categories: [sampleCat],
};

let cardID : string;
beforeAll(async () => {
const connection = await dbConnection();

const categoryRepository = connection?.getRepository(Category);
await categoryRepository?.save({ ...sampleCat });

const userRepository = connection?.getRepository(User);
await userRepository?.save({ ...sampleVendor1 });
await userRepository?.save({ ...sampleVendor1});
await userRepository?.save({ ...sampleBuyer1 });

const productRepository = connection?.getRepository(Product);
await productRepository?.save({ ...sampleProduct1 });
});

afterAll(async () => {
await cleanDatabase();

server.close();
});

Expand Down Expand Up @@ -122,3 +136,23 @@ describe('Get single product', () => {
expect(response.body.message).toBe('Product not found');
}, 10000);
});
describe('Cart Order and payment functionalities', () => {
it('should create a cart for a product', async () => {
const productId = product1Id;
const quantity = 8;

const token = getAccessToken(BuyerID, sampleBuyer1.email);

const response = await request(app)
.post('/cart')
.set('Authorization', `Bearer ${token}`)
.send({ productId, quantity });


expect(response.status).toBe(201);
expect(response.body.data.cart).toBeDefined();
cardID = JSON.stringify(response.body.data.cart.id)
});

}
)
6 changes: 5 additions & 1 deletion src/controllers/productController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
productStatusServices,
viewSingleProduct,
searchProductService,
listAllProductsService,
listAllProductsService,
confirmPayment,
} from '../services';

export const readProduct = async (req: Request, res: Response) => {
Expand Down Expand Up @@ -70,3 +71,6 @@ export const searchProduct = async (req: Request, res: Response) => {
res.status(500).json({ error: 'Internal Server Error' });
}
};
export const Payment = async (req: Request, res: Response) => {
await confirmPayment(req, res);
};
1 change: 1 addition & 0 deletions src/entities/User.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

import {
Entity,
PrimaryGeneratedColumn,
Expand Down
2 changes: 1 addition & 1 deletion src/entities/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@ export class Transaction {

@UpdateDateColumn()
updatedAt!: Date;
}
}
5 changes: 3 additions & 2 deletions src/routes/ProductRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RequestHandler, Router } from 'express';

import { productStatus, searchProduct } from '../controllers/index';
import { productStatus, searchProduct, } from '../controllers/index';
import { hasRole } from '../middlewares/roleCheck';
import upload from '../middlewares/multer';
import { authMiddleware } from '../middlewares/verifyToken';
Expand All @@ -18,7 +18,7 @@ import {
createOrder,
getOrders,
updateOrder,
getOrdersHistory,
getOrdersHistory,Payment,
getSingleVendorOrder,
getVendorOrders,
updateVendorOrder,
Expand Down Expand Up @@ -54,5 +54,6 @@ router.put('/vendor/orders/:id', authMiddleware as RequestHandler, hasRole('VEND
router.get('/admin/orders', authMiddleware as RequestHandler, hasRole('ADMIN'), getBuyerVendorOrders);
router.get('/admin/orders/:id', authMiddleware as RequestHandler, hasRole('ADMIN'), getSingleBuyerVendorOrder);
router.put('/admin/orders/:id', authMiddleware as RequestHandler, hasRole('ADMIN'), updateBuyerVendorOrder);
router.post('/payment/:id', authMiddleware as RequestHandler, hasRole('BUYER'), Payment)

export default router;
1 change: 1 addition & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export * from './productServices/listAllProductsService';
export * from './productServices/productStatus';
export * from './productServices/viewSingleProduct';
export * from './productServices/searchProduct';
export * from './productServices/payment'

// Buyer wishlist services
export * from './wishListServices/addProduct';
Expand Down
2 changes: 1 addition & 1 deletion src/services/orderServices/createOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,4 @@ const saveVendorRelatedOrder = async (order: Order, CartItem: CartItem[]) => {
} catch (error) {
console.log((error as Error).message);
}
};
};
2 changes: 1 addition & 1 deletion src/services/orderServices/updateOrderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const updateOrderService = async (req: Request, res: Response) => {
return sendSuccessResponse(res, 200, 'Order updated successfully', orderResponse);
});
} catch (error) {
console.error('Error updating order:', error);
console.error('Error updating order:', (error as Error).message);
return sendErrorResponse(res, 500, (error as Error).message);
}
};
Expand Down
52 changes: 52 additions & 0 deletions src/services/productServices/payment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Request, Response } from 'express';
import { Cart } from '../../entities/Cart'; // Import your Cart entity
import { Order } from '../../entities/Order'; // Import your Order entity
import { getRepository, getTreeRepository } from 'typeorm';
import dotenv from 'dotenv';
import Stripe from 'stripe';
dotenv.config();
const stripeInstance = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
apiVersion: "2024-04-10",
});

export const confirmPayment = async (req: Request, res: Response) => {
try {
const { payment_method } = req.body;
const cartId = req.params.cartId; // Get the cart ID from the params

const cartRepository = getRepository(Cart);
const orderRepository = getTreeRepository(Order)
const cart = await cartRepository.findOne({where: {id : cartId}});
if (!cart) {
return res.status(404).json({ error: 'Cart not found.' });
}
const order = await orderRepository.findOne({ where: { buyer: cart.user } });
if (!order) {
return res.status(404).json({ error: 'order not found.' });
}

const paymentIntent = await stripeInstance.paymentIntents.create({
amount: cart.totalAmount, // Convert total to cents
currency: 'usd',
description: `Order #${cartId}`,
return_url: 'https://frontend-website.com/success',
confirm: true,
payment_method,
});

order.orderStatus = 'awaiting shipment';
await orderRepository.save(order);


if (paymentIntent.status === 'succeeded') {
// Payment succeeded
res.status(200).json({ message: 'Payment successful!' });
} else {
// Payment failed
res.status(400).json({ error: 'Payment failed.' });
}
} catch (error) {
console.error('Error confirming payment:', error);
res.status(500).json({ error: 'Something went wrong' });
}
};

0 comments on commit bb96156

Please sign in to comment.