diff --git a/.env.example b/.env.example index b88b7020..d28c3748 100644 --- a/.env.example +++ b/.env.example @@ -59,3 +59,5 @@ status = "" ADMIN_ROLE="" gender="" +STRIPE_SECRET_KEY="" +STRIPE_WEBHOOK_SECRET="" \ No newline at end of file diff --git a/package.json b/package.json index 80420789..f6bbde56 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "migrate": "npx sequelize-cli db:migrate", "migrate:undo": "npx sequelize-cli db:migrate:undo", "migrate:undo:all": "npx sequelize-cli db:migrate:undo:all", - "migrate:undo:all": "npx sequelize-cli db:migrate:undo:all", "seed": "npx sequelize-cli db:seed:all", "seed:undo": "npx sequelize-cli db:seed:undo", "seed:undo:all": "npx sequelize-cli db:seed:undo:all" @@ -50,6 +49,7 @@ "randomstring": "^1.3.0", "sequelize": "^6.37.3", "socket.io": "^4.7.5", + "stripe": "^15.7.0", "swagger-ui-express": "^5.0.0", "uuid": "^9.0.1", "winston": "^3.13.0", diff --git a/public/cancel.html b/public/cancel.html new file mode 100644 index 00000000..5dcb1b97 --- /dev/null +++ b/public/cancel.html @@ -0,0 +1,11 @@ + + + + + + Document + + +

Cancel

+ + diff --git a/public/index.html b/public/index.html index 8c607338..d5deb612 100644 --- a/public/index.html +++ b/public/index.html @@ -1,456 +1,453 @@ - - + Socket.IO chat - + - +
-
- -
-

-
+
+ +
+

+
-
- - -
-
- -
-
-
-
-
- -
- -
- -
-
+
+ +
+
+ +
+
+
+
+
+ +
+ +
+ +
+
- - - \ No newline at end of file + + diff --git a/public/order.html b/public/order.html new file mode 100644 index 00000000..0e83a419 --- /dev/null +++ b/public/order.html @@ -0,0 +1,353 @@ + + + + + + Order Status + + + +
+

Login

+ + + +
+ +
+

Order Status

+ + + + + + + + + + +
SelectOrder IDStatusActions
+
+ +
+

+ + +
+ +
+

+ +
+ + + + diff --git a/public/script.js b/public/script.js new file mode 100644 index 00000000..20f0d77b --- /dev/null +++ b/public/script.js @@ -0,0 +1,25 @@ +const checkoutButton = document.getElementById('checkout-button'); + +checkoutButton.addEventListener('click', () => { + fetch('api/payments/charge', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + items: [ + { id: 'f0b655c0-6ad5-47f0-8d71-60929c656508', quantity: 3 }, + { id: 'a1a8403f-0351-401c-9c20-9cf0fdb4f8e5', quantity: 1 }, + ], + }), + }) + .then(res => { + if (res.ok) return res.json(); + }) + .then(({ url }) => { + window.location = url; + }) + .catch(err => { + console.log(err.message); + }); +}); diff --git a/public/seller.html b/public/seller.html new file mode 100644 index 00000000..fa5cae33 --- /dev/null +++ b/public/seller.html @@ -0,0 +1,294 @@ + + + + + + Seller Order Management + + + +
+

Login

+ + + +
+ +
+

Seller Order Management

+ + + + + + + + + + +
SelectProductStatusActions
+
+ +
+

+ + + + + + +
+ + + + diff --git a/public/stripe.html b/public/stripe.html new file mode 100644 index 00000000..14277d34 --- /dev/null +++ b/public/stripe.html @@ -0,0 +1,12 @@ + + + + + + Document + + + + + + diff --git a/public/success.html b/public/success.html new file mode 100644 index 00000000..136dab91 --- /dev/null +++ b/public/success.html @@ -0,0 +1,11 @@ + + + + + + Document + + +

Success

