parent
1ce941b31c
commit
15b8d229d4
29 changed files with 17 additions and 1135 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,13 +0,0 @@ |
||||
// add-to-cart.dto.ts
|
||||
import { isInt, IsInt, IsNotEmpty, IsNumber, min, Min } from 'class-validator'; |
||||
|
||||
export class AddToCartDto { |
||||
@IsInt()
|
||||
@IsNotEmpty() |
||||
productId: number; |
||||
|
||||
@IsInt() |
||||
@IsNotEmpty() |
||||
@Min(1) |
||||
quantity: number; |
||||
} |
@ -1,8 +0,0 @@ |
||||
// update-cart.dto.ts
|
||||
import { IsInt, Min } from 'class-validator'; |
||||
|
||||
export class UpdateCartDto { |
||||
@IsInt() |
||||
@Min(1) |
||||
quantity: number; |
||||
} |
@ -1,47 +0,0 @@ |
||||
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"; |
||||
|
||||
@Table |
||||
export class Cart extends Model<Cart> { |
||||
@ForeignKey(() => User) |
||||
@Column |
||||
userId: number; |
||||
|
||||
@BelongsTo(() => User, { onDelete: "CASCADE" }) |
||||
user: User; |
||||
|
||||
@ForeignKey(() => Product) |
||||
@Column |
||||
productId: number; |
||||
|
||||
@BelongsTo(() => Product, { onDelete: "CASCADE" }) |
||||
product: Product; |
||||
|
||||
@ForeignKey(() => Invoice) |
||||
@Column |
||||
invoiceId: number; |
||||
|
||||
@BelongsTo(() => Invoice, { onDelete: "CASCADE" }) |
||||
invoice: Invoice; |
||||
|
||||
@Column({ |
||||
type: DataType.INTEGER, |
||||
allowNull: true, |
||||
}) |
||||
quantity: number; |
||||
|
||||
@Column({ |
||||
type: DataType.INTEGER, |
||||
allowNull: true, |
||||
}) |
||||
productPrice: number; |
||||
|
||||
@Column({ |
||||
type: DataType.ENUM("open", "closed"), |
||||
allowNull: false, |
||||
defaultValue: "open", |
||||
}) |
||||
status: "open" | "closed"; |
||||
} |
@ -1,37 +0,0 @@ |
||||
import { |
||||
Table, |
||||
Column, |
||||
ForeignKey, |
||||
BelongsTo, |
||||
DataType, |
||||
Model, |
||||
HasMany, |
||||
} from "sequelize-typescript"; |
||||
import { User } from "../../users/entities/user.entity"; |
||||
import { Cart } from "src/cart/entities/cart.entity"; |
||||
|
||||
@Table |
||||
export class Invoice extends Model<Invoice> { |
||||
@ForeignKey(() => User) |
||||
@Column |
||||
userId: number; |
||||
|
||||
@BelongsTo(() => User, { onDelete: "CASCADE" })
|
||||
user: User; |
||||
|
||||
@HasMany(() => Cart)
|
||||
carts: Cart[]; |
||||
|
||||
@Column({ |
||||
type: DataType.INTEGER, |
||||
allowNull: false, |
||||
}) |
||||
totalPaymentAmount: number; |
||||
|
||||
@Column({ |
||||
type: DataType.ENUM("pending", "paid"), |
||||
allowNull: false, |
||||
defaultValue: "pending",
|
||||
}) |
||||
status: string; |
||||
} |
@ -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,41 +0,0 @@ |
||||
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"; |
||||
|
||||
@Table |
||||
export class Payment extends Model<Payment> { |
||||
@ForeignKey(() => User) |
||||
@Column |
||||
userId: number; |
||||
|
||||
@BelongsTo(() => User, { onDelete: "CASCADE" }) |
||||
user: User; |
||||
|
||||
@ForeignKey(() => Wallet) |
||||
@Column |
||||
walletId: number; |
||||
|
||||
@BelongsTo(() => Wallet, { onDelete: "CASCADE" }) |
||||
wallet: Wallet; |
||||
|
||||
@Column({ |
||||
type: DataType.INTEGER, |
||||
allowNull: false, |
||||
}) |
||||
paymentAmount: number; |
||||
|
||||
|
||||
@Column({ |
||||
type: DataType.ENUM( "completed", "failed",), |
||||
allowNull: false, |
||||
defaultValue: "failed", |
||||
}) |
||||
status: string; |
||||
|
||||
@Column({ |
||||
type: DataType.DATE, |
||||
allowNull: false, |
||||
defaultValue: DataType.NOW, |
||||
}) |
||||
paymentDate: Date; |
||||
} |
@ -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,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,19 +0,0 @@ |
||||
import { Model, Table, Column, ForeignKey, BelongsTo, DataType } from 'sequelize-typescript'; |
||||
import { Wallet } from './wallet.entity'; |
||||
|
||||
@Table |
||||
export class Transaction extends Model<Transaction> { |
||||
@ForeignKey(() => Wallet) |
||||
@Column |
||||
walletId: number; |
||||
|
||||
@BelongsTo(() => Wallet, { onDelete: 'CASCADE' })
|
||||
wallet: Wallet; |
||||
|
||||
@Column({ |
||||
type: DataType.STRING, |
||||
allowNull: false, |
||||
defaultValue: "0",
|
||||
}) |
||||
amount: string; |
||||
} |
@ -1,19 +0,0 @@ |
||||
import { Model, Table, Column, ForeignKey, BelongsTo, DataType } from 'sequelize-typescript'; |
||||
import { User } from '../../users/entities/user.entity'; |
||||
|
||||
@Table |
||||
export class Wallet extends Model<Wallet> { |
||||
@ForeignKey(() => User) |
||||
@Column |
||||
userId: number; |
||||
|
||||
@BelongsTo(() => User, { onDelete: 'CASCADE' })
|
||||
user: User; |
||||
|
||||
@Column({ |
||||
type: DataType.INTEGER, |
||||
allowNull: false, |
||||
defaultValue: 0,
|
||||
}) |
||||
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