From 5efcd796b1c3b22ebcdc2b70af529a1355ec7bb2 Mon Sep 17 00:00:00 2001 From: Prince-Kid Date: Tue, 17 Sep 2024 15:57:40 +0200 Subject: [PATCH] Fx notifications for the checkout --- src/controllers/Payment.ts | 76 +++++++-- src/controllers/checkout.controller.ts | 112 +++++++------ src/controllers/orderController.ts | 6 +- src/controllers/orderStatus.controller.ts | 99 ++++++++---- src/controllers/product.controller.ts | 14 +- src/database/config/config.js | 14 +- src/database/models/vendor.ts | 32 +++- src/helpers/expiring.ts | 180 ++++++++++----------- src/routes/checkout.router.ts | 76 +++++++-- src/services/emailService.ts | 188 ++++++++++++++++++++++ src/services/paymentService.ts | 26 +-- src/services/rolesService.ts | 15 +- src/services/types.ts | 27 ++++ 13 files changed, 628 insertions(+), 237 deletions(-) create mode 100644 src/services/emailService.ts create mode 100644 src/services/types.ts diff --git a/src/controllers/Payment.ts b/src/controllers/Payment.ts index 7cdfb78..92f0cd4 100644 --- a/src/controllers/Payment.ts +++ b/src/controllers/Payment.ts @@ -1,28 +1,54 @@ import express, { Request, Response } from "express"; import Stripe from "stripe"; import dotenv from "dotenv"; -import { findOrderById, findProductById } from "../services/paymentService"; +import { + findOrderById, + findProductById, + findUserById, +} from "../services/paymentService"; +import { sendOrderConfirmation } from "../services/emailService"; 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; + + // Validate orderId + if (!orderId) { + return res.status(400).json({ message: "Order ID is required" }); + } + const order = await findOrderById(orderId); + if (!order) { return res.status(404).json({ message: "Order not found" }); } + + // Validate order.products + if (!order.products || !Array.isArray(order.products)) { + return res.status(400).json({ message: "Order products are not valid" }); + } + 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); + const productDetails: any = await findProductById(item.productId); + + if (!productDetails) { + console.error(`Product with ID ${item.productId} not found`); + throw new Error("Product not found"); + } + + const unit_amount = Math.round(productDetails.price * 100); + return { price_data: { currency: "usd", product_data: { - name: productDetails?.name, - images: [productDetails?.image[0]], + name: productDetails.name, + images: [productDetails.image[0]], }, unit_amount: unit_amount, }, @@ -30,7 +56,6 @@ export const checkout = async (req: Request, res: Response) => { }; }) ); - const session = await stripe.checkout.sessions.create({ line_items, @@ -42,9 +67,9 @@ export const checkout = async (req: Request, res: Response) => { }, }); - res.status(200).json({ url: session.url }); } catch (error: any) { + console.error("Error during checkout process:", error); res.status(500).json({ message: error.message }); } }; @@ -53,27 +78,54 @@ 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); + console.error(`⚠️ 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; + const session: any = event.data.object as Stripe.Checkout.Session; + + // Log session details + console.log("Checkout Session Completed Event:"); + console.log("Session ID:", session.id); + console.log("Session Metadata:", session.metadata); + console.log("Session Object:", session); try { + // Fetch line items to get more order details const lineItems = await stripe.checkout.sessions.listLineItems( session.id ); + console.log("Line Items:", lineItems); const orderId = session.metadata.orderId; + + if (!orderId) { + console.error("Order ID not found in session metadata"); + return res.status(400).json({ message: "Order ID not found" }); + } + const order = await findOrderById(orderId); + console.log("Order:", order); + if (order) { order.status = "paid"; await order.save(); + + // Fetch user details and log them + const user = await findUserById(order.userId); + console.log("User:", user); + + if (user) { + await sendOrderConfirmation(user, order); + } else { + console.error("User not found for order:", orderId); + } } else { console.error("Order not found:", orderId); } @@ -83,16 +135,18 @@ export const webhook = async (req: Request, res: Response) => { break; case "payment_intent.succeeded": - const paymentIntent = event.data.object; + const paymentIntent = event.data.object as Stripe.PaymentIntent; console.log("Payment Intent succeeded: ", paymentIntent); break; + case "payment_method.attached": - const paymentMethod = event.data.object; + const paymentMethod = event.data.object as Stripe.PaymentMethod; console.log("Payment Method attached: ", paymentMethod); break; default: console.log(`Unhandled event type ${event.type}`); } + res.json({ received: true }); }; diff --git a/src/controllers/checkout.controller.ts b/src/controllers/checkout.controller.ts index 59099e5..953817b 100644 --- a/src/controllers/checkout.controller.ts +++ b/src/controllers/checkout.controller.ts @@ -2,59 +2,81 @@ import Order from "../database/models/order"; import Cart from "../database/models/cart"; import CartItem from "../database/models/cartitem"; import { Request, Response } from "express"; -import { v4 as uuidv4 } from 'uuid'; +import { v4 as uuidv4 } from "uuid"; import Product from "../database/models/product"; - +import { + findOrderById, + findProductById, + findUserById, +} from "../services/paymentService"; +import { sendOrderConfirmation } from "../services/emailService"; +import { notifyVendorsOfSale } from "../services/emailService"; export const createOrder = async (req: Request, res: Response) => { - const { userId, deliveryAddress, paymentMethod,client } = req.body; - if(!userId || !deliveryAddress || !paymentMethod) { - return res.status(400).json({ message: 'All fields are required' }) + const { userId, deliveryAddress, paymentMethod, client } = req.body; + if (!userId || !deliveryAddress || !paymentMethod) { + return res.status(400).json({ message: "All fields are required" }); + } + try { + const cart = await Cart.findOne({ where: { userId } }); + if (!cart) { + return res.status(404).json({ message: "Cart not found" }); } - try { - const cart = await Cart.findOne({ where: { userId } }); - if (!cart) { - return res.status(404).json({ message: 'Cart not found' }); - } - const cartItems = await CartItem.findAll({ where: { cartId: cart.cartId } }); - if (cartItems.length === 0) { - return res.status(400).json({ message: "Your cart is empty" }); - } + const cartItems = await CartItem.findAll({ + where: { cartId: cart.cartId }, + }); + if (cartItems.length === 0) { + return res.status(400).json({ message: "Your cart is empty" }); + } - const orderItems = cartItems.map(item => ({ - productId: item.productId, - quantity: item.quantity, - price: item.price, - status:"pending" - })); + const orderItems = cartItems.map((item) => ({ + productId: item.productId, + quantity: item.quantity, + price: item.price, + status: "pending", + })); - const totalAmount = orderItems.reduce((total, item) => total + (item.price * item.quantity), 0); - console.log(orderItems) + const totalAmount = orderItems.reduce( + (total, item) => total + item.price * item.quantity, + 0 + ); + console.log(orderItems); - const order = await Order.create({ - orderId: uuidv4(), - deliveryAddress, - userId, - paymentMethod, - status: 'pending', - products: orderItems, - totalAmount: totalAmount, - client - }); - for (const item of orderItems) { - const product = await Product.findOne({ where: { productId: item.productId } }); - if (product) { - product.quantity -= item.quantity; - if (product.quantity < 0) { - return res.status(400).json({ message: `Not enough stock for product ${product.productId}` }); - } - await product.save(); - } + const order = await Order.create({ + orderId: uuidv4(), + deliveryAddress, + userId, + paymentMethod, + status: "pending", + products: orderItems, + totalAmount: totalAmount, + client, + }); + for (const item of orderItems) { + const product = await Product.findOne({ + where: { productId: item.productId }, + }); + if (product) { + product.quantity -= item.quantity; + if (product.quantity < 0) { + return res.status(400).json({ + message: `Not enough stock for product ${product.productId}`, + }); } - await CartItem.destroy({ where: { cartId: cart.cartId } }); - res.status(201).json({ message: 'Order placed successfully', order }); - } catch (error: any) { - res.status(500).json({ message: error.message }); + await product.save(); + } + } + await CartItem.destroy({ where: { cartId: cart.cartId } }); + const user = await findUserById(order.userId); + if (user) { + await sendOrderConfirmation(user, order); + await notifyVendorsOfSale(order); + } else { + console.error("User not found for order:"); } + res.status(201).json({ message: "Order placed successfully", order }); + } catch (error: any) { + res.status(500).json({ message: error.message }); + } }; diff --git a/src/controllers/orderController.ts b/src/controllers/orderController.ts index f2eea1c..b96b4e8 100644 --- a/src/controllers/orderController.ts +++ b/src/controllers/orderController.ts @@ -73,7 +73,7 @@ export const getAllOrders = async (req: Request, res: Response) => { let orders: any; - orders = await Order.findAll({ where: { userId }}) + orders = await Order.findAll({ where: { userId } }); return res.status(200).json(orders); } catch (error: any) { @@ -105,8 +105,6 @@ export const getOrder = async (req: Request, res: Response) => { } }; - - export const getSellerOrder = async (req: Request, res: Response) => { try { const vendorId = req.params.vendorId; @@ -127,7 +125,7 @@ export const getSellerOrder = async (req: Request, res: Response) => { } } - console.log("products_____:", products) + console.log("products_____:", products); res.status(200).send(products); } catch (error) { diff --git a/src/controllers/orderStatus.controller.ts b/src/controllers/orderStatus.controller.ts index 46a351c..402c27b 100644 --- a/src/controllers/orderStatus.controller.ts +++ b/src/controllers/orderStatus.controller.ts @@ -1,7 +1,29 @@ import { Request, Response } from "express"; import Order from "../database/models/order"; -import { ioServer } from ".."; +import User from "../database/models/user"; import pusher from "../pusher"; +import nodemailer from "nodemailer"; +import dotenv from "dotenv"; +import { ioServer } from ".."; + +dotenv.config(); + +const allowedStatuses = [ + "pending", + "processing", + "shipped", + "delivered", + "on hold", + "cancelled", +]; + +const transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: process.env.EMAIL, + pass: process.env.EMAIL_PASS, + }, +}); export const getOrderStatus = async (req: Request, res: Response) => { try { @@ -12,13 +34,11 @@ export const getOrderStatus = async (req: Request, res: Response) => { if (!order) { return res.status(404).send({ message: "Order not found" }); } - res - .status(200) - .json({ - orderId, - status: order.status, - expectedDeliveryDate: order.expectedDeliveryDate, - }); + res.status(200).json({ + orderId, + status: order.status, + expectedDeliveryDate: order.expectedDeliveryDate, + }); } catch (err: any) { res.status(500).json({ message: err.message }); } @@ -30,27 +50,16 @@ export const updateOrderStatus = async (req: Request, res: Response) => { const status = req.body.status; const vendorId = (req as any).vendorId; - const validStatus = [ - "pending", - "processing", - "shipped", - "delivered", - "on hold", - "cancelled", - ]; - - if (!validStatus.includes(status)) { + if (!allowedStatuses.includes(status)) { return res.status(400).json({ message: "Invalid order status" }); } - const order = await Order.findOne({ where: { orderId: orderId } }); + const order = await Order.findOne({ where: { orderId } }); if (!order) { return res.status(404).send({ message: "Order not found" }); } - - order.status = status; if (status === "processing") { @@ -62,14 +71,43 @@ export const updateOrderStatus = async (req: Request, res: Response) => { order.expectedDeliveryDate = expectedDeliveryDate; } - console.log('Updating order to: ', status) + console.log("Updating order to: ", status); await order.save(); - console.log('Order updated successfully: ', order.toJSON()) + console.log("Order updated successfully: ", order.toJSON()); const formattedDate = order.expectedDeliveryDate ? order.expectedDeliveryDate.toLocaleDateString() : null; + // Notify the buyer via email + const buyer = await User.findByPk(order.userId); + if (buyer) { + const emailContent = ` +

