Compare commits

...

8 Commits

  1. 12
      src/app.module.ts
  2. 65
      src/cart/cart.controller.ts
  3. 28
      src/cart/cart.module.ts
  4. 6
      src/cart/cart.response.ts
  5. 210
      src/cart/cart.service.ts
  6. 26
      src/invoice/invoice.controller.ts
  7. 22
      src/invoice/invoice.module.ts
  8. 139
      src/invoice/invoice.service.ts
  9. 75
      src/payment/payment.controller.ts
  10. 23
      src/payment/payment.module.ts
  11. 56
      src/payment/payment.service.ts
  12. 45
      src/products/products.controller.ts
  13. 19
      src/products/products.module.ts
  14. 184
      src/products/products.service.ts
  15. 0
      src/shop/dto/cart/add-to-cart.dto.ts
  16. 0
      src/shop/dto/cart/update-cart.dto.ts
  17. 0
      src/shop/dto/products/create-product.dto.ts
  18. 0
      src/shop/dto/products/update-product.dto.ts
  19. 4
      src/shop/entities/cart.entity.ts
  20. 2
      src/shop/entities/invoice.entity.ts
  21. 2
      src/shop/entities/payment.entity.ts
  22. 0
      src/shop/entities/product.entity.ts
  23. 0
      src/shop/entities/transaction.entity.ts
  24. 0
      src/shop/entities/wallet.entity.ts
  25. 221
      src/shop/shop.controller.ts
  26. 26
      src/shop/shop.module.ts
  27. 647
      src/shop/shop.service.ts
  28. 0
      src/users/guard/auth.guard.ts
  29. 0
      src/users/guard/auth.module.ts
  30. 0
      src/users/guard/role.guard.ts
  31. 4
      src/users/users.controller.ts
  32. 2
      src/users/users.module.ts
  33. 97
      src/wallet/WalletService.ts
  34. 4
      src/wallet/add-balance-response.interface.ts
  35. 44
      src/wallet/wallet.controller.ts
  36. 24
      src/wallet/wallet.module.ts
  37. 98
      src/wallet/wallet.service.ts

@ -5,11 +5,8 @@ import { ConfigModule } from "@nestjs/config";
import { SequelizeModule } from "@nestjs/sequelize";
import { databaseConfig } from "../config/database.config";
import { UsersModule } from './users/users.module';
import { ProductsModule } from './products/products.module';
import { CartModule } from './cart/cart.module';
import { WalletModule } from './wallet/wallet.module';
import { InvoiceModule } from './invoice/invoice.module';
import { PaymentModule } from "./payment/payment.module";
import { ProductsModule } from './shop/shop.module';
@Module({
imports: [
@ -19,11 +16,6 @@ import { PaymentModule } from "./payment/payment.module";
SequelizeModule.forRoot(databaseConfig),
UsersModule,
ProductsModule,
CartModule,
WalletModule,
InvoiceModule,
PaymentModule,
],
controllers: [AppController],

@ -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> {

@ -8,7 +8,7 @@ import {
HasMany,
} from "sequelize-typescript";
import { User } from "../../users/entities/user.entity";
import { Cart } from "src/cart/entities/cart.entity";
import { Cart } from "./cart.entity";
@Table
export class Invoice extends Model<Invoice> {

@ -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);
}
}
}

@ -3,9 +3,9 @@ import { UsersService } from "./users.service";
import { User } from "./entities/user.entity";
import { CreateUserDto } from "./dto/create-user.dto";
import { LoginUserDto } from "./dto/login-user.dto";
import { JwtAuthGuard } from "src/guard/auth.guard";
import { JwtAuthGuard } from "src/users/guard/auth.guard";
import { UpdateUserDto } from "./dto/update-user.dto";
import { RoleGuard } from "src/guard/role.guard";
import { RoleGuard } from "src/users/guard/role.guard";
import { plainToInstance } from "class-transformer";
@Controller("user")

@ -6,7 +6,7 @@ import { User } from './entities/user.entity';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { JwtAuthGuard } from 'src/guard/auth.guard';
import { JwtAuthGuard } from 'src/users/guard/auth.guard';

@ -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…
Cancel
Save