You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
647 lines
21 KiB
647 lines
21 KiB
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<Product> { |
|
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<Product> { |
|
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<string, any> = {}; |
|
|
|
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<Product> { |
|
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<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); |
|
} |
|
|
|
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<string> { |
|
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<string> { |
|
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<string> { |
|
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<Invoice> { |
|
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<Invoice> { |
|
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); |
|
} |
|
} |
|
}
|
|
|