From 8c7f8d05fed1646828fcd59969f0035b932a73d1 Mon Sep 17 00:00:00 2001 From: bernice uwituze Date: Sun, 14 Jul 2024 09:56:27 +0200 Subject: [PATCH 1/2] get orders for vendors --- src/controllers/orderController.ts | 50 ++++++++++++++++++++++++++++-- src/helpers/generateToken.ts | 27 +++++++++++----- src/routes/order.route.ts | 4 ++- 3 files changed, 70 insertions(+), 11 deletions(-) diff --git a/src/controllers/orderController.ts b/src/controllers/orderController.ts index 0f2a6ec..e98a008 100644 --- a/src/controllers/orderController.ts +++ b/src/controllers/orderController.ts @@ -66,14 +66,33 @@ export const modifyOrderStatus = async (req: Request, res: Response) => { export const getAllOrders = async (req:Request, res: Response) => { try{ const userId = (req as any).token.id; - const vendor = await findVendorByUserId(userId); + const vendorId = req.params.vendorId; + + let orders: any; - let orders; + if(vendorId){ + const allOrders: any = await Order.findAll({ + include: [{ model: Product, as: 'products' }] + }); + if(allOrders.length === 0){ + return res.status(404).json({ message: "No orders found" }); + } + + orders = allOrders.filter(order => + order.products.some(product => product.vendorId === vendorId) + ) + + + } else { orders = await Order.findAll({ where: { userId }}) + + } + return res.status(200).json(orders); } catch(error: any){ + console.error(error); return res.status(500).json({ error: error.message}) } } @@ -104,3 +123,30 @@ export const getOrder = async(req: Request, res: Response) => { } } +export const getSellerOrder = async (req: Request, res: Response) => { + try { + const vendorId = req.params.vendorId; + const orders: any = await Order.findAll(); + if (!orders) { + return res.status(404).json({ message: "No order found" }); + } + + const products: any[] = []; + for (const order of orders) { + for (const data of order.products) { + const single_product = await Product.findOne({ + where: { productId: data.productId }, + }); + if (single_product?.vendorId === vendorId) { + products.push(order); + } + } + } + + res.status(200).send(products); + } catch (error) { + console.log(error); + res.status(500).json({ message: "Internal error server" }); + } +}; + diff --git a/src/helpers/generateToken.ts b/src/helpers/generateToken.ts index dc00fb1..9e61c8c 100644 --- a/src/helpers/generateToken.ts +++ b/src/helpers/generateToken.ts @@ -1,20 +1,31 @@ import jwt from 'jsonwebtoken'; import User from '../database/models/user'; import dotenv from 'dotenv'; +import Vendor from '../database/models/vendor'; dotenv.config(); const generateToken = async (userData: User) => { - return jwt.sign({ + const vendor = await Vendor.findOne({ + where: { userId: userData?.userId }, + }); + const vendorId = vendor ? vendor.vendorId : null; + + return jwt.sign( + { role: userData.role, email: userData.email, id: userData.userId, - password:userData.password - }, "crafters1234", { - expiresIn: '1d' - }); -} - -export { generateToken }; + vendor: vendorId, + password: userData.password, + }, + "crafters1234", + { + expiresIn: "1d", + } + ); + }; + + export { generateToken }; diff --git a/src/routes/order.route.ts b/src/routes/order.route.ts index 4b692bc..e492a02 100644 --- a/src/routes/order.route.ts +++ b/src/routes/order.route.ts @@ -3,7 +3,8 @@ import { getAllOrder, modifyOrderStatus, getOrder, - getAllOrders + getAllOrders, + getSellerOrder } from "../controllers/orderController"; import { VerifyAccessToken } from "../middleware/verfiyToken"; import { @@ -29,5 +30,6 @@ router.put( router.get("/order/getAllOrder", getAllOrder); router.get("/order/getOrder/:orderId", getOrder); router.get("/orders", VerifyAccessToken, getAllOrders); +router.get("/order/getSellerOrder/:vendorId", getSellerOrder); export default router; From da839b9a9b0704b3f6da44d0d23c4034e719793a Mon Sep 17 00:00:00 2001 From: sevelinCA Date: Fri, 19 Jul 2024 13:04:19 +0200 Subject: [PATCH 2/2] add pay chat --- package-lock.json | 30 ++++++-- package.json | 1 + src/controllers/Payment.ts | 99 ++++++++++++++++++++++++++ src/controllers/cart.controller.ts | 5 +- src/controllers/checkout.controller.ts | 5 +- src/database/models/cartitem.ts | 4 ++ src/database/models/order.ts | 1 + src/index.ts | 7 ++ src/routes/checkout.router.ts | 15 +++- src/services/paymentService.ts | 12 ++++ 10 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 src/controllers/Payment.ts create mode 100644 src/services/paymentService.ts diff --git a/package-lock.json b/package-lock.json index 45f007d..3e00a66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "sequelize-typescript": "^2.1.6", "sinon": "^18.0.0", "socket.io": "^4.7.5", + "stripe": "^16.2.0", "supertest": "^7.0.0", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.0", @@ -1981,9 +1982,9 @@ } }, "node_modules/agent-base/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dependencies": { "ms": "2.1.2" }, @@ -2060,6 +2061,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -3711,6 +3713,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", @@ -4020,9 +4023,9 @@ } }, "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dependencies": { "ms": "2.1.2" }, @@ -8292,6 +8295,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", @@ -9008,6 +9012,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dependencies": { "glob": "^7.1.3" }, @@ -9022,6 +9027,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9748,6 +9754,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stripe": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-16.2.0.tgz", + "integrity": "sha512-kNHb5x6mlQJ8V92Afn+F+uyHG3U0+Mc1pOYxDkbEh/kI3oF63URnUt6dwTQthELYWBy04arerm2XjwgtzEPDEA==", + "dependencies": { + "@types/node": ">=8.1.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + } + }, "node_modules/superagent": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", diff --git a/package.json b/package.json index 8d92575..8d0c0f7 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "sequelize-typescript": "^2.1.6", "sinon": "^18.0.0", "socket.io": "^4.7.5", + "stripe": "^16.2.0", "supertest": "^7.0.0", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.0", diff --git a/src/controllers/Payment.ts b/src/controllers/Payment.ts new file mode 100644 index 0000000..24c9b3b --- /dev/null +++ b/src/controllers/Payment.ts @@ -0,0 +1,99 @@ +import express, { Request, Response } from "express"; +import Stripe from "stripe"; +import dotenv from "dotenv"; +import { findOrderById, findProductById } from "../services/paymentService"; + +dotenv.config(); + +const stripe = new Stripe(`${process.env.STRIPE_SECRET_KEY}`); +export const checkout = async (req: Request, res: Response) => { + try { + const orderId = req.params.id; + const order = await findOrderById(orderId); + if (!order) { + return res.status(404).json({ message: "Order not found" }); + } + const line_items: any[] = await Promise.all( + order.products.map(async (item: any) => { + const productDetails:any = await findProductById(item.productId); + const unit_amount = Math.round(productDetails!.price * 100); + return { + price_data: { + currency: "usd", + product_data: { + name: productDetails?.name, + images: [productDetails?.image[0]], + }, + unit_amount: unit_amount, + }, + quantity: item.quantity, + }; + }) + ); + console.log(line_items) + + + const session = await stripe.checkout.sessions.create({ + line_items, + mode: "payment", + success_url: process.env.SUCCESS_PAYMENT_URL, + cancel_url: process.env.CANCEL_PAYMENT_URL, + metadata: { + orderId: orderId, + }, + }); + + + res.status(200).json({ url: session.url }); + } catch (error: any) { + res.status(500).json({ message: error.message }); + } +}; + +export const webhook = async (req: Request, res: Response) => { + const sig: any = req.headers["stripe-signature"]; + const webhookSecret: any = process.env.WEBHOOK_SECRET_KEY; + let event: any; + try { + event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret); + } catch (err: any) { + console.log(`⚠️ Webhook signature verification failed.`, err.message); + return res.status(400).send(`Webhook Error: ${err.message}`); + } + + switch (event.type) { + case "checkout.session.completed": + const session = event.data.object; + + try { + const lineItems = await stripe.checkout.sessions.listLineItems( + session.id + ); + + const orderId = session.metadata.orderId; + const order = await findOrderById(orderId); + if (order) { + order.status = "paid"; + await order.save(); + } else { + console.error("Order not found:", orderId); + } + } catch (err) { + console.error("Error processing session completed event:", err); + } + break; + + case "payment_intent.succeeded": + const paymentIntent = event.data.object; + console.log("Payment Intent succeeded: ", paymentIntent); + break; + case "payment_method.attached": + const paymentMethod = event.data.object; + console.log("Payment Method attached: ", paymentMethod); + break; + + default: + console.log(`Unhandled event type ${event.type}`); + } + res.json({ received: true }); +}; diff --git a/src/controllers/cart.controller.ts b/src/controllers/cart.controller.ts index 38130ae..f7dc4d4 100644 --- a/src/controllers/cart.controller.ts +++ b/src/controllers/cart.controller.ts @@ -66,7 +66,10 @@ export const getCart = async (req: Request, res: Response) => { if (!cart) { return res.status(404).json({ message: "Cart not found" }); } - const cartitem = await CartItem.findAll({ where: { cartId: cart.cartId } }); + const cartitem = await CartItem.findAll({ where: { cartId: cart.cartId }, include: { + model: Product, + as: "Product" + } }); return res.status(200).json({ cartitem }); } catch (error: any) { diff --git a/src/controllers/checkout.controller.ts b/src/controllers/checkout.controller.ts index 5ed68b5..c8bc07a 100644 --- a/src/controllers/checkout.controller.ts +++ b/src/controllers/checkout.controller.ts @@ -6,7 +6,7 @@ import { v4 as uuidv4 } from 'uuid'; export const createOrder = async (req: Request, res: Response) => { - const { userId, deliveryAddress, paymentMethod } = req.body; + const { userId, deliveryAddress, paymentMethod,client } = req.body; if(!userId || !deliveryAddress || !paymentMethod) { return res.status(400).json({ message: 'All fields are required' }) } @@ -37,7 +37,8 @@ export const createOrder = async (req: Request, res: Response) => { paymentMethod, status: 'pending', products: orderItems, - totalAmount: totalAmount + totalAmount: totalAmount, + client }); await CartItem.destroy({ where: { cartId: cart.cartId } }); res.status(201).json({ message: 'Order placed successfully', order }); diff --git a/src/database/models/cartitem.ts b/src/database/models/cartitem.ts index eebf3bd..44a5c9d 100644 --- a/src/database/models/cartitem.ts +++ b/src/database/models/cartitem.ts @@ -14,6 +14,10 @@ class CartItem extends Model { foreignKey: "cartId", as: "cart", }); + CartItem.belongsTo(models.Product,{ + foreignKey: "productId", + as: "Product" + }) } static initModel(sequelize: Sequelize) { CartItem.init( diff --git a/src/database/models/order.ts b/src/database/models/order.ts index 57886d9..83903d9 100644 --- a/src/database/models/order.ts +++ b/src/database/models/order.ts @@ -25,6 +25,7 @@ class Order extends Model { userId: { type: DataTypes.STRING, allowNull: false }, paymentMethod: { type: DataTypes.INTEGER, allowNull: false }, status: { type: DataTypes.STRING, allowNull: false }, + client: { type: DataTypes.STRING, allowNull: false }, products: { type: DataTypes.JSONB, allowNull: false }, totalAmount: {type:DataTypes.INTEGER,allowNull: false}, expectedDeliveryDate: {type: DataTypes.DATE, allowNull: true} diff --git a/src/index.ts b/src/index.ts index 7bd6203..738e9d1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,6 +58,13 @@ app.use(cors({ } )); app.use(cookieParser()); +app.use((req, res, next) => { + if (req.originalUrl === '/webhook') { + next(); + } else { + express.json()(req, res, next); + } +}); app.use(express.urlencoded({ extended: true })); app.use( session({ diff --git a/src/routes/checkout.router.ts b/src/routes/checkout.router.ts index eb54457..ab685e4 100644 --- a/src/routes/checkout.router.ts +++ b/src/routes/checkout.router.ts @@ -1,7 +1,20 @@ -import express from 'express' +import express, { Request, Response } from 'express' import { createOrder } from '../controllers/checkout.controller' +import { checkout, webhook } from '../controllers/Payment'; +import { VerifyAccessToken } from '../middleware/verfiyToken'; const router = express.Router() router.post('/checkout', createOrder) +router.post("/payment/:id", VerifyAccessToken, checkout); + +router.post('/webhook', express.raw({ type: 'application/json' }), webhook); + +router.get("/success", async(req:Request,res:Response)=>{ + res.send("Succesfully") +}); +router.get("/cancel", async(req:Request,res:Response)=>{ + res.send("Cancel") +}); + export default router \ No newline at end of file diff --git a/src/services/paymentService.ts b/src/services/paymentService.ts new file mode 100644 index 0000000..02ea506 --- /dev/null +++ b/src/services/paymentService.ts @@ -0,0 +1,12 @@ +import Order from "../database/models/order" +import Product from "../database/models/product" + + +export const findOrderById = async (orderId: any)=>{ + const order = await Order.findByPk(orderId) + return order +} +export const findProductById = async (productId:any)=>{ + const product = await Product.findByPk(productId) + return product +} \ No newline at end of file