Compare commits
	
		
			8 Commits 
		
	
	
		
			cc98768a90
			...
			a864ed9999
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | a864ed9999 | 9 months ago | 
|  | a79e6c68e4 | 9 months ago | 
|  | 15b8d229d4 | 9 months ago | 
|  | 1ce941b31c | 9 months ago | 
|  | 79a36d2b82 | 9 months ago | 
|  | 112e0598b5 | 9 months ago | 
|  | 89051142da | 9 months ago | 
|  | 725412c59c | 9 months ago | 
				 37 changed files with 903 additions and 1182 deletions
			
			
		| @ -1,65 +0,0 @@ | ||||
| import { Controller, Get, Post, Patch, Delete, Body, Param, UseGuards, Request, HttpException, HttpStatus } from "@nestjs/common"; | ||||
| import { CartService } from "./cart.service"; | ||||
| import { JwtAuthGuard } from "src/guard/auth.guard"; | ||||
| import { AddToCartDto } from "./dto/add-to-cart.dto"; | ||||
| import { UpdateCartDto } from "./dto/update-cart.dto"; | ||||
| import { Cart } from "./entities/cart.entity"; | ||||
| import { Invoice } from "src/invoice/entities/invoice.entity"; | ||||
| 
 | ||||
| @Controller("cart") | ||||
| export class CartController { | ||||
|   constructor(private readonly cartService: CartService) {} | ||||
| 
 | ||||
|   //create and a item to cart by user
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Post() | ||||
|   async createAndAddItemToCart(@Body() addToCartDto: AddToCartDto, @Request() req: any): Promise<{ message: string; cartItem: Cart }> { | ||||
|     const userId = req.user.id; | ||||
|     return this.cartService.createAndAddItemToCart({ ...addToCartDto, userId }); | ||||
|   } | ||||
|   //get user cart items 
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get() | ||||
|   async getUserOpenCart(@Request() req: any): Promise<{ cartItems: Cart[]; totalPrice: number }> { | ||||
|     const userId = req.user.id; | ||||
|     return this.cartService.getUserOpenCart(userId); | ||||
|   } | ||||
|   //edit quantity an item in cart by user
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Patch(":productId") | ||||
|   async updateCart(@Param("productId") productId: number, @Body() updateCartDto: UpdateCartDto, @Request() req: any): Promise<{ message: string; updatedCart: Cart }> { | ||||
|     const userId = req.user.id; | ||||
|     const updatedCart = await this.cartService.updateCart(userId, productId, updateCartDto.quantity); | ||||
|     return { | ||||
|       message: "Cart updated successfully", | ||||
|       updatedCart, | ||||
|     }; | ||||
|   } | ||||
|   //delete an item from cart by user
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Delete(":productId") | ||||
|   async removeFromCart(@Param("productId") productId: number, @Request() req: any) { | ||||
|     const userId = req.user.id; | ||||
|     return await this.cartService.removeFromCart(userId, productId); | ||||
|   } | ||||
|   //clear whole cart by user
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get("clear-cart") | ||||
|   async clearCart(@Request() req: any) { | ||||
|     const userId = req.user.id; | ||||
|     return await this.cartService.clearCart(userId); | ||||
|   } | ||||
|   //get checkout process 
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get("checkout") | ||||
|   async processOrder(@Request() req: any): Promise<{ message: string; invoice: Invoice }> { | ||||
|     const userId = req.user.id; | ||||
|     try { | ||||
|       const totalAmount = (await this.cartService.getUserOpenCart(userId)).totalPrice | ||||
|       const result = await this.cartService.processOrder(userId, totalAmount); | ||||
|       return result; | ||||
|     } catch (error) { | ||||
|       throw new HttpException(error.message || "Order processing failed.", HttpStatus.INTERNAL_SERVER_ERROR); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,28 +0,0 @@ | ||||
| import { Module, forwardRef } from "@nestjs/common"; | ||||
| import { CartService } from "./cart.service"; | ||||
| import { CartController } from "./cart.controller"; | ||||
| import { Cart } from "./entities/cart.entity"; | ||||
| import { SequelizeModule } from "@nestjs/sequelize"; | ||||
| import { User } from "src/users/entities/user.entity"; | ||||
| import { Product } from "src/products/entities/product.entity"; | ||||
| import { JwtModule } from "@nestjs/jwt"; | ||||
| import { JwtAuthGuard } from "src/guard/auth.guard"; | ||||
| import { WalletModule } from "src/wallet/wallet.module"; | ||||
| import { InvoiceModule } from "src/invoice/invoice.module"; | ||||
| import { Invoice } from "src/invoice/entities/invoice.entity"; | ||||
| 
 | ||||
| @Module({ | ||||
|   imports: [ | ||||
|     SequelizeModule.forFeature([Cart, User, Product,Invoice]), | ||||
|     JwtModule.register({ | ||||
|       secret: process.env.JWT_SECRET, | ||||
|       signOptions: { expiresIn: "1h" }, | ||||
|     }), | ||||
|     WalletModule, | ||||
|     forwardRef(()=>InvoiceModule), 
 | ||||
|   ], | ||||
|   controllers: [CartController], | ||||
|   providers: [CartService, JwtAuthGuard], | ||||
|   exports: [CartService], | ||||
| }) | ||||
| export class CartModule {} | ||||
| @ -1,6 +0,0 @@ | ||||
| import { Cart } from "./entities/cart.entity"; | ||||
| 
 | ||||
| export interface CartResponse { | ||||
|   message: string; | ||||
|   cartItem: Cart;  
 | ||||
| } | ||||
| @ -1,210 +0,0 @@ | ||||
| import { Injectable, HttpException, HttpStatus, Inject, forwardRef } from "@nestjs/common"; | ||||
| import { InjectModel } from "@nestjs/sequelize"; | ||||
| import { Cart } from "./entities/cart.entity"; | ||||
| import { Product } from "src/products/entities/product.entity"; | ||||
| import { WalletService } from "src/wallet/WalletService"; | ||||
| import { InvoiceService } from "src/invoice/invoice.service"; | ||||
| import { Invoice } from "src/invoice/entities/invoice.entity"; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class CartService { | ||||
|   constructor( | ||||
|     @InjectModel(Cart) private readonly cartModel: typeof Cart, | ||||
|     @InjectModel(Invoice) private readonly invoiceModel: typeof Invoice, | ||||
|     @InjectModel(Product) private readonly productModel: typeof Product, | ||||
|     private readonly walletService: WalletService, | ||||
|     @Inject(forwardRef(() => InvoiceService)) | ||||
|     private invoiceService: InvoiceService, | ||||
|   ) {} | ||||
|   //create a cart and add item to 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.invoiceService.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.invoiceService.updateTotalPayment(userId); | ||||
| 
 | ||||
|       return { | ||||
|         message: cart.id ? "Product quantity updated in cart successfully!" : "Product added to cart successfully!", | ||||
|         cartItem: cart, | ||||
|       }; | ||||
|     } catch (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) { | ||||
|       console.error("Error fetching cart items:", 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.invoiceService.updateTotalPayment(userId); | ||||
|       return cartItem; | ||||
|     } catch (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.invoiceService.updateTotalPayment(userId); | ||||
|       return { message: "Item deleted from your cart successfully.", cartItem }; | ||||
|     } catch (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.walletService.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); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,26 +0,0 @@ | ||||
| import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Request } from "@nestjs/common"; | ||||
| import { InvoiceService } from "./invoice.service"; | ||||
| import { JwtAuthGuard } from "src/guard/auth.guard"; | ||||
| import { RoleGuard } from "src/guard/role.guard"; | ||||
| 
 | ||||
| @Controller("invoice") | ||||
| export class InvoiceController { | ||||
|   constructor(private readonly invoiceService: InvoiceService) {} | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get() | ||||
|   async getInvoiceByUser(@Request() req) { | ||||
|     const userId = req.user.id; | ||||
|     return this.invoiceService.getInvoiceByUser(userId); | ||||
|   } | ||||
|   @UseGuards(RoleGuard) | ||||
|   @Get('list') | ||||
|   async getInvoices() { | ||||
|     return this.invoiceService.getInvoices(); | ||||
|   } | ||||
|   @UseGuards(RoleGuard) | ||||
|   @Get(':id') | ||||
|   async getUserInvoice(@Param('id') id:number) { | ||||
|     return this.invoiceService.getUserInvoices(id); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| import { Module, forwardRef } from "@nestjs/common"; | ||||
| import { SequelizeModule } from "@nestjs/sequelize"; | ||||
| import { InvoiceController } from "./invoice.controller"; | ||||
| import { InvoiceService } from "./invoice.service"; | ||||
| import { Invoice } from "./entities/invoice.entity"; | ||||
| import { CartModule } from "src/cart/cart.module"; | ||||
| import { JwtModule } from "@nestjs/jwt"; | ||||
| import { JwtAuthGuard } from "src/guard/auth.guard"; | ||||
| import { RoleGuard } from "src/guard/role.guard"; | ||||
| 
 | ||||
| @Module({ | ||||
|   imports: [SequelizeModule.forFeature([Invoice]), | ||||
|   JwtModule.register({ | ||||
|         secret: process.env.JWT_SECRET, | ||||
|         signOptions: { expiresIn: "1h" }, | ||||
|       }), | ||||
|    forwardRef(()=>CartModule)], | ||||
|   controllers: [InvoiceController], | ||||
|   providers: [InvoiceService,JwtAuthGuard,RoleGuard], | ||||
|   exports: [InvoiceService], | ||||
| }) | ||||
| export class InvoiceModule {} | ||||
| @ -1,139 +0,0 @@ | ||||
| import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from "@nestjs/common"; | ||||
| import { InjectModel } from "@nestjs/sequelize"; | ||||
| import { Invoice } from "./entities/invoice.entity"; | ||||
| import { CartService } from "src/cart/cart.service"; | ||||
| import { User } from "src/users/entities/user.entity"; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class InvoiceService { | ||||
|   constructor( | ||||
|     @InjectModel(Invoice) private readonly invoiceModel: typeof Invoice, | ||||
|     @Inject(forwardRef(() => CartService)) | ||||
|     private cartService: CartService, | ||||
|   ) {} | ||||
| 
 | ||||
|   async createInvoiceFromCart(userId: number): Promise<Invoice> { | ||||
|     const user = await User.findByPk(userId); | ||||
|     if (!user) { | ||||
|       throw new HttpException("User not found", HttpStatus.NOT_FOUND); | ||||
|     } | ||||
| 
 | ||||
|     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) { | ||||
|       console.error("Error during invoice creation:", error); | ||||
|       throw new HttpException("An error occurred while creating the invoice.", HttpStatus.INTERNAL_SERVER_ERROR); | ||||
|     } | ||||
|   } | ||||
|   async updateTotalPayment(userId: number) { | ||||
|     const user = await User.findByPk(userId); | ||||
|     if (!user) { | ||||
|       throw new HttpException("User not found", HttpStatus.NOT_FOUND); | ||||
|     } | ||||
| 
 | ||||
|     const userCartItems = await this.cartService.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(); | ||||
|   } | ||||
| 
 | ||||
|   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); | ||||
|     } | ||||
|   } | ||||
|   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); | ||||
|     } | ||||
|   } | ||||
|   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); | ||||
|     } | ||||
|   } | ||||
|   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); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,75 +0,0 @@ | ||||
| import { Controller, Post, Body, Param, Get, Query, UseGuards, Request } from "@nestjs/common"; | ||||
| import { PaymentService } from "./payment.service"; | ||||
| import { InvoiceService } from "../invoice/invoice.service"; | ||||
| import { WalletService } from "src/wallet/WalletService"; | ||||
| import { console } from "inspector"; | ||||
| import { InjectModel } from "@nestjs/sequelize"; | ||||
| import { Payment } from "./entities/payment.entity"; | ||||
| import { JwtAuthGuard } from "src/guard/auth.guard"; | ||||
| import { Transaction } from "src/wallet/entities/transaction.entity"; | ||||
| import { RoleGuard } from "src/guard/role.guard"; | ||||
| 
 | ||||