+ + diff --git a/src/chatSetup.ts b/src/chatSetup.ts index 9fb666d4..ed13eda7 100644 --- a/src/chatSetup.ts +++ b/src/chatSetup.ts @@ -66,7 +66,13 @@ const disconnected = () => { }; export const socketSetUp = (server: HttpServer) => { - const io = new Server(server); + const io = new Server(server, { + cors: { + origin: '*', + methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], + allowedHeaders: ['Content-Type', 'Authorization'], + }, + }); // eslint-disable-next-line @typescript-eslint/no-misused-promises io.use(async (socket: CustomSocket, next) => { const id = findId(socket); @@ -78,6 +84,10 @@ export const socketSetUp = (server: HttpServer) => { io.emit('welcome', await getUserNames(socket.userId as string)); socket.on('sentMessage', data => sentMessage(socket, data, io)); socket.on('typing', isTyping => handleTyping(socket, isTyping)); + socket.on('changeOrderStatus', async statusData => { + io.emit('orderStatusChanged', { order: statusData }); + }); socket.on('disconnect', disconnected); }); + return io; }; diff --git a/src/controllers/handlePaymentsController.ts b/src/controllers/handlePaymentsController.ts new file mode 100644 index 00000000..25bb0ee4 --- /dev/null +++ b/src/controllers/handlePaymentsController.ts @@ -0,0 +1,138 @@ +import { Request, Response } from 'express'; +import Stripe from 'stripe'; +import { config } from 'dotenv'; +import { Product } from '../database/models/Product'; +import { Size } from '../database/models/Size'; +import Order from '../database/models/order'; +import { sendInternalErrorResponse } from '../validations'; + +config(); +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string); + +type Items = { + id: string; + sizes: Size[]; +}[]; + +const fetchProducts = async (items: Items) => { + const products = await Promise.all( + items.map(async item => { + return await Product.findAll({ + where: { id: item.id }, + include: { + model: Size, + as: 'sizes', + attributes: ['size', 'price', 'quantity', 'id'], + where: { + id: item.sizes.map(size => size.id), + }, + }, + }); + }) + ); + return products.flat(); +}; + +let productSize: Size; +export const handlePayments = async (req: Request, res: Response) => { + try { + const { items } = req.body as { items: Items }; + const { orderId } = req.params; + + const products = await fetchProducts(items); + + const lineItems = []; + for (const item of items) { + const product = products.find(p => p.id === item.id); + if (product) { + for (const size of item.sizes) { + productSize = product.sizes.find((s: { id: string }) => s.id === size.id.toString()); + lineItems.push({ + price_data: { + currency: 'usd', + product_data: { + name: `${product.name} (Size: ${productSize.size})`, + }, + unit_amount: productSize.price * 100, + }, + quantity: size.quantity, + }); + } + } + } + + const session = await stripe.checkout.sessions.create({ + payment_method_types: ['card'], + mode: 'payment', + success_url: `${process.env.URL_HOST}/success.html`, + line_items: lineItems, + cancel_url: `${process.env.URL_HOST}/cancel.html`, + metadata: { + orderId, + items: JSON.stringify(items), + }, + }); + + const url = session.url; + res.status(200).json({ ok: true, url }); + } catch (err) { + sendInternalErrorResponse(res, err); + } +}; + +const updateStockLevels = async (items: Items) => { + const products = await fetchProducts(items); + for (const item of items) { + const product = products.find(p => p.id === item.id); + if (product) { + for (const size of item.sizes) { + const newQuantity = productSize.quantity - size.quantity; + await Size.update({ quantity: newQuantity, updatedAt: new Date() }, { where: { id: productSize.id } }); + if (newQuantity <= 0) { + await Size.update({ available: false }, { where: { id: productSize.id } }); + } + } + } + } +}; + +declare module 'express-serve-static-core' { + interface Request { + rawBody?: Buffer | string; + } +} + +export const handleWebHooks = async (req: Request, res: Response) => { + const sig = req.headers['stripe-signature'] as string; + let event; + + try { + event = stripe.webhooks.constructEvent(req.rawBody!, sig, process.env.STRIPE_WEBHOOK_SECRET!); + } catch (err) { + return res.status(400).send(`Webhook Error: ${err}`); + } + + if (event.type === 'checkout.session.completed') { + const session = event.data.object as Stripe.Checkout.Session; + const orderId = session.metadata?.orderId; + const items = session.metadata?.items; + + if (!orderId) { + return res.status(400).send('No order ID found in session metadata'); + } + try { + // update status of order + const updatedOrder = await Order.update({ status: 'paid' }, { where: { id: orderId } }); + if (updatedOrder[0] === 0) { + return res.status(404).send('Order not found or not updated'); + } + // update stock levels + await updateStockLevels(JSON.parse(items!)); + res.json({ received: true }); + } catch (err) { + return sendInternalErrorResponse(res, err); + } + } else { + res.json({ received: true }); + } +}; diff --git a/src/controllers/orderController.ts b/src/controllers/orderController.ts index 74026b57..7ad5a691 100644 --- a/src/controllers/orderController.ts +++ b/src/controllers/orderController.ts @@ -1,14 +1,15 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ import logger from '../logs/config'; import { Request, Response } from 'express'; import Order from '../database/models/order'; import { sendInternalErrorResponse, validateFields } from '../validations'; import sequelize from '../database/models/index'; -import { Transaction } from 'sequelize'; import User from '../database/models/user'; import { Product } from '../database/models/Product'; import { Size } from '../database/models/Size'; import Cart from '../database/models/cart'; import OrderItems from '../database/models/orderItems'; +import { sendErrorResponse } from '../helpers/helper'; export const createOrder = async (req: Request, res: Response): Promise => { const { id: userId } = req.user as User; @@ -39,7 +40,7 @@ export const createOrder = async (req: Request, res: Response): Promise => } const [orderItems]: any = await sequelize.query( - 'SELECT "productId", quantity FROM "CartsProducts" WHERE "cartId" = ?', + 'SELECT "productId", "sizeId", quantity FROM "CartsProducts" WHERE "cartId" = ?', { replacements: [cart.id], } @@ -47,27 +48,32 @@ export const createOrder = async (req: Request, res: Response): Promise => const sizes = await Promise.all( orderItems.map(async (item: any) => { - const product = await Product.findOne({ - where: { id: item.productId }, - include: { - model: Size, - as: 'sizes', - attributes: ['price'], - }, + const productSize = await Size.findOne({ + where: { id: item.sizeId }, + attributes: ['id', 'price', 'discount'], }); - if (!product) { - throw new Error(`Product with id ${item.productId} not found`); + if (!productSize) { + throw new Error(`Size with id ${item.sizeId} not found`); } - const size = product.sizes[0]; - const totalSum = size.price * item.quantity; + const discountDecimal = 1 - productSize.discount / 100; + const discountedPrice = productSize.price * discountDecimal; + const sizeQtyPrice = discountedPrice * item.quantity; - return { productId: item.productId, quantity: item.quantity, price: size.price, totalSum }; + return { + productId: item.productId, + sizeId: productSize.id, + quantity: item.quantity, + price: discountedPrice, + sizeQtyPrice, + }; }) ); - const totalPrice = sizes.reduce((prev: number, cur: any) => prev + cur.totalSum, 0); + // combine all products sizes retrieved by flat() + const combinedproductsAndSizes = sizes.flat(); + const totalPrice = combinedproductsAndSizes.reduce((prev: number, curr: any) => prev + curr.sizeQtyPrice, 0); const order = await Order.create({ phone, @@ -81,10 +87,11 @@ export const createOrder = async (req: Request, res: Response): Promise => }); await Promise.all( - sizes.map(async (item: any) => + combinedproductsAndSizes.map(async (item: any) => OrderItems.create({ orderId: order.id, productId: item.productId, + sizeId: item.sizeId, quantity: item.quantity, price: item.price, }) @@ -98,60 +105,62 @@ export const createOrder = async (req: Request, res: Response): Promise => sendInternalErrorResponse(res, error); } }; -export const getAllOrders = async (req: Request, res: Response): Promise => { - const transaction: Transaction = await sequelize.transaction(); +export const getUserOrders = async (req: Request, res: Response) => { try { + const { id } = req.user as User; + const orders = await Order.findAll({ + where: { + userId: id, + }, + }); + + if (!orders || orders.length === 0) { + return sendErrorResponse(res, 'No orders found'); + } + + return res.status(200).json({ + ok: true, + message: 'Orders retrieved successfully', + data: orders, + }); + } catch (err) { + return sendInternalErrorResponse(res, err); + } +}; +export const sellerProductOrders = async (req: Request, res: Response) => { + try { + const { id } = req.user as User; const orders = await Order.findAll({ - attributes: ['id', 'totalPrice', 'country', 'city', 'phone'], include: [ { - model: User, - as: 'user', - attributes: ['email', 'phoneNumber', 'firstName', 'lastName'], - }, - { - model: Cart, - as: 'Carts', + model: OrderItems, + as: 'orderItems', + include: [ + { + model: Product, + as: 'products', + where: { sellerId: id }, + }, + ], }, ], - transaction, }); - - if (orders.length === 0) { - res.status(404).json({ - ok: false, - message: 'No orders found', - }); - return; + if (!orders) { + return sendErrorResponse(res, 'No orders found'); } - const [cartItems] = await sequelize.query('SELECT "productId", quantity from "CartsProducts" where "cartId" = ?', { - replacements: [orders.map((order: any) => order.Carts.id)], - transaction, + + res.status(200).json({ + ok: true, + message: 'Orders retrieved successfully', + data: orders, }); - const orderedProducts = Promise.all( - cartItems.map(async (item: any) => { - const productsAndQuantity = []; - const product = await Product.findOne({ - where: { id: item.productId }, - attributes: ['name', 'description', 'id'], - include: { - model: Size, - as: 'sizes', - }, - }); - productsAndQuantity.push({ product, quantity: item.quantity }); - return productsAndQuantity; - }) - ); - await transaction.commit(); - res.status(200).json({ ok: true, data: { orders, orderItems: await orderedProducts } }); - } catch (error) { - logger.error(error); - await transaction.rollback(); - sendInternalErrorResponse(res, error); - return; + } catch (err) { + logger.error('Error changing order status:', err); + console.log('hello there'); + return sendInternalErrorResponse(res, err); } }; + export const deleteOrder = async (req: Request, res: Response) => { try { const order = await Order.findByPk(req.params.id); @@ -166,17 +175,3 @@ export const deleteOrder = async (req: Request, res: Response) => { sendInternalErrorResponse(res, error); } }; -export const updateOrder = async (req: Request, res: Response) => { - try { - const order = await Order.findByPk(req.params.id); - if (!order) { - res.status(404).json({ ok: false, message: `No order found with id: ${req.params.id}` }); - return; - } - await Order.update(req.body, { where: { id: req.params.id } }); - res.status(200).json({ ok: true, message: 'Order updated successfully!' }); - } catch (error) { - logger.error(error); - sendInternalErrorResponse(res, error); - } -}; diff --git a/src/controllers/orderStatusController.ts b/src/controllers/orderStatusController.ts new file mode 100644 index 00000000..1e2d6f37 --- /dev/null +++ b/src/controllers/orderStatusController.ts @@ -0,0 +1,95 @@ +/* eslint-disable @typescript-eslint/no-unnecessary-condition */ +import { Request, Response } from 'express'; +import { sendErrorResponse } from '../helpers/helper'; +import Order from '../database/models/order'; +import User from '../database/models/user'; +import logger from '../logs/config'; +import { io } from '../server'; +import { sendInternalErrorResponse } from '../validations'; + +export const processOrder = async (req: Request, res: Response) => { + try { + const { orderId } = req.params; + const { id } = req.user as User; + const order = await Order.findOne({ where: { id: orderId, userId: id } }); + + if (!order) { + return sendErrorResponse(res, 'Order not found'); + } + + io.emit('orderProcessed', { order }); + + return res.status(200).json({ + ok: true, + message: `The order is ${order.status}`, + }); + } catch (err) { + logger.error('Error processing order:', err); + return sendInternalErrorResponse(res, err); + } +}; + +export const cancelOrder = async (req: Request, res: Response) => { + try { + const { orderId } = req.params; + const { id } = req.user as User; + const order = await Order.findOne({ where: { id: orderId, userId: id } }); + + if (!order) { + return sendErrorResponse(res, 'Order not found'); + } + console.log(order); + + if (order.status !== 'cancelled' && order.status !== 'pending') { + return sendErrorResponse( + res, + 'Only orders that are only paid only or pending are the only ones that can be cancelled' + ); + } + order.status = 'cancelled'; + await order.save(); + + io.emit('orderCancelled', { order }); + + return res.status(200).json({ + ok: true, + message: 'Order is cancelled successfully !', + data: order, + }); + } catch (err) { + logger.error('Error cancelling order:', err); + return sendInternalErrorResponse(res, err); + } +}; + +export const sellerChangeOrderStatus = async (req: Request, res: Response) => { + try { + const { orderId } = req.params; + const { status, expectedDeliveryDate } = req.body; + const order = await Order.findOne({ + where: { + id: orderId, + }, + }); + + if (!order) { + return sendErrorResponse(res, 'Order not found or you do not have permission to modify this order'); + } + + order.status = status; + if (expectedDeliveryDate) { + order.expectedDeliveryDate = new Date(expectedDeliveryDate); + } + await order.save(); + + io.emit('orderStatusChanged', { order }); + + return res.status(200).json({ + ok: true, + message: 'Order status updated successfully', + }); + } catch (err) { + logger.error('Error changing order status:', err); + return sendInternalErrorResponse(res, err); + } +}; diff --git a/src/controllers/productsController.ts b/src/controllers/productsController.ts index 00fd53b2..f33ebc03 100644 --- a/src/controllers/productsController.ts +++ b/src/controllers/productsController.ts @@ -13,9 +13,7 @@ import User from '../database/models/user'; import { sendEmail } from '../helpers/send-email'; import { destroyImage } from '../helpers/destroyImage'; import { extractImageId } from '../helpers/extractImageId'; -// import Review, { ReviewAttributes } from '../database/models/Review'; import Review, { ReviewAttributes } from '../database/models/Review'; -import Cart from '../database/models/cart'; import Order from '../database/models/order'; import OrderItems from '../database/models/orderItems'; @@ -23,7 +21,7 @@ export const createProduct = async (req: Request, res: Response) => { try { const { name, description, colors } = req.body as ProductAttributes; const { categoryId } = req.params; - const seller = (await req.user) as any; + const seller = (await req.user) as User; const sellerId = seller.id; // when products exists @@ -68,7 +66,7 @@ export const createSize = async (req: Request, res: Response) => { const { productId } = req.params; const { size, price, discount, expiryDate, quantity } = req.body as SizeAttributes; - const result = await Size.create({ size, price, discount, expiryDate, productId, quantity }); + await Size.create({ size, price, discount, expiryDate, productId, quantity }); res.status(201).json({ ok: true, message: 'Product size added successfully', diff --git a/src/database/index.ts b/src/database/index.ts index 647e9f76..80551d5b 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -6,8 +6,8 @@ const databaseConnection = async () => { try { await sequelize.authenticate(); logger.info('connected to the database'); - } catch (error: any) { - logger.error(error.message); + } catch (error: unknown) { + if (error instanceof Error) logger.error(error.message); } }; diff --git a/src/database/migrations/20240514160800-orders.js b/src/database/migrations/20240514160800-orders.js index e7f132ae..bf93bf22 100644 --- a/src/database/migrations/20240514160800-orders.js +++ b/src/database/migrations/20240514160800-orders.js @@ -10,7 +10,7 @@ module.exports = { primaryKey: true, }, status: { - type: Sequelize.ENUM('pending', 'delivered', 'cancelled'), + type: Sequelize.ENUM('pending', 'delivered', 'cancelled', 'paid'), defaultValue: 'pending', }, shippingAddress1: { @@ -49,6 +49,10 @@ module.exports = { type: Sequelize.FLOAT, allowNull: false, }, + expectedDeliveryDate: { + type: Sequelize.DATE, + allowNull: true, + }, createdAt: { type: Sequelize.DATE, allowNull: false, diff --git a/src/database/migrations/20240527203432-add-sizeId-to-orderItems.js b/src/database/migrations/20240527203432-add-sizeId-to-orderItems.js new file mode 100644 index 00000000..6265586f --- /dev/null +++ b/src/database/migrations/20240527203432-add-sizeId-to-orderItems.js @@ -0,0 +1,20 @@ +'use strict'; + +module.exports = { + up: async (queryInterface, Sequelize) => { + await queryInterface.addColumn('orderItems', 'sizeId', { + type: Sequelize.UUID, + allowNull: false, + references: { + model: 'sizes', + key: 'id', + }, + onUpdate: 'CASCADE', + onDelete: 'CASCADE', + }); + }, + + down: async (queryInterface, Sequelize) => { + await queryInterface.removeColumn('orderItems', 'sizeId'); + }, +}; diff --git a/src/database/models/Product.ts b/src/database/models/Product.ts index 49155d3f..9391df66 100644 --- a/src/database/models/Product.ts +++ b/src/database/models/Product.ts @@ -2,7 +2,7 @@ import { Model, DataTypes } from 'sequelize'; import sequelize from './index'; import { UUIDV4 } from 'sequelize'; import { Category } from './Category'; -import { Size } from './Size'; +import { Size, SizeAttributes } from './Size'; import User from './user'; export interface ProductAttributes { @@ -13,6 +13,7 @@ export interface ProductAttributes { images: string[]; colors?: string[]; categoryId: string; + sizes?: any; createdAt?: Date; updatedAt?: Date; } @@ -25,9 +26,9 @@ export class Product extends Model implements ProductAttribut public categoryId!: string; public images!: string[]; public colors!: string[]; + public sizes!: any; public readonly createdAt!: Date | undefined; public readonly updatedAt!: Date | undefined; - sizes?: any; } Product.init( diff --git a/src/database/models/Size.ts b/src/database/models/Size.ts index ef0652e3..c9850534 100644 --- a/src/database/models/Size.ts +++ b/src/database/models/Size.ts @@ -1,7 +1,7 @@ import { Model, Optional, DataTypes, UUIDV4 } from 'sequelize'; import sequelize from './index'; export interface SizeAttributes { - id: number; + id?: number; size?: string; price: number; quantity?: number; @@ -9,11 +9,11 @@ export interface SizeAttributes { expiryDate?: Date; productId: string; available?: boolean; + createdAt?: Date; + updatedAt?: Date; } -export interface SizeCreationAttributes extends Optional {} - -export class Size extends Model implements SizeAttributes { +export class Size extends Model implements SizeAttributes { public id!: number; public size!: string; public price!: number; diff --git a/src/database/models/order.ts b/src/database/models/order.ts index 2bf01245..4f0d45b3 100644 --- a/src/database/models/order.ts +++ b/src/database/models/order.ts @@ -16,6 +16,7 @@ interface OrderAttributes { userId: string; zipCode?: string; totalPrice: number; + expectedDeliveryDate?: Date; } interface OrderCreationAttributes extends Optional {} @@ -33,6 +34,7 @@ class Order extends Model implements O public userId!: string; public totalPrice!: number; public zipCode?: string; + public expectedDeliveryDate?: Date; } Order.init( @@ -43,7 +45,7 @@ Order.init( primaryKey: true, }, status: { - type: DataTypes.ENUM('pending', 'delivered', 'cancelled'), + type: DataTypes.ENUM('pending', 'delivered', 'cancelled', 'paid'), defaultValue: 'pending', }, shippingAddress1: { @@ -82,6 +84,10 @@ Order.init( type: DataTypes.FLOAT, allowNull: false, }, + expectedDeliveryDate: { + type: DataTypes.DATE, + allowNull: true, + }, createdAt: { type: DataTypes.DATE, allowNull: false, diff --git a/src/database/models/orderItems.ts b/src/database/models/orderItems.ts index 2cafc934..3b3cd22e 100644 --- a/src/database/models/orderItems.ts +++ b/src/database/models/orderItems.ts @@ -2,11 +2,13 @@ import { DataTypes, Model, Optional, UUIDV4 } from 'sequelize'; import sequelize from './index'; import Order from './order'; import { Product } from './Product'; +import { Size } from './Size'; interface OrderItemsAttributes { id: string; orderId: string; productId: string; + sizeId: string; quantity: number; price: number; createdAt?: Date; @@ -19,6 +21,7 @@ class OrderItems extends Model { - if (req.files) { - multerUpload.array('images', 8)(req, res, next); - } else { - multerUpload.single('image')(req, res, next); - } -}; diff --git a/src/routes/index.ts b/src/routes/index.ts index 64d4aa2a..d49ad096 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -13,8 +13,8 @@ import notificationRoutes from './notificationRoutes'; import { permissionRoute } from './permissionRoute'; import { sellerRequestRouter } from './sellerRequestRoute'; import chatRoute from './chatRoute'; - import orderRouter from './orderRoute'; +import { paymentRouter } from './paymentRoutes'; const router = Router(); router.use('/chats', chatRoute); @@ -29,6 +29,7 @@ router.use('/wishlist', wishlistRoute); router.use('/notifications', notificationRoutes); router.use('/permissions', permissionRoute); router.use('/vendor-requests', sellerRequestRouter); +router.use('/payments', paymentRouter); router.use('/orders', orderRouter); export default router; diff --git a/src/routes/orderRoute.ts b/src/routes/orderRoute.ts index fbe91a72..7e2fe31e 100644 --- a/src/routes/orderRoute.ts +++ b/src/routes/orderRoute.ts @@ -1,14 +1,20 @@ import { Router } from 'express'; import { checkUserRoles, isAuthenticated } from '../middlewares/authMiddlewares'; -import { getAllOrders, createOrder, deleteOrder, updateOrder } from '../controllers/orderController'; +import { getUserOrders, createOrder, deleteOrder, sellerProductOrders } from '../controllers/orderController'; +import { processOrder, cancelOrder, sellerChangeOrderStatus } from '../controllers/orderStatusController'; const orderRouter = Router(); orderRouter .route('/') - .get(isAuthenticated, checkUserRoles('buyer'), getAllOrders) + .get(isAuthenticated, checkUserRoles('buyer'), getUserOrders) .post(isAuthenticated, checkUserRoles('buyer'), createOrder); -orderRouter - .route('/:id') - .delete(isAuthenticated, checkUserRoles('admin'), deleteOrder) - .patch(isAuthenticated, checkUserRoles('buyer'), updateOrder); + +orderRouter.route('/get-orders').get(isAuthenticated, checkUserRoles('seller'), sellerProductOrders); +orderRouter.route('/:id').delete(isAuthenticated, checkUserRoles('admin'), deleteOrder); + +orderRouter.get('/:orderId/check-status', isAuthenticated, checkUserRoles('buyer'), processOrder); +orderRouter.put('/:orderId/cancel', isAuthenticated, checkUserRoles('buyer'), cancelOrder); +orderRouter.put('/:orderId/seller-change-status', isAuthenticated, checkUserRoles('seller'), sellerChangeOrderStatus); +orderRouter.get('/seller-products-status', isAuthenticated, checkUserRoles('seller'), sellerProductOrders); + export default orderRouter; diff --git a/src/routes/paymentRoutes.ts b/src/routes/paymentRoutes.ts new file mode 100644 index 00000000..714c8028 --- /dev/null +++ b/src/routes/paymentRoutes.ts @@ -0,0 +1,9 @@ +/* eslint-disable @typescript-eslint/no-misused-promises */ +import { Router } from 'express'; +import { isAuthenticated } from '../middlewares/authMiddlewares'; +import { handlePayments, handleWebHooks } from '../controllers/handlePaymentsController'; + +export const paymentRouter = Router(); + +paymentRouter.post('/:orderId/charge', isAuthenticated, handlePayments); +paymentRouter.post('/webhook', handleWebHooks); diff --git a/src/server.ts b/src/server.ts index 2d828cae..a239ead3 100644 --- a/src/server.ts +++ b/src/server.ts @@ -13,6 +13,7 @@ import databaseConnection from './database'; import { schedulePasswordUpdatePrompts } from './scheduler'; import scheduledTasks from './config/cornJobs'; import { socketSetUp } from './chatSetup'; +import { IncomingMessage } from 'http'; dotenv.config(); @@ -20,7 +21,13 @@ export const app: Application = express(); app.use(cors()); app.use(passport.initialize()); -app.use(express.json()); +app.use( + express.json({ + verify: (req, res, buf) => { + (req as IncomingMessage & { rawBody: Buffer | string }).rawBody = buf; + }, + }) +); app.use(express.urlencoded({ extended: true })); app.use(express.static('public')); const swaggerSpec = swaggerJsDoc(options); @@ -59,5 +66,5 @@ const PORT = process.env.PORT ?? 3000; const server = app.listen(PORT, () => { logger.info(`Server is running on port ${PORT}`); }); - socketSetUp(server); +export const io = socketSetUp(server);