Order Status Update

+

Dear ${buyer.name},

+

Your order with ID ${orderId} has been updated to "${status}".

+

Expected Delivery Date: ${ + formattedDate || "Not set yet" + }

+

Thank you for shopping with us!

+

Best regards,

+

Crafters E-commerce Team

+ `; + + try { + await transporter.sendMail({ + from: `"Crafters E-commerce" <${process.env.EMAIL}>`, + to: buyer.email, + subject: "Order Status Update", + html: emailContent, + }); + console.log("Status update email sent to the buyer."); + } catch (error) { + console.error("Error sending status update email:", error); + } + } + + // Trigger Pusher event and Socket.io event try { await pusher.trigger("order-channel", "order-updated", { orderId, @@ -85,14 +123,13 @@ export const updateOrderStatus = async (req: Request, res: Response) => { status: order.status, expectedDeliveryDate: formattedDate, }); - res - .status(200) - .json({ - orderId, - status: order.status, - expectedDeliveryDate: formattedDate, - }); + + res.status(200).json({ + orderId, + status: order.status, + expectedDeliveryDate: formattedDate, + }); } catch (err: any) { res.status(500).json({ message: err.message }); } -}; \ No newline at end of file +}; diff --git a/src/controllers/product.controller.ts b/src/controllers/product.controller.ts index 7754e64..8da88e3 100644 --- a/src/controllers/product.controller.ts +++ b/src/controllers/product.controller.ts @@ -23,7 +23,6 @@ import { request } from "http"; import { getProductById } from "../services/productService"; import { fetchSimilarProducts } from "../services/productService"; - export const createProduct = async (req: Request, res: Response) => { try { const tokenData = (req as any).token; @@ -47,7 +46,7 @@ export const createProduct = async (req: Request, res: Response) => { category, expiringDate, } = req.body; - console.log(image) + console.log(image); if (!name || !image || !description || !price || !quantity || !category) { return res.status(200).json("All Field are required"); } @@ -56,8 +55,6 @@ export const createProduct = async (req: Request, res: Response) => { return res.status(400).json({ message: "Exactly 4 images are required" }); } - - const data = { name, image: image, @@ -73,7 +70,7 @@ export const createProduct = async (req: Request, res: Response) => { if (!save) { return res.status(500).json({ error: "Failed to save data" }); } - + productLifecycleEmitter.emit(PRODUCT_ADDED, data); return res.status(201).json({ message: "Product Created", data: save }); @@ -106,7 +103,6 @@ export const similarProducts = async (req: Request, res: Response) => { const product = await getProductById(productId); if (!product) { - return res.status(404).json({ error: "Product not found" }); } @@ -116,10 +112,9 @@ export const similarProducts = async (req: Request, res: Response) => { if (similarProducts.length === 0) { return res.status(404).json({ error: "No similar products found" }); } - console.log("hhhhhhhhhh",similarProducts) + console.log("hhhhhhhhhh", similarProducts); return res.status(200).json(similarProducts); - } catch (error: any) { res.status(500).json({ error: error.message }); } @@ -143,7 +138,6 @@ export const readAllProducts = async (req: Request, res: Response) => { } }; - export const searchProduct = async (req: Request, res: Response) => { try { const { name, category } = req.query; @@ -206,7 +200,7 @@ export const deleteProduct = async (req: Request, res: Response) => { const tokenData = (req as any).token; const productId = req.params.id; const { vendorId } = req.body; - console.log(productId) + console.log(productId); const permissionCheck: any = await checkVendorModifyPermission( tokenData, diff --git a/src/database/config/config.js b/src/database/config/config.js index 01ddaa7..90868db 100644 --- a/src/database/config/config.js +++ b/src/database/config/config.js @@ -4,12 +4,6 @@ const config = { development: { url: process.env.DATABASE_DEVELOPMENT_URL, dialect: "postgres", - dialectOptions: { - ssl: { - require: true, - rejectUnauthorized: true, - } - } }, test: { url: process.env.DATABASE_TEST_URL, @@ -18,8 +12,8 @@ const config = { ssl: { require: true, rejectUnauthorized: true, - } - } + }, + }, }, production: { url: process.env.DATABASE_PRODUCTION_URL, @@ -28,8 +22,8 @@ const config = { ssl: { require: true, rejectUnauthorized: true, - } - } + }, + }, }, }; diff --git a/src/database/models/vendor.ts b/src/database/models/vendor.ts index 916f792..7d13e09 100644 --- a/src/database/models/vendor.ts +++ b/src/database/models/vendor.ts @@ -1,27 +1,45 @@ -"use strict"; -import { Model, DataTypes, Sequelize } from "sequelize"; +import { Model, DataTypes, Sequelize, Optional } from "sequelize"; import connectSequelize from "../config/db.config"; -import { ftruncate } from "fs"; +import User from "./user"; // Make sure this path is correct -class Vendor extends Model { +interface VendorAttributes { + vendorId?: string; + userId: string; + storeName: string; + address: any; + TIN: number; + bankAccount: number; + paymentDetails?: any; + status?: string; +} + +interface VendorCreationAttributes + extends Optional {} + +class Vendor extends Model { public vendorId?: string; public userId!: string; public storeName!: string; public address!: any; public TIN!: number; public bankAccount!: number; - public paymentDetails!: any; + public paymentDetails?: any; public status!: string; + + // Declare associations + public user?: User; // Add this line to declare the association + static associate(models: any) { Vendor.hasMany(models.Product, { foreignKey: "vendorId", - as: "vendor", + as: "Products", // It's better to use "Products" for consistency }); Vendor.belongsTo(models.User, { foreignKey: "userId", - as: "user", + as: "user", // Ensure alias is consistent with the use in queries }); } + static initModel(sequelize: Sequelize) { Vendor.init( { diff --git a/src/helpers/expiring.ts b/src/helpers/expiring.ts index e2066b1..b3ea1d9 100644 --- a/src/helpers/expiring.ts +++ b/src/helpers/expiring.ts @@ -5,42 +5,42 @@ import nodemailer from "nodemailer"; import models from "../database/models"; import { Response } from "express"; export const checkExpiringProducts = async (req?: Request, res?: Response) => { - try { - const expiringProducts = await Product.findAll({ - where: { - expiringDate: { - [Op.between]: [ - new Date(), - new Date(new Date().getTime() + 14 * 24 * 60 * 60 * 1000).getTime(), - ], - }, - }, - include: { - model: models.Vendor, - as: "Vendor", - }, - }); - console.log(expiringProducts); - if (expiringProducts.length === 0) { - return res?.status(204).json({ message: "No Expiring Products" }); - } + try { + const expiringProducts = await Product.findAll({ + where: { + expiringDate: { + [Op.between]: [ + new Date(), + new Date(new Date().getTime() + 14 * 24 * 60 * 60 * 1000).getTime(), + ], + }, + }, + include: { + model: models.Vendor, + as: "Vendor", + }, + }); + console.log(expiringProducts); + if (expiringProducts.length === 0) { + return res?.status(204).json({ message: "No Expiring Products" }); + } - const sendEmails = expiringProducts.map(async (product) => { - const userId = product.Vendor.userId; - const userEmail = await User.findByPk(userId); - const transporter = nodemailer.createTransport({ - service: "gmail", - secure: true, - auth: { - user: process.env.EMAIL, - pass: process.env.EMAIL_PASS, - }, - }); - let mailOptions = { - from: process.env.EMAIL, - to: userEmail?.email, - subject: "Reminder: Expiring Product in Store Inventory⚠️⚠️", - html: ` + const sendEmails = expiringProducts.map(async (product) => { + const userId = product.Vendor.userId; + const userEmail = await User.findByPk(userId); + const transporter = nodemailer.createTransport({ + service: "gmail", + secure: true, + auth: { + user: process.env.EMAIL, + pass: process.env.EMAIL_PASS, + }, + }); + let mailOptions = { + from: process.env.EMAIL, + to: userEmail?.email, + subject: "Reminder: Expiring Product in Store Inventory⚠️⚠️", + html: ` @@ -115,7 +115,7 @@ export const checkExpiringProducts = async (req?: Request, res?: Response) => {
    ${product.name}

It's essential to take proactive measures to manage these expiring products effectively. Here are a few suggestions:

Consider offering special promotions or discounts to encourage customers to purchase these items before they expire. This can help minimize losses and increase sales.

- Add Discount + Add Discount

Best regards,
Crafters

`, - }; - await transporter.sendMail(mailOptions); - }); - await Promise.all(sendEmails); - res?.status(200).json({ message: "Check Expiring Product Successfully" }); - } catch (error: any) { - return res?.status(500).json({ error: error.message }); - } + }; + await transporter.sendMail(mailOptions); + }); + await Promise.all(sendEmails); + res?.status(200).json({ message: "Check Expiring Product Successfully" }); + } catch (error: any) { + return res?.status(500).json({ error: error.message }); + } }; export const checkExpiredProducts = async (req?: Request, res?: Response) => { - try { - const expiredProduct = await Product.findAll({ - where: { - expiringDate: { - [Op.lt]: new Date(), - }, - expired: false, - }, - include: { - model: models.Vendor, - as: "Vendor", - }, - }); - if (expiredProduct.length === 0) { - return res?.status(204).json({ - message: "No Expired Products To Update", - }); - } - const updatePromise = expiredProduct.map(async (product) => { - const userId = product.Vendor.userId; - const userEmail = await User.findByPk(userId); - product.update({ expired: true, available: false }).then(async () => { - const transporter = nodemailer.createTransport({ - service: "gmail", - secure: true, - auth: { - user: process.env.EMAIL, - pass: process.env.EMAIL_PASS, - }, + try { + const expiredProduct = await Product.findAll({ + where: { + expiringDate: { + [Op.lt]: new Date(), + }, + expired: false, + }, + include: { + model: models.Vendor, + as: "Vendor", + }, }); - let mailOptions = { - from: process.env.EMAIL, - to: userEmail?.email, - subject: "expireProduct", - html: ` + if (expiredProduct.length === 0) { + return res?.status(204).json({ + message: "No Expired Products To Update", + }); + } + const updatePromise = expiredProduct.map(async (product) => { + const userId = product.Vendor.userId; + const userEmail = await User.findByPk(userId); + product.update({ expired: true, available: false }).then(async () => { + const transporter = nodemailer.createTransport({ + service: "gmail", + secure: true, + auth: { + user: process.env.EMAIL, + pass: process.env.EMAIL_PASS, + }, + }); + let mailOptions = { + from: process.env.EMAIL, + to: userEmail?.email, + subject: "expireProduct", + html: ` @@ -254,17 +254,17 @@ export const checkExpiredProducts = async (req?: Request, res?: Response) => { `, - }; - await transporter.sendMail(mailOptions); - }); - }); - await Promise.all(updatePromise); - res - ?.status(200) - .json({ message: "Check Expired Product Successfully And Sent Emails" }); - } catch (error: any) { - console.log(error.message); + }; + await transporter.sendMail(mailOptions); + }); + }); + await Promise.all(updatePromise); + res + ?.status(200) + .json({ message: "Check Expired Product Successfully And Sent Emails" }); + } catch (error: any) { + console.log(error.message); - return res?.status(500).json({ error: error.message }); - } + return res?.status(500).json({ error: error.message }); + } }; diff --git a/src/routes/checkout.router.ts b/src/routes/checkout.router.ts index ab685e4..2ed0fbe 100644 --- a/src/routes/checkout.router.ts +++ b/src/routes/checkout.router.ts @@ -1,20 +1,72 @@ -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() +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("/checkout", createOrder); router.post("/payment/:id", VerifyAccessToken, checkout); -router.post('/webhook', express.raw({ type: 'application/json' }), webhook); +router.post("/webhook", express.raw({ type: "application/json" }), webhook); -router.get("/success", async(req:Request,res:Response)=>{ - res.send("Succesfully") +router.get("/success", async (req: Request, res: Response) => { + res.send(` + + + + + Payment Successful + + + +
+

Payment Successful!

+

Thank you for your purchase. Your payment has been processed successfully.

+ Return to Home +
+ + + `); }); -router.get("/cancel", async(req:Request,res:Response)=>{ - res.send("Cancel") +router.get("/cancel", async (req: Request, res: Response) => { + res.send("Cancel"); }); -export default router \ No newline at end of file +export default router; diff --git a/src/services/emailService.ts b/src/services/emailService.ts new file mode 100644 index 0000000..524cb89 --- /dev/null +++ b/src/services/emailService.ts @@ -0,0 +1,188 @@ +import nodemailer from "nodemailer"; +import Product from "../database/models/product"; +import Vendor from "../database/models/vendor"; +import Order from "../database/models/order"; +import User from "../database/models/user"; +import { Op } from "sequelize"; +import dotenv from "dotenv"; + +dotenv.config(); + +const transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: process.env.EMAIL, + pass: process.env.EMAIL_PASS, + }, +}); + +// Send confirmation email to the customer +export const sendOrderConfirmation = async (user: any, order: any) => { + try { + const products = await Product.findAll({ + where: { + productId: { + [Op.in]: order.products.map((item: any) => item.productId), + }, + }, + include: [{ model: Vendor, as: "Vendor" }], + }); + + // Generate order summary for the user + const productDetails = products + .map( + (product: any, index: number) => ` + + ${index + 1} + ${product.name} + + ${product.price} + + ` + ) + .join(""); + + const emailContent = ` +

Order Confirmation

+

Dear ${user.name},

+

Thank you for your purchase! Here are your order details:

+ + + + + + + + + + + ${productDetails} + +
#Product NameImagePrice
+

Total Amount Paid: $${order.totalAmount}

+

Delivery Address: ${order.deliveryAddress.cell}, ${order.deliveryAddress.district}

+

We hope you enjoy your purchase!

+

Best regards,

+

Crafters E-commerce Team

+ `; + + await transporter.sendMail({ + from: `"Crafters E-commerce" <${process.env.EMAIL}>`, + to: user.email, + subject: "Order Confirmation", + html: emailContent, + }); + + console.log("Order confirmation email sent to customer!"); + } catch (error) { + console.error("Error sending order confirmation email:", error); + } +}; + +// Send notification email to vendors whose products were sold +export const notifyVendorsOfSale = async (order: any) => { + try { + const products = await Product.findAll({ + where: { + productId: { + [Op.in]: order.products.map((item: any) => item.productId), + }, + }, + include: [{ model: Vendor, as: "Vendor" }], + }); + + // Group products by vendor + const vendorProducts: any = {}; + products.forEach((product: any) => { + if (!vendorProducts[product.vendorId]) { + vendorProducts[product.vendorId] = []; + } + vendorProducts[product.vendorId].push(product); + }); + + // Fetch buyer information + const buyer = await User.findOne({ + where: { userId: order.userId }, // Assuming order.userId is the reference to the buyer + }); + + // Send an email to each vendor with details about their sold products + for (const vendorId in vendorProducts) { + const vendor = await Vendor.findOne({ + where: { vendorId }, + include: [{ model: User, as: "user" }], + }); + + if (!vendor) { + console.error(`Vendor with ID ${vendorId} not found.`); + continue; // Use continue to proceed to the next vendor + } + + const soldProducts = vendorProducts[vendorId]; + + const productDetails = soldProducts + .map( + (product: any, index: number) => ` + + ${index + 1} + ${product.name} + + ${product.price} + ${ + order.products.find((p: any) => p.productId === product.productId) + .quantity + } + + ` + ) + .join(""); + + const vendorEmailContent = ` +

Products Sold Notification

+

Dear ${vendor.storeName},

+

The following products from your store have been sold:

+ + + + + + + + + + + + ${productDetails} + +
#Product NameImagePriceQuantity Sold
+

Total Amount for these Products: $${soldProducts.reduce( + (total: number, product: any) => + total + + product.price * + order.products.find((p: any) => p.productId === product.productId) + .quantity, + 0 + )}

+

Buyer Information:

+

Name: ${buyer?.name}

+

Email: ${buyer?.email}

+

Address: ${order.deliveryAddress.cell}, ${ + order.deliveryAddress.district + }

+

We hope you continue enjoying sales through Crafters E-commerce!

+

Best regards,

+

Crafters E-commerce Team

+ `; + + await transporter.sendMail({ + from: `"Crafters E-commerce" <${process.env.EMAIL}>`, + to: vendor.user?.email ?? "", // Uses optional chaining to avoid accessing 'email' if 'user' is undefined + subject: "Products Sold", + html: vendorEmailContent, + }); + + console.log(`Email notification sent to vendor: ${vendor.storeName}`); + } + } catch (error) { + console.error("Error sending vendor notification emails:", error); + } +}; diff --git a/src/services/paymentService.ts b/src/services/paymentService.ts index 02ea506..e17371b 100644 --- a/src/services/paymentService.ts +++ b/src/services/paymentService.ts @@ -1,12 +1,16 @@ -import Order from "../database/models/order" -import Product from "../database/models/product" +import Order from "../database/models/order"; +import Product from "../database/models/product"; +import User from "../database/models/user"; - -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 +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; +}; +export const findUserById = async (userId: any) => { + const user = await User.findByPk(userId); + return user; +}; diff --git a/src/services/rolesService.ts b/src/services/rolesService.ts index 10e5761..2434b0f 100644 --- a/src/services/rolesService.ts +++ b/src/services/rolesService.ts @@ -3,11 +3,10 @@ import User from "../database/models/user"; import nodemailer from "nodemailer"; - function toCapital(string: string): string { return string .split(" ") - .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(" "); } @@ -104,14 +103,16 @@ export const approveVendorRequest = async (userId: string) => {

Hello ${toCapital(user.name)},

We are excited to inform you that your request to become a vendor has been approved!

You can now start listing your products and take advantage of our vendor features.

- Get Started + Get Started

If you have any questions or need assistance, feel free to contact our support team.

Welcome aboard and happy selling!

Best regards,
Crafter Team

@@ -124,7 +125,7 @@ export const approveVendorRequest = async (userId: string) => { } }; -export const rejectVendorRequest = async (userId: string, message: string) => { +export const rejectVendorRequest = async (userId: string, message: string) => { const vendor = await Vendor.findOne({ where: { userId: userId } }); const user: any = await User.findOne({ where: { userId: userId } }); @@ -223,7 +224,9 @@ export const rejectVendorRequest = async (userId: string, message: string) => { diff --git a/src/services/types.ts b/src/services/types.ts new file mode 100644 index 0000000..03652c0 --- /dev/null +++ b/src/services/types.ts @@ -0,0 +1,27 @@ +// types.ts +interface Address { + street: string; + city: string; + country: string; +} + +interface ProductItem { + productId: string; + quantity: number; +} + +interface Order { + products: ProductItem[]; + totalAmount: number; + deliveryAddress: Address; +} + +interface User { + name: string; + email: string; +} + +interface Vendor { + storeName: string; + user: User; +}