| @Controller("payment") | ||||
| export class PaymentController { | ||||
|   constructor( | ||||
|     @InjectModel(Payment) private readonly paymentModel: typeof Payment, | ||||
|     private readonly walletService: WalletService, | ||||
|     private readonly paymentService: PaymentService, | ||||
|     private readonly invoiceService: InvoiceService, | ||||
|     @InjectModel(Transaction) private readonly transactionModel: typeof Transaction, | ||||
|   ) {} | ||||
| 
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Post("request") | ||||
|   async requestPayment(@Request() req) { | ||||
|     const userId = req.user.id; | ||||
|     const invoice = await this.invoiceService.getInvoicePendingByUser(userId); | ||||
|     const totalAmount = invoice.totalPaymentAmount; | ||||
|     if (totalAmount < 1000) { | ||||
|       return { message: "please enter amount above 1000" }; | ||||
|     } | ||||
|     const callbackUrl = `http://localhost:3000/payment/verify?userId=${userId}&amount=${totalAmount}`; | ||||
|     const paymentUrl = await this.paymentService.requestPayment(totalAmount, "Purchase products", callbackUrl); | ||||
| 
 | ||||
|     return { url: paymentUrl }; | ||||
|   } | ||||
| 
 | ||||
|   @Get("verify") | ||||
|   async verifyPayment(@Query() query: { Authority: string; Status: string; userId: number; amount: number }): Promise<any> { | ||||
|     const { Authority, Status, userId, amount } = query; | ||||
| 
 | ||||
|     if (Status !== "OK") { | ||||
|       throw new Error("Payment failed"); | ||||
|     } | ||||
| 
 | ||||
|     if (!userId) { | ||||
|       throw new Error("User ID is required."); | ||||
|     } | ||||
|     const wallet = this.walletService.getWalletInfo(userId); | ||||
|     try { | ||||
|       const refId = await this.paymentService.verifyPayment(Authority, amount); | ||||
|       await this.walletService.addBalance(userId, amount); | ||||
|       const wallet = this.walletService.getWalletInfo(userId); | ||||
|       await this.paymentModel.create({ | ||||
|         userId, | ||||
|         walletId: (await wallet).walletId, | ||||
|         paymentAmount: amount, | ||||
|         status: "completed", | ||||
|       }); | ||||
|       await this.transactionModel.create({ | ||||
|         walletId: (await wallet).walletId, | ||||
|         amount: String(amount).startsWith("+") ? String(amount) : `+${amount}`, | ||||
|       }); | ||||
|       return { message: "Payment successful", refId }; | ||||
|     } catch (error) { | ||||
|       console.log(error); | ||||
|       await this.paymentModel.create({ | ||||
|         userId, | ||||
|         walletId: (await wallet).walletId, | ||||
|         paymentAmount: amount, | ||||
|         status: "failed", | ||||
|       }); | ||||
|       throw new Error(`Error during payment verification: ${error.message}`); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,23 +0,0 @@ | ||||
| import { Module } from '@nestjs/common'; | ||||
| import { PaymentService } from './payment.service'; | ||||
| import { PaymentController } from './payment.controller'; | ||||
| import { InvoiceService } from 'src/invoice/invoice.service'; | ||||
| import { CartModule } from 'src/cart/cart.module'; | ||||
| import { WalletModule } from 'src/wallet/wallet.module'; | ||||
| import { InvoiceModule } from 'src/invoice/invoice.module'; | ||||
| import { Payment } from './entities/payment.entity'; | ||||
| import { SequelizeModule } from '@nestjs/sequelize'; | ||||
| import { JwtModule } from '@nestjs/jwt'; | ||||
| import { Transaction } from 'src/wallet/entities/transaction.entity'; | ||||
| 
 | ||||
| @Module({ | ||||
|   imports:[SequelizeModule.forFeature([Payment,Transaction]), | ||||
|   JwtModule.register({ | ||||
|         secret: process.env.JWT_SECRET, | ||||
|         signOptions: { expiresIn: "1h" }, | ||||
|       }), | ||||
|   CartModule,WalletModule,InvoiceModule], | ||||
|   controllers: [PaymentController], | ||||
|   providers: [PaymentService], | ||||
| }) | ||||
| export class PaymentModule {} | ||||
| @ -1,56 +0,0 @@ | ||||
| import { Injectable, InternalServerErrorException } from "@nestjs/common"; | ||||
| import { InjectModel } from "@nestjs/sequelize"; | ||||
| import { Payment } from "./entities/payment.entity"; | ||||
| 
 | ||||
| const ZarinpalCheckout = require("zarinpal-checkout"); | ||||
| 
 | ||||
| @Injectable() | ||||
| export class PaymentService { | ||||
|   private zarinpal; | ||||
| 
 | ||||
|   constructor() { | ||||
|     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); | ||||
|   } | ||||
| 
 | ||||
|   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}`); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   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}`); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -1,45 +0,0 @@ | ||||
| import { Controller, Get, Post, Body, Param, Delete, Query, Put, UseGuards } from "@nestjs/common"; | ||||
| import { ProductsService } from "./products.service"; | ||||
| import { Product } from "./entities/product.entity"; | ||||
| import { CreateProductDto } from "./dto/create-product.dto"; | ||||
| import { UpdateProductDto } from "./dto/update-product.dto"; | ||||
| import { RoleGuard } from "src/guard/role.guard"; | ||||
| 
 | ||||
| @Controller("products") | ||||
| export class ProductsController { | ||||
|   constructor(private readonly productsService: ProductsService) {} | ||||
| 
 | ||||
|   @UseGuards(RoleGuard) | ||||
|   @Post() | ||||
|   async create(@Body() createProductDto: CreateProductDto) { | ||||
|     const product = await this.productsService.create(createProductDto); | ||||
|     return { | ||||
|       message: "Product created successfully!", | ||||
|       product, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   @Get() | ||||
|   async findAll(@Query() query: { search?: string; priceMin?: number; priceMax?: number }) { | ||||
|     const { search, priceMin, priceMax } = query; | ||||
|     return this.productsService.findAll(search, priceMin, priceMax); | ||||
|   } | ||||
| 
 | ||||
|   @Get(":id") | ||||
|   async findOne(@Param("id") id: string): Promise<Product> { | ||||
|     return this.productsService.findOne(id); | ||||
|   } | ||||
|   @UseGuards(RoleGuard) | ||||
|   @Put(":id") | ||||
|   async update( | ||||
|     @Param("id") id: string, | ||||
|     @Body() updateProductDto: UpdateProductDto | ||||
|   ): Promise<Product> { | ||||
|     return this.productsService.update(id, updateProductDto); | ||||
|   } | ||||
|   @UseGuards(RoleGuard) | ||||
|   @Delete(':id') | ||||
|   async remove(@Param('id') id: string): Promise<{ message: string }> { | ||||
|     return this.productsService.remove(id); | ||||
|   } | ||||
| } | ||||
| @ -1,19 +0,0 @@ | ||||
| import { Module } from "@nestjs/common"; | ||||
| import { ProductsService } from "./products.service"; | ||||
| import { ProductsController } from "./products.controller"; | ||||
| import { SequelizeModule } from "@nestjs/sequelize"; | ||||
| import { Product } from "./entities/product.entity"; | ||||
| import { RoleGuard } from "src/guard/role.guard"; | ||||
| import { JwtModule } from "@nestjs/jwt"; | ||||
| 
 | ||||
| @Module({ | ||||
|   imports: [SequelizeModule.forFeature([Product]), | ||||
|   JwtModule.register({ 
 | ||||
|     secret: process.env.JWT_SECRET, | ||||
|     signOptions: { expiresIn: '1h' }, | ||||
|   }) | ||||
| ], | ||||
|   controllers: [ProductsController], | ||||
|   providers: [ProductsService,RoleGuard], | ||||
| }) | ||||
| export class ProductsModule {} | ||||
| @ -1,184 +0,0 @@ | ||||
| import { Injectable } from "@nestjs/common"; | ||||
| import { InjectModel } from "@nestjs/sequelize"; | ||||
| import { Product } from "./entities/product.entity"; | ||||
| import { CreateProductDto } from "./dto/create-product.dto"; | ||||
| import { UpdateProductDto } from "./dto/update-product.dto"; | ||||
| import { Op } from "sequelize"; | ||||
| import { HttpException, HttpStatus } from "@nestjs/common"; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class ProductsService { | ||||
|   constructor(@InjectModel(Product) private readonly productModel: typeof Product) {} | ||||
| 
 | ||||
|   // 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) { | ||||
|       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, | ||||
|       }); | ||||
| 
 | ||||
|       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) { | ||||
|       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); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,7 +1,7 @@ | ||||
| import { Model, Table, Column, ForeignKey, BelongsTo, DataType } from "sequelize-typescript"; | ||||
| import { User } from "../../users/entities/user.entity"; | ||||
| import { Product } from "../../products/entities/product.entity"; | ||||
| import { Invoice } from "src/invoice/entities/invoice.entity"; | ||||
| import { Product } from "../../shop/entities/product.entity"; | ||||
| import { Invoice } from "./invoice.entity"; | ||||
| 
 | ||||
| @Table | ||||
| export class Cart extends Model<Cart> { | ||||
| @ -1,6 +1,6 @@ | ||||
| import { BelongsTo, Column, DataType, ForeignKey, Model, Table } from "sequelize-typescript"; | ||||
| import { User } from "src/users/entities/user.entity"; | ||||
| import { Wallet } from "src/wallet/entities/wallet.entity"; | ||||
| import { Wallet } from "./wallet.entity"; | ||||
| 
 | ||||
| @Table | ||||
| export class Payment extends Model<Payment> { | ||||
| @ -0,0 +1,221 @@ | ||||
| import { Controller, Get, Post, Body, Param, Delete, Query, Put, UseGuards, Request, Patch, HttpException, HttpStatus } from "@nestjs/common"; | ||||
| import { ShopService } from "./shop.service"; | ||||
| import { Product } from "./entities/product.entity"; | ||||
| import { CreateProductDto } from "./dto/products/create-product.dto"; | ||||
| import { UpdateProductDto } from "./dto/products/update-product.dto"; | ||||
| import { RoleGuard } from "src/users/guard/role.guard"; | ||||
| import { AddToCartDto } from "./dto/cart/add-to-cart.dto"; | ||||
| import { JwtAuthGuard } from "src/users/guard/auth.guard"; | ||||
| import { UpdateCartDto } from "./dto/cart/update-cart.dto"; | ||||
| import { InjectModel } from "@nestjs/sequelize"; | ||||
| import { Transaction } from "./entities/transaction.entity"; | ||||
| import { Payment } from "./entities/payment.entity"; | ||||
| 
 | ||||
| @Controller("shop") | ||||
| export class ShopController { | ||||
|   constructor( | ||||
|     private readonly shopService: ShopService, | ||||
|     @InjectModel(Transaction) private readonly transactionModel: typeof Transaction, | ||||
|     @InjectModel(Payment) private readonly paymentModel: typeof Payment, | ||||
|   ) {} | ||||
| 
 | ||||
|   ////////////////////////////////////////products////////////////////////////////////////
 | ||||
|   //create a new product (admin)
 | ||||
|   @UseGuards(RoleGuard) | ||||
|   @Post("product") | ||||
|   async create(@Body() createProductDto: CreateProductDto) { | ||||
|     const product = await this.shopService.create(createProductDto); | ||||
|     return { | ||||
|       message: "Product created successfully!", | ||||
|       product, | ||||
|     }; | ||||
|   } | ||||
|   //list of all product
 | ||||
|   @Get("product") | ||||
|   async findAll(@Query() query: { search?: string; priceMin?: number; priceMax?: number }) { | ||||
|     const { search, priceMin, priceMax } = query; | ||||
|     return this.shopService.findAll(search, priceMin, priceMax); | ||||
|   } | ||||
|   //get a product detail
 | ||||
|   @Get("product/:id") | ||||
|   async findOne(@Param("id") id: string) { | ||||
|     return this.shopService.findOne(id); | ||||
|   } | ||||
|   //edit a product info (admin)
 | ||||
|   @UseGuards(RoleGuard) | ||||
|   @Put("product/:id") | ||||
|   async update(@Param("id") id: string, @Body() updateProductDto: UpdateProductDto) { | ||||
|     const product = await this.shopService.update(id, updateProductDto); | ||||
|     return { | ||||
|       message: "product updated successful", | ||||
|       product, | ||||
|     }; | ||||
|   } | ||||
|   //delete a product (admin)
 | ||||
|   @UseGuards(RoleGuard) | ||||
|   @Delete("product/:id") | ||||
|   async remove(@Param("id") id: string): Promise<{ message: string }> { | ||||
|     return this.shopService.remove(id); | ||||
|   } | ||||
|   ////////////////////////////////////////cart////////////////////////////////////////
 | ||||
|   //create and a item to cart (user)
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Post("cart") | ||||
|   async createAndAddItemToCart(@Body() addToCartDto: AddToCartDto, @Request() req: any) { | ||||
|     const userId = req.user.id; | ||||
|     return this.shopService.createAndAddItemToCart({ ...addToCartDto, userId }); | ||||
|   } | ||||
|   //get user cart items(user)
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get("cart") | ||||
|   async getUserOpenCart(@Request() req: any) { | ||||
|     const userId = req.user.id; | ||||
|     return this.shopService.getUserOpenCart(userId); | ||||
|   } | ||||
|   //edit quantity an item in cart (user)
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Patch("cart/:productId") | ||||
|   async updateCart(@Param("productId") productId: number, @Body() updateCartDto: UpdateCartDto, @Request() req: any) { | ||||
|     const userId = req.user.id; | ||||
|     const updatedCart = await this.shopService.updateCart(userId, productId, updateCartDto.quantity); | ||||
|     return { | ||||
|       message: "Cart updated successfully", | ||||
|       updatedCart, | ||||
|     }; | ||||
|   } | ||||
|   //delete an item from cart (user)
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Delete("cart/:productId") | ||||
|   async removeFromCart(@Param("productId") productId: number, @Request() req: any) { | ||||
|     const userId = req.user.id; | ||||
|     return await this.shopService.removeFromCart(userId, productId); | ||||
|   } | ||||
|   //clear whole cart (user)
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get("cart/clear-cart") | ||||
|   async clearCart(@Request() req: any) { | ||||
|     const userId = req.user.id; | ||||
|     return await this.shopService.clearCart(userId); | ||||
|   } | ||||
|   //get checkout process (user)
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get("cart/checkout") | ||||
|   async processOrder(@Request() req: any) { | ||||
|     const userId = req.user.id; | ||||
|     try { | ||||
|       const totalAmount = (await this.shopService.getUserOpenCart(userId)).totalPrice; | ||||
|       const result = await this.shopService.processOrder(userId, totalAmount); | ||||
|       return result; | ||||
|     } catch (error) { | ||||
|       throw new HttpException(error.message || "Order processing failed.", HttpStatus.INTERNAL_SERVER_ERROR); | ||||
|     } | ||||
|   } | ||||
|   ////////////////////////////////////////wallet////////////////////////////////////////
 | ||||
|   //getting wallet balance (user)
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get("wallet") | ||||
|   async getBalance(@Request() req) { | ||||
|     const userId = req.user.id; | ||||
|     return this.shopService.getBalance(userId); | ||||
|   } | ||||
|   //charging wallet (user)
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Post("wallet/charge") | ||||
|   async addBalance(@Body("amount") amount: number, @Request() req) { | ||||
|     const userId = req.user.id; | ||||
|     const callbackUrl = `http://localhost:3000/payment/verify?userId=${userId}&amount=${amount}`; | ||||
|     const paymentUrl = this.shopService.requestPayment(amount, "Wallet Charge", callbackUrl); | ||||
|     return paymentUrl; | ||||
|   } | ||||
|   //get transaction (user)
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get("wallet/transaction") | ||||
|   async getTransactionById(@Request() req) { | ||||
|     const userId = req.user.id; | ||||
|     return this.shopService.getTransactionById(userId); | ||||
|   } | ||||
|   //get specific user transaction (admin)
 | ||||
|   @UseGuards(RoleGuard) | ||||
|   @Get("wallet/transaction/:id") | ||||
|   async getTransactionByIdForAdmin(@Param("id") id: number) { | ||||
|     return this.shopService.getTransactionByIdForAdmin(id); | ||||
|   } | ||||
|   ////////////////////////////////////////payment////////////////////////////////////////
 | ||||
|   //payment request
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get("payment/request") | ||||
|   async requestPayment(@Request() req) { | ||||
|     const userId = req.user.id; | ||||
|     const invoice = await this.shopService.getInvoicePendingByUser(userId); | ||||
|     const totalAmount = invoice.totalPaymentAmount; | ||||
|     if (totalAmount < 1000) { | ||||
|       return { message: "please enter amount above 1000" }; | ||||
|     } | ||||
|     const callbackUrl = `http://localhost:3000/shop/payment/verify?userId=${userId}&amount=${totalAmount}`; | ||||
|     const paymentUrl = await this.shopService.requestPayment(totalAmount, "Purchase products", callbackUrl); | ||||
| 
 | ||||
|     return { url: paymentUrl }; | ||||
|   } | ||||
|   //payment verify
 | ||||
|   @Get("payment/verify") | ||||
|   async verifyPayment(@Query() query: { Authority: string; Status: string; userId: number; amount: number }): Promise<any> { | ||||
|     const { Authority, Status, userId, amount } = query; | ||||
| 
 | ||||
|     if (Status !== "OK") { | ||||
|       throw new Error("Payment failed"); | ||||
|     } | ||||
| 
 | ||||
|     if (!userId) { | ||||
|       throw new Error("User ID is required."); | ||||
|     } | ||||
|     const wallet = this.shopService.getWalletInfo(userId); | ||||
|     try { | ||||
|       const refId = await this.shopService.verifyPayment(Authority, amount); | ||||
|       await this.shopService.addBalance(userId, amount); | ||||
|       const wallet = this.shopService.getWalletInfo(userId); | ||||
|       await this.paymentModel.create({ | ||||
|         userId, | ||||
|         walletId: (await wallet).walletId, | ||||
|         paymentAmount: amount, | ||||
|         status: "completed", | ||||
|       }); | ||||
|       await this.transactionModel.create({ | ||||
|         walletId: (await wallet).walletId, | ||||
|         amount: String(amount).startsWith("+") ? String(amount) : `+${amount}`, | ||||
|       }); | ||||
|       return { message: "Payment successful", refId }; | ||||
|     } catch (error) { | ||||
|       if(error instanceof HttpException){ | ||||
|         throw error | ||||
|       } | ||||
|       await this.paymentModel.create({ | ||||
|         userId, | ||||
|         walletId: (await wallet).walletId, | ||||
|         paymentAmount: amount, | ||||
|         status: "failed", | ||||
|       }); | ||||
|       throw new Error(`Error during payment verification: ${error.message}`); | ||||
|     } | ||||
|   } | ||||
|   ////////////////////////////////////////invoice////////////////////////////////////////
 | ||||
|   //get invoice (user)
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get('invoice') | ||||
|   async getInvoiceByUser(@Request() req) { | ||||
|     const userId = req.user.id; | ||||
|     return this.shopService.getInvoiceByUser(userId); | ||||
|   } | ||||
|   //get invoices list (admin)
 | ||||
|   @UseGuards(RoleGuard) | ||||
|   @Get('invoice/list') | ||||
|   async getInvoices() { | ||||
|     return this.shopService.getInvoices(); | ||||
|   } | ||||
|   //get specific user invoices
 | ||||
|   @UseGuards(RoleGuard) | ||||
|   @Get('invoice/:id') | ||||
|   async getUserInvoice(@Param('id') id:number) { | ||||
|     return this.shopService.getUserInvoices(id); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,26 @@ | ||||
| import { Module } from "@nestjs/common"; | ||||
| import { ShopService } from "./shop.service"; | ||||
| import { ShopController } from "./shop.controller"; | ||||
| import { SequelizeModule } from "@nestjs/sequelize"; | ||||
| import { Product } from "./entities/product.entity"; | ||||
| import { RoleGuard } from "src/users/guard/role.guard"; | ||||
| import { JwtModule } from "@nestjs/jwt"; | ||||
| import { Cart } from "./entities/cart.entity"; | ||||
| import { JwtAuthGuard } from "src/users/guard/auth.guard"; | ||||
| import { Wallet } from "./entities/wallet.entity"; | ||||
| import { Transaction } from "./entities/transaction.entity"; | ||||
| import { Payment } from "./entities/payment.entity"; | ||||
| import { Invoice } from "./entities/invoice.entity"; | ||||
| 
 | ||||
| @Module({ | ||||
|   imports: [ | ||||
|     SequelizeModule.forFeature([Product, Cart, Invoice, Wallet, Transaction, Payment]), | ||||
|     JwtModule.register({ | ||||
|       secret: process.env.JWT_SECRET, | ||||
|       signOptions: { expiresIn: "1h" }, | ||||
|     }), | ||||
|   ], | ||||
|   controllers: [ShopController], | ||||
|   providers: [ShopService, RoleGuard, JwtAuthGuard], | ||||
| }) | ||||
| export class ProductsModule {} | ||||
| @ -0,0 +1,647 @@ | ||||
| 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); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,97 +0,0 @@ | ||||
| import { Injectable, HttpException, HttpStatus } from "@nestjs/common"; | ||||
| import { InjectModel } from "@nestjs/sequelize"; | ||||
| import { AddBalanceResponse } from "./add-balance-response.interface"; | ||||
| import { Transaction } from "./entities/transaction.entity"; | ||||
| import { Wallet } from "./entities/wallet.entity"; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class WalletService { | ||||
|   constructor( | ||||
|     @InjectModel(Wallet) private walletModel: typeof Wallet, | ||||
|     @InjectModel(Transaction) private transactionModel: typeof Transaction, | ||||
|   ) {} | ||||
|   //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 }; | ||||
|   } | ||||
|   //charge balance of wallet by user
 | ||||
|   async addBalance(userId: number, amount: number): Promise<AddBalanceResponse> { | ||||
|     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); | ||||
|     } | ||||
|   } | ||||
|   //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 }, | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| @ -1,4 +0,0 @@ | ||||
| export interface AddBalanceResponse { | ||||
|   message: string; | ||||
|   balance: number; | ||||
| } | ||||
| @ -1,44 +0,0 @@ | ||||
| import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Request, forwardRef, Inject } from "@nestjs/common"; | ||||
| import { WalletService } from "./WalletService"; | ||||
| import { JwtAuthGuard } from "src/guard/auth.guard"; | ||||
| import { PaymentService } from "src/payment/payment.service"; | ||||
| import { RoleGuard } from "src/guard/role.guard"; | ||||
| 
 | ||||
| @Controller("wallet") | ||||
| export class WalletController { | ||||
|   constructor( | ||||
|     private readonly walletService: WalletService, | ||||
|     @Inject(forwardRef(() => PaymentService)) | ||||
|     private paymentService: PaymentService, | ||||
|   ) {} | ||||
| 
 | ||||
|   //getting wallet balance by user
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get() | ||||
|   async getBalance(@Request() req) { | ||||
|     const userId = req.user.id; | ||||
|     return this.walletService.getBalance(userId); | ||||
|   } | ||||
| 
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Post("charge") | ||||
|   async addBalance(@Body("amount") amount: number, @Request() req) { | ||||
|     const userId = req.user.id; | ||||
|     const callbackUrl = `http://localhost:3000/payment/verify?userId=${userId}&amount=${amount}`; | ||||
|     const paymentUrl = this.paymentService.requestPayment(amount, "Wallet Charge", callbackUrl); | ||||
|     return paymentUrl; | ||||
|   } | ||||
| 
 | ||||
|   @UseGuards(JwtAuthGuard) | ||||
|   @Get("transaction") | ||||
|   async getTransactionById(@Request() req) { | ||||
|     const userId = req.user.id; | ||||
|     return this.walletService.getTransactionById(userId); | ||||
|   } | ||||
| 
 | ||||
|   @UseGuards(RoleGuard) | ||||
|   @Get("transaction/:id") | ||||
|   async getTransactionByIdForAdmin(@Param("id") id: number) { | ||||
|     return this.walletService.getTransactionByIdForAdmin(id); | ||||
|   } | ||||
| } | ||||
| @ -1,24 +0,0 @@ | ||||
| import { Module } from "@nestjs/common"; | ||||
| import { WalletService } from "./WalletService"; | ||||
| import { WalletController } from "./wallet.controller"; | ||||
| import { Wallet } from "./entities/wallet.entity"; | ||||
| import { SequelizeModule } from "@nestjs/sequelize"; | ||||
| import { JwtModule } from "@nestjs/jwt"; | ||||
| import { RoleGuard } from "src/guard/role.guard"; | ||||
| import { JwtAuthGuard } from "src/guard/auth.guard"; | ||||
| import { PaymentService } from "src/payment/payment.service"; | ||||
| import { Transaction } from "./entities/transaction.entity"; | ||||
| 
 | ||||
| @Module({ | ||||
|   imports: [ | ||||
|     SequelizeModule.forFeature([Wallet, Transaction]), | ||||
|     JwtModule.register({ | ||||
|       secret: process.env.JWT_SECRET, | ||||
|       signOptions: { expiresIn: "1h" }, | ||||
|     }), | ||||
|   ], | ||||
|   controllers: [WalletController], | ||||
|   providers: [WalletService, JwtAuthGuard, RoleGuard, PaymentService], | ||||
|   exports: [WalletService], | ||||
| }) | ||||
| export class WalletModule {} | ||||
| @ -1,98 +0,0 @@ | ||||
| import { Injectable } from "@nestjs/common"; | ||||
| import { InjectModel } from "@nestjs/sequelize"; | ||||
| import { Wallet } from "./entities/wallet.entity"; | ||||
| import { HttpException, HttpStatus } from "@nestjs/common"; | ||||
| import { AddBalanceResponse } from "./add-balance-response.interface"; | ||||
| import { Transaction } from "./entities/transaction.entity"; | ||||
| 
 | ||||
| @Injectable() | ||||
| export class WalletService { | ||||
|   constructor( | ||||
|     @InjectModel(Wallet) private walletModel: typeof Wallet, | ||||
|     @InjectModel(Transaction) private transactionModel: typeof Transaction, | ||||
|   ) {} | ||||
|   //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 }; | ||||
|   } | ||||
|   //charge balance of wallet by user
 | ||||
|   async addBalance(userId: number, amount: number): Promise<AddBalanceResponse> { | ||||
|     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); | ||||
|     } | ||||
|   } | ||||
|   //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 }, | ||||
|     }); | ||||
|   } | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue