import { Injectable } from "@nestjs/common"; import { InjectModel } from "@nestjs/sequelize"; import { Product } from "./entities/product.entity"; import { CreateProductDto } from "./dto/products/create-product.dto"; import { UpdateProductDto } from "./dto/products/update-product.dto"; import { Op } from "sequelize"; import { HttpException, HttpStatus } from "@nestjs/common"; import { Cart } from "./entities/cart.entity"; import { Wallet } from "./entities/wallet.entity"; import { Transaction } from "./entities/transaction.entity"; import { InternalServerErrorException } from "@nestjs/common"; import { Invoice } from "./entities/invoice.entity"; const ZarinpalCheckout = require("zarinpal-checkout"); @Injectable() export class ShopService { private zarinpal; constructor( @InjectModel(Product) private readonly productModel: typeof Product, @InjectModel(Cart) private readonly cartModel: typeof Cart, @InjectModel(Invoice) private readonly invoiceModel: typeof Invoice, @InjectModel(Wallet) private walletModel: typeof Wallet, @InjectModel(Transaction) private transactionModel: typeof Transaction, ) { this.zarinpal = this.initializeZarinpal(); } private initializeZarinpal() { const merchantId = "00000000-0000-0000-0000-000000000000"; // Merchant ID should be valid const sandboxMode = true; return ZarinpalCheckout.create(merchantId, sandboxMode); } ///////////////////////////////////////////products////////////////////////////////////////////// // create a new product async create(createProductDto: CreateProductDto): Promise { try { const existingProduct = await this.productModel.findOne({ where: { name: createProductDto.name }, }); if (existingProduct) { existingProduct.quantity += createProductDto.quantity || 0; await existingProduct.save(); return existingProduct; } const newProduct = await this.productModel.create(createProductDto); return newProduct; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException("An error occurred while creating or updating the product.", HttpStatus.INTERNAL_SERVER_ERROR); } } // find a product by id async findOne(id: string): Promise { try { const product = await this.productModel.findByPk(id); if (!product) { throw new HttpException("Product not found with the given ID.", HttpStatus.NOT_FOUND); } return product; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException("An unexpected error occurred while fetching the product.", HttpStatus.INTERNAL_SERVER_ERROR); } } // list of all product async findAll( search?: string, priceMin?: number, priceMax?: number, page: number = 1, limit: number = 10, ): Promise<{ products: Product[]; total: number; totalPages: number; currentPage: number }> { try { const where: Record = {}; if (search) { where.name = { [Op.iLike]: `%${search}%` }; } if (priceMin !== undefined || priceMax !== undefined) { where.price = {}; if (priceMin !== undefined) { where.price[Op.gte] = priceMin; } if (priceMax !== undefined) { where.price[Op.lte] = priceMax; } } const offset = (page - 1) * limit; const { rows: products, count: total } = await this.productModel.findAndCountAll({ where, limit, offset, attributes: { exclude: ["description", "quantity", "createdAt", "updatedAt", "tags"] }, }); const totalPages = Math.ceil(total / limit); return { products, total, totalPages, currentPage: page, }; } catch (error) { console.error("Error retrieving products:", error.message); if (error instanceof HttpException) { throw error; } throw new HttpException("An unexpected error occurred while retrieving products.", HttpStatus.INTERNAL_SERVER_ERROR); } } // update a product info async update(id: string, updateProductDto: UpdateProductDto): Promise { const product = await this.productModel.findByPk(id); if (!product) { throw new HttpException("Product not found.", HttpStatus.NOT_FOUND); } try { const { name, description, price, imageUrl, tags, quantity, brand, color, category } = updateProductDto; let updated = false; if (name && name !== product.name) { product.name = name; updated = true; } if (description && description !== product.description) { product.description = description; updated = true; } if (price !== undefined && price !== product.price) { product.price = price; updated = true; } if (imageUrl && imageUrl !== product.imageUrl) { product.imageUrl = imageUrl; updated = true; } if (tags && tags !== product.tags) { product.tags = tags; updated = true; } if (quantity !== undefined && quantity !== product.quantity) { product.quantity = quantity; updated = true; } if (brand && brand !== product.brand) { product.brand = brand; updated = true; } if (color && color !== product.color) { product.color = color; updated = true; } if (category && category !== product.category) { product.category = category; updated = true; } if (updated) { await product.save(); } return product; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException("An error occurred while updating the product.", HttpStatus.INTERNAL_SERVER_ERROR); } } // delete a product async remove(id: string): Promise<{ message: string }> { try { const product = await this.productModel.findByPk(id); if (!product) { throw new HttpException(`Product with id ${id} not found.`, HttpStatus.NOT_FOUND); } await product.destroy(); return { message: "Product deleted successfully." }; } catch (error) { console.error("Error during product deletion:", error.message); if (error instanceof HttpException) { throw error; } throw new HttpException("An unexpected error occurred while deleting the product.", HttpStatus.INTERNAL_SERVER_ERROR); } } ////////////////////////////////////////////cart///////////////////////////////////////////////// //create and add item to a cart async createAndAddItemToCart(addToCartDto: { userId: number; productId: number; quantity: number }): Promise<{ message: string; cartItem: Cart }> { const { userId, productId, quantity } = addToCartDto; if (!userId || !productId || !quantity || isNaN(Number(quantity)) || Number(quantity) <= 0) { throw new HttpException("Invalid parameters: userId, productId, and a positive quantity are required.", HttpStatus.BAD_REQUEST); } const product = await this.productModel.findByPk(productId); if (!product) { throw new HttpException("Product not found!", HttpStatus.NOT_FOUND); } if (product.quantity < quantity) { throw new HttpException("Product quantity insufficient!", HttpStatus.CONFLICT); } try { let invoice = await this.invoiceModel.findOne({ where: { userId, status: "pending" } }); if (!invoice) { invoice = await this.createInvoiceFromCart(userId); } const invoiceId = invoice.id; let cart = await this.cartModel.findOne({ where: { userId, productId, status: "open" } }); if (!cart) { cart = await this.cartModel.create({ userId, productId, invoiceId, quantity, productPrice: product.price, status: "open", }); await cart.save(); } else { cart.quantity += Number(quantity); await cart.save(); } await this.updateTotalPayment(userId); return { message: cart.id ? "Product quantity updated in cart successfully!" : "Product added to cart successfully!", cartItem: cart, }; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException("An unexpected error occurred while adding the product to cart. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR); } } // Get user's cart async getUserOpenCart(userId: number): Promise<{ cartItems: Cart[]; totalPrice: number }> { if (!userId) { throw new HttpException("User ID is required.", HttpStatus.BAD_REQUEST); } try { const cartItems = await this.cartModel.findAll({ where: { userId, status: "open" }, include: [ { model: Product, attributes: ["name", "price"], }, ], }); if (!cartItems || cartItems.length === 0) { return { cartItems: [], totalPrice: 0 }; } const totalPrice = cartItems.reduce((sum, item) => { return sum + (Number(item.productPrice) * item.quantity || 0); }, 0); return { cartItems, totalPrice }; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException("An unexpected error occurred while fetching the cart. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR); } } // Update cart item quantity async updateCart(userId: number, productId: number, quantity: number): Promise { const cartItem = await this.cartModel.findOne({ where: { userId, productId, status: "open" } }); if (!cartItem) { throw new HttpException("Product not found in the cart.", HttpStatus.NOT_FOUND); } const product = await this.productModel.findByPk(productId); if (!product) { throw new HttpException("Product not found.", HttpStatus.NOT_FOUND); } if (product.quantity < quantity) { throw new HttpException("Insufficient product quantity.", HttpStatus.CONFLICT); } try { cartItem.quantity = quantity; await cartItem.save(); await this.updateTotalPayment(userId); return cartItem; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException("An unexpected error occurred while updating the cart. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR); } } // Remove an item from cart async removeFromCart(userId: number, productId: number): Promise<{ message: string; cartItem: Cart }> { const cartItem = await this.cartModel.findOne({ where: { userId, productId, status: "open" } }); if (!cartItem) { throw new HttpException("Product not found in the cart.", HttpStatus.NOT_FOUND); } try { await cartItem.destroy(); await this.updateTotalPayment(userId); return { message: "Item deleted from your cart successfully.", cartItem }; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException("An unexpected error occurred while removing the item from the cart. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR); } } //delete whole cart by user async clearCart(userId: number) { await this.cartModel.destroy({ where: { userId, status: "open" }, }); return { message: "Cart cleared successfully" }; } //order async processOrder(userId: number, totalAmount: number): Promise<{ message: string; invoice: Invoice }> { try { const carts = await this.cartModel.findAll({ where: { userId, status: "open" } }); if (!carts || carts.length === 0) { throw new HttpException("No open carts found for this user.", HttpStatus.NOT_FOUND); } let invoice: Invoice | null = null; for (const cart of carts) { const invoiceId = cart.invoiceId; invoice = await this.invoiceModel.findOne({ where: { id: invoiceId, userId } }); if (invoice && invoice.status === "paid") { return { message: `Order for cart ID ${cart.id} has already been processed.`, invoice, }; } } await this.processPayment(userId, totalAmount); for (const cartItem of carts) { const { productId, quantity } = cartItem; const product = await this.productModel.findOne({ where: { id: productId } }); if (!product) { throw new HttpException(`Product with ID ${productId} not found.`, HttpStatus.NOT_FOUND); } if (product.quantity < quantity) { throw new HttpException(`Insufficient stock for product ID ${productId}.`, HttpStatus.BAD_REQUEST); } product.quantity -= quantity; await product.save(); } for (const cart of carts) { cart.status = "closed"; await cart.save(); } if (invoice) { invoice.status = "paid"; await invoice.save(); } return { message: "Order processed successfully!", invoice }; } catch (error) { console.error(error); if (error instanceof HttpException) { throw error; } else { throw new HttpException(`An error occurred while processing the order: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR); } } } ///////////////////////////////////////////wallet////////////////////////////////////////////// //get wallet info async getWalletInfo(userId: number) { const wallet = await this.walletModel.findOne({ where: { userId } }); if (!wallet) { const newWallet = await this.walletModel.create({ userId, balance: 0 }); return { walletId: newWallet.id, userId: newWallet.userId, balance: newWallet.balance }; } return { walletId: wallet.id, userId: wallet.userId, balance: wallet.balance }; } //get wallet balance async getBalance(userId: number) { const wallet = await this.walletModel.findOne({ where: { userId } }); if (!wallet) { throw new HttpException("Wallet not found!", HttpStatus.NOT_FOUND); } return { balance: wallet.balance }; } //getting transaction async getTransactionById(userId: number) { const wallet = await this.getWalletInfo(userId); if (!wallet) { throw new HttpException("Wallet not found for the user.", HttpStatus.NOT_FOUND); } return await this.transactionModel.findAll({ where: { walletId: wallet.walletId }, }); } //getting transaction a user (admin) async getTransactionByIdForAdmin(userId: number) { const wallet = await this.getWalletInfo(userId); if (!wallet) { throw new HttpException("Wallet not found for the user.", HttpStatus.NOT_FOUND); } return await this.transactionModel.findAll({ where: { walletId: wallet.walletId }, }); } //charge balance of wallet by user async addBalance(userId: number, amount: number) { try { const wallet = await this.walletModel.findOne({ where: { userId } }); if (wallet) { wallet.balance += Number(amount); await wallet.save(); return { message: "Balance updated successfully.", balance: wallet.balance }; } else { const newWallet = await this.walletModel.create({ userId, balance: amount }); return { message: "Wallet created and balance added successfully.", balance: newWallet.balance }; } } catch (error) { throw new HttpException("An error occurred while adding balance to the wallet.", HttpStatus.INTERNAL_SERVER_ERROR); } } //process of payment async processPayment(userId: number, amount: number): Promise { const wallet = await this.walletModel.findOne({ where: { userId } }); if (!wallet) { throw new HttpException("Please Charge your wallet", HttpStatus.NOT_FOUND); } if (wallet.balance < amount) { throw new HttpException("Insufficient funds", HttpStatus.BAD_REQUEST); } try { wallet.balance -= amount; await this.transactionModel.create({ walletId: wallet.id, amount: `-${amount}`, }); await wallet.save(); return "Payment processed successfully"; } catch (error) { console.error("Error processing payment:", error.message); throw new HttpException("An error occurred while processing the payment.", HttpStatus.INTERNAL_SERVER_ERROR); } } ///////////////////////////////////////////payment////////////////////////////////////////////// //payment request async requestPayment(amount: number, description: string, callbackUrl: string): Promise { try { const result = await this.zarinpal.PaymentRequest({ Amount: amount, CallbackURL: callbackUrl, Description: description, }); if (result.status === 100) { return result.url; } else { throw new Error(`Payment request failed with status: ${result.status}`); } } catch (error) { console.log("Error in PaymentRequest:", error.message || error); throw new InternalServerErrorException(`Error in payment request: ${error.message}`); } } //payment verify async verifyPayment(authority: string, amount: number): Promise { try { const result = await this.zarinpal.PaymentVerification({ Amount: amount, Authority: authority, }); if (result.status === 100) { return result.RefID; } else { throw new Error(`Payment verification failed with status: ${result.status}`); } } catch (error) { throw new InternalServerErrorException(`Error in payment verification: ${error.message}`); } } ///////////////////////////////////////////invoice////////////////////////////////////////////// // get invoice by user async getInvoiceByUser(userId: number) { try { if (!userId) { throw new HttpException("User ID are required.", HttpStatus.BAD_REQUEST); } const invoices = await this.invoiceModel.findAll({ where: { userId }, }); if (!invoices) { throw new HttpException("Invoice not found for this user and cart.", HttpStatus.NOT_FOUND); } return { invoices }; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException("An error occurred while retrieving the invoice.", HttpStatus.INTERNAL_SERVER_ERROR); } } //get list of invoices by admin async getInvoices() { try { const invoices = await this.invoiceModel.findAll(); if (!invoices) { throw new HttpException("Invoice not found for this user and cart.", HttpStatus.NOT_FOUND); } return { invoices }; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException("An error occurred while retrieving the invoice.", HttpStatus.INTERNAL_SERVER_ERROR); } } //get user invoices async getUserInvoices(userId: number) { try { if (!userId) { throw new HttpException("User ID are required.", HttpStatus.BAD_REQUEST); } const invoices = await this.invoiceModel.findAll({ where: { userId }, }); if (!invoices) { throw new HttpException("Invoice not found for this user and cart.", HttpStatus.NOT_FOUND); } return { invoices }; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException("An error occurred while retrieving the invoice.", HttpStatus.INTERNAL_SERVER_ERROR); } } //create invoices from cart async createInvoiceFromCart(userId: number): Promise { try { const invoice = await this.invoiceModel.create({ userId, totalPaymentAmount: 0, }); if (!invoice) { throw new HttpException("Failed to create invoice", HttpStatus.INTERNAL_SERVER_ERROR); } return invoice; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException("An error occurred while creating the invoice.", HttpStatus.INTERNAL_SERVER_ERROR); } } //update total payment async updateTotalPayment(userId: number) { const userCartItems = await this.getUserOpenCart(userId); if (!userCartItems || !userCartItems.cartItems || userCartItems.cartItems.length === 0) { throw new HttpException("Cart is empty", HttpStatus.BAD_REQUEST); } let invoice = await this.invoiceModel.findOne({ where: { userId, status: "pending" } }); if (!invoice) { throw new HttpException("Invoice not found", HttpStatus.NOT_FOUND); } invoice.totalPaymentAmount = userCartItems.totalPrice; await invoice.save(); } //get pending user invoices async getInvoicePendingByUser(userId: number): Promise { try { if (!userId) { throw new HttpException("User ID are required.", HttpStatus.BAD_REQUEST); } const invoice = await this.invoiceModel.findOne({ where: { userId, status: "pending" }, }); if (!invoice) { throw new HttpException("Invoice not found for this user and cart.", HttpStatus.NOT_FOUND); } return invoice; } catch (error) { if (error instanceof HttpException) { throw error; } throw new HttpException("An error occurred while retrieving the invoice.", HttpStatus.INTERNAL_SERVER_ERROR); } } }