Compare commits

..

No commits in common. 'ee10b49cf8a36a34bd0c44b722b9406f5b65f1d1' and '9e665f8710599849458beb738c56a4ff30d404d0' have entirely different histories.

  1. 1
      migrations/20250104074851-create-user.js
  2. 2
      migrations/20250104094702-create-admin.js
  3. 8
      migrations/20250104112403-create-product.js
  4. 54
      migrations/20250105062732-create-invoice.js
  5. 31
      migrations/20250105063054-create-cart.js
  6. 22
      src/cart/cart.controller.ts
  7. 15
      src/cart/cart.module.ts
  8. 126
      src/cart/cart.service.ts
  9. 2
      src/cart/dto/add-to-cart.dto.ts
  10. 24
      src/cart/entities/cart.entity.ts
  11. 31
      src/invoice/entities/invoice.entity.ts
  12. 14
      src/invoice/invoice.controller.ts
  13. 14
      src/invoice/invoice.module.ts
  14. 67
      src/invoice/invoice.service.ts
  15. 4
      src/main.ts
  16. 74
      src/payment/payment.controller.ts
  17. 4
      src/payment/payment.module.ts
  18. 32
      src/payment/payment.service.ts
  19. 19
      src/wallet/wallet.controller.ts
  20. 1
      src/wallet/wallet.module.ts
  21. 16
      src/wallet/wallet.service.ts

@ -3,7 +3,6 @@
/** @type {import('sequelize-cli').Migration} */ /** @type {import('sequelize-cli').Migration} */
module.exports = { module.exports = {
async up(queryInterface, Sequelize) { async up(queryInterface, Sequelize) {
await queryInterface.dropTable('Users',{cascade:true})
await queryInterface.createTable('Users', { await queryInterface.createTable('Users', {
id: { id: {
allowNull: false, allowNull: false,

@ -2,8 +2,6 @@
module.exports = { module.exports = {
up: async (queryInterface, Sequelize) => { up: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Admins', { cascade: true });
await queryInterface.createTable('Admins', { await queryInterface.createTable('Admins', {
id: { id: {
allowNull: false, allowNull: false,

@ -1,10 +1,8 @@
"use strict"; 'use strict';
module.exports = { module.exports = {
up: async (queryInterface, Sequelize) => { up: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Products", { cascade: true }); await queryInterface.createTable('Products', {
await queryInterface.createTable("Products", {
id: { id: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
allowNull: false, allowNull: false,
@ -62,6 +60,6 @@ module.exports = {
}, },
down: async (queryInterface, Sequelize) => { down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Products"); await queryInterface.dropTable('Products');
}, },
}; };

@ -1,54 +0,0 @@
"use strict";
module.exports = {
up: async (queryInterface, Sequelize) => {
const tableExists = await queryInterface
.describeTable("Invoices")
.then(() => true)
.catch(() => false);
if (tableExists) {
await queryInterface.dropTable("Invoices", { cascade: true });
}
await queryInterface.createTable("Invoices", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
userId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: "Users",
key: "id",
},
onDelete: "CASCADE",
},
totalPaymentAmount: {
type: Sequelize.FLOAT,
allowNull: false,
},
status: {
type: Sequelize.ENUM("pending", "paid"),
allowNull: false,
defaultValue: "pending",
},
createdAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.fn("NOW"),
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false,
defaultValue: Sequelize.fn("NOW"),
},
});
},
down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Invoices", { cascade: true });
},
};

@ -2,13 +2,12 @@
module.exports = { module.exports = {
up: async (queryInterface, Sequelize) => { up: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Carts", { cascade: true });
await queryInterface.createTable('Carts', { await queryInterface.createTable('Carts', {
id: { id: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
allowNull: false,
autoIncrement: true, autoIncrement: true,
primaryKey: true, primaryKey: true,
allowNull: false,
}, },
userId: { userId: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
@ -21,44 +20,38 @@ module.exports = {
}, },
productId: { productId: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
allowNull: true, allowNull: false,
references: { references: {
model: 'Products', model: 'Products',
key: 'id', key: 'id',
}, },
onDelete: 'CASCADE', onDelete: 'CASCADE',
}, },
invoiceId: {
type: Sequelize.INTEGER,
allowNull: true,
references: {
model: 'Invoices',
key: 'id',
},
onDelete: 'CASCADE',
},
quantity: { quantity: {
type: Sequelize.INTEGER, type: Sequelize.INTEGER,
allowNull: true, allowNull: false,
}, },
productPrice: { productPrice: {
type: Sequelize.DECIMAL(10, 2), type: Sequelize.DECIMAL(10, 2),
allowNull: true, allowNull: false,
},
totalPrice: {
type: Sequelize.DECIMAL(10, 2),
allowNull: false,
}, },
status: { productName: {
type: Sequelize.ENUM('open', 'closed'), type: Sequelize.STRING,
allowNull: false, allowNull: false,
defaultValue: 'open',
}, },
createdAt: { createdAt: {
type: Sequelize.DATE, type: Sequelize.DATE,
allowNull: false, allowNull: false,
defaultValue: Sequelize.fn('NOW'), defaultValue: Sequelize.NOW,
}, },
updatedAt: { updatedAt: {
type: Sequelize.DATE, type: Sequelize.DATE,
allowNull: false, allowNull: false,
defaultValue: Sequelize.fn('NOW'), defaultValue: Sequelize.NOW,
}, },
}); });
}, },

@ -1,21 +1,19 @@
import { Controller, Get, Post, Patch, Delete, Body, Param, UseGuards, Request, HttpException, HttpStatus } from "@nestjs/common"; import { Controller, Get, Post, Patch, Delete, Body, Param, UseGuards, Request } from "@nestjs/common";
import { CartService } from "./cart.service"; import { CartService } from "./cart.service";
import { JwtAuthGuard } from "src/guard/auth.guard"; import { JwtAuthGuard } from "src/guard/auth.guard";
import { AddToCartDto } from "./dto/add-to-cart.dto"; import { AddToCartDto } from "./dto/add-to-cart.dto";
import { UpdateCartDto } from "./dto/update-cart.dto"; import { UpdateCartDto } from "./dto/update-cart.dto";
import { Cart } from "./entities/cart.entity"; import { Cart } from "./entities/cart.entity";
import { Invoice } from "src/invoice/entities/invoice.entity";
@Controller("cart") @Controller("cart")
export class CartController { export class CartController {
constructor(private readonly cartService: CartService) {} constructor(private readonly cartService: CartService) {}
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Post() @Post()
async createAndAddItemToCart(@Body() addToCartDto: AddToCartDto, @Request() req: any): Promise<{ message: string; cartItem: Cart }> { async addToCart(@Body() addToCartDto: AddToCartDto, @Request() req: any): Promise<{ message: string; cartItem: Cart }> {
const userId = req.user.id; const userId = req.user.id;
return this.cartService.createAndAddItemToCart({ ...addToCartDto, userId }); return this.cartService.addToCart({ ...addToCartDto, userId });
} }
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@ -45,18 +43,4 @@ export class CartController {
message: "Product removed from cart successfully", message: "Product removed from cart successfully",
}; };
} }
@Post(":userId/checkout")
async processOrder(@Param("userId") userId: number, @Body("totalAmount") totalAmount: number): Promise<{ message: string; invoice: Invoice }> {
if (!totalAmount || totalAmount <= 0) {
throw new HttpException("Invalid total amount.", HttpStatus.BAD_REQUEST);
}
try {
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,4 +1,4 @@
import { Module, forwardRef } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { CartService } from "./cart.service"; import { CartService } from "./cart.service";
import { CartController } from "./cart.controller"; import { CartController } from "./cart.controller";
import { Cart } from "./entities/cart.entity"; import { Cart } from "./entities/cart.entity";
@ -7,22 +7,15 @@ import { User } from "src/users/entities/user.entity";
import { Product } from "src/products/entities/product.entity"; import { Product } from "src/products/entities/product.entity";
import { JwtModule } from "@nestjs/jwt"; import { JwtModule } from "@nestjs/jwt";
import { JwtAuthGuard } from "src/guard/auth.guard"; 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({ @Module({
imports: [ imports: [SequelizeModule.forFeature([Cart,User,Product]),
SequelizeModule.forFeature([Cart, User, Product,Invoice]),
JwtModule.register({ JwtModule.register({
secret: process.env.JWT_SECRET, secret: process.env.JWT_SECRET,
signOptions: { expiresIn: "1h" }, signOptions: { expiresIn: '1h' },
}), })
WalletModule,
forwardRef(()=>InvoiceModule),
], ],
controllers: [CartController], controllers: [CartController],
providers: [CartService,JwtAuthGuard], providers: [CartService,JwtAuthGuard],
exports: [CartService],
}) })
export class CartModule {} export class CartModule {}

@ -1,27 +1,24 @@
import { Injectable, HttpException, HttpStatus, Inject, forwardRef } from "@nestjs/common"; import { Injectable, HttpException, HttpStatus } from "@nestjs/common";
import { InjectModel } from "@nestjs/sequelize"; import { InjectModel } from "@nestjs/sequelize";
import { Cart } from "./entities/cart.entity"; import { Cart } from "./entities/cart.entity";
import { CartResponse } from "./cart.response";
import { User } from "src/users/entities/user.entity";
import { Product } from "src/products/entities/product.entity"; import { Product } from "src/products/entities/product.entity";
import { WalletService } from "src/wallet/wallet.service"; import { console } from "inspector";
import { InvoiceService } from "src/invoice/invoice.service";
import { Invoice } from "src/invoice/entities/invoice.entity";
@Injectable() @Injectable()
export class CartService { export class CartService {
constructor( constructor(
@InjectModel(Cart) private readonly cartModel: typeof Cart, @InjectModel(Cart) private readonly cartModel: typeof Cart,
@InjectModel(Invoice) private readonly invoiceModel: typeof Invoice,
@InjectModel(Product) private readonly productModel: typeof Product, @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 }> { // Add product to cart
async addToCart(addToCartDto: { userId: number; productId: number; quantity: number }): Promise<{ message: string; cartItem: Cart }> {
const { userId, productId, quantity } = addToCartDto; const { userId, productId, quantity } = addToCartDto;
if (!userId || !productId || !quantity || isNaN(Number(quantity)) || Number(quantity) <= 0) { if (!userId || !productId || !quantity) {
throw new HttpException("Invalid parameters: userId, productId, and a positive quantity are required.", HttpStatus.BAD_REQUEST); throw new HttpException("Missing required parameters: userId, productId, and quantity are required.", HttpStatus.BAD_REQUEST);
} }
const product = await this.productModel.findByPk(productId); const product = await this.productModel.findByPk(productId);
@ -29,49 +26,37 @@ export class CartService {
throw new HttpException("Product not found!", HttpStatus.NOT_FOUND); throw new HttpException("Product not found!", HttpStatus.NOT_FOUND);
} }
let invoice = await this.invoiceModel.findOne({ where: { userId, status: "pending" } }); const existingCartItem = await this.cartModel.findOne({
if (!invoice) { where: { userId, productId },
invoice = await this.invoiceService.createInvoiceFromCart(userId);
}
const invoiceId = invoice.id;
let cart = await this.cartModel.findOne({ where: { userId, productId, status: "open" } });
console.log(cart);
if (!cart) {
cart = await this.cartModel.create({
userId,
productId,
invoiceId,
quantity,
productPrice: product.price,
status: "open",
}); });
await cart.save();
} else { if (existingCartItem) {
cart.quantity += Number(quantity); existingCartItem.quantity += Number(quantity);
await cart.save(); existingCartItem.totalPrice = existingCartItem.quantity * existingCartItem.productPrice;
await existingCartItem.save();
return {
message: "Product quantity updated in cart successfully!",
cartItem: existingCartItem,
};
} }
await this.invoiceService.updateTotalPayment(userId); const newCartItem = await this.cartModel.create({ userId, productId, quantity, productPrice: product.price, totalPrice: product.price * quantity, productName: product.name });
return { return {
message: cart.id ? "Product quantity updated in cart successfully!" : "Product added to cart successfully!", message: "Product added to cart successfully!",
cartItem: cart, cartItem: newCartItem,
}; };
} }
// Get user's cart // Get user's cart
async getUserCart(userId: number): Promise<{ cartItems: Cart[]; totalPrice: number }> { async getUserCart(userId: number): Promise<{ cartItems: Cart[]; totalPrice: number }> {
if (!userId) {
throw new HttpException("User ID is required.", HttpStatus.BAD_REQUEST);
}
const cartItems = await this.cartModel.findAll({ const cartItems = await this.cartModel.findAll({
where: { userId }, where: { userId },
include: [ include: [
{ {
model: Product, model: Product,
attributes: [], attributes: ["id", "name", "price", "description", "imageUrl"],
}, },
], ],
}); });
@ -80,7 +65,7 @@ export class CartService {
throw new HttpException("No cart items found for the specified user.", HttpStatus.NOT_FOUND); throw new HttpException("No cart items found for the specified user.", HttpStatus.NOT_FOUND);
} }
const totalPrice = cartItems.reduce((sum, item) => sum + (Number(item.productPrice * item.quantity) || 0), 0); const totalPrice = cartItems.reduce((sum, item) => sum + Number(item.totalPrice), 0);
return { cartItems, totalPrice }; return { cartItems, totalPrice };
} }
@ -94,73 +79,20 @@ export class CartService {
} }
cartItem.quantity = quantity; cartItem.quantity = quantity;
cartItem.totalPrice = cartItem.quantity * cartItem.productPrice;
await cartItem.save(); await cartItem.save();
await this.invoiceService.updateTotalPayment(userId);
return cartItem; return cartItem;
} }
// Remove an item from cart // Remove product from cart
async removeFromCart(userId: number, productId: number): Promise<{ message: string }> { async removeFromCart(userId: number, productId: number): Promise<{ message: string }> {
const cartItem = await this.cartModel.findOne({ where: { userId, productId } }); const cartItem = await this.cartModel.findOne({ where: { userId, productId } });
if (!cartItem) { if (!cartItem) {
throw new HttpException("Product not found in the cart.", HttpStatus.NOT_FOUND); throw new HttpException("Product not found in the cart.", HttpStatus.NOT_FOUND);
} }
await cartItem.destroy(); await cartItem.destroy();
await this.invoiceService.updateTotalPayment(userId);
return { message: "Item deleted from your cart successfully." }; return { message: "Item deleted from your cart successfully." };
} }
//delete whole cart
async clearCart(userId: number): Promise<void> {
await this.cartModel.destroy({ where: { userId } });
}
//order(clearCart disable)
async processOrder(userId: number, totalAmount: number): Promise<{ message: string; invoice: Invoice }> {
try {
const cart = await this.cartModel.findOne({ where: { userId } });
if (!cart) {
throw new HttpException("Cart not found for this user.", HttpStatus.NOT_FOUND);
}
// Deducting credit from wallet
await this.walletService.processPayment(userId, totalAmount);
// Retrieve cart items
const cartItems = await this.cartModel.findAll({ where: { userId } });
if (cartItems.length === 0) {
throw new HttpException("Cart is empty.", HttpStatus.BAD_REQUEST);
}
// Process each cart item and update stock
for (const cartItem of cartItems) {
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; // Reduce stock
await product.save();
}
// Create the invoices for all cart
const invoice = await this.invoiceModel.findOne({ where: { userId, status: "pending" } });
return { message: "Order processed successfully", invoice };
} catch (error) {
console.log(error);
if (error instanceof HttpException) {
throw error;
} else {
throw new HttpException(`An error occurred while processing the order: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
} }

@ -8,6 +8,6 @@ export class AddToCartDto {
@IsInt() @IsInt()
@IsNotEmpty() @IsNotEmpty()
@Min(1) @Min(0)
quantity: number; quantity: number;
} }

@ -1,7 +1,6 @@
import { Model, Table, Column, ForeignKey, BelongsTo, DataType } from "sequelize-typescript"; import { Model, Table, Column, ForeignKey, BelongsTo, DataType } from "sequelize-typescript";
import { User } from "../../users/entities/user.entity"; import { User } from "../../users/entities/user.entity";
import { Product } from "../../products/entities/product.entity"; import { Product } from "../../products/entities/product.entity";
import { Invoice } from "src/invoice/entities/invoice.entity";
@Table @Table
export class Cart extends Model<Cart> { export class Cart extends Model<Cart> {
@ -19,29 +18,28 @@ export class Cart extends Model<Cart> {
@BelongsTo(() => Product, { onDelete: "CASCADE" }) @BelongsTo(() => Product, { onDelete: "CASCADE" })
product: Product; product: Product;
@ForeignKey(() => Invoice)
@Column
invoiceId: number;
@BelongsTo(() => Invoice, { onDelete: "CASCADE" })
invoice: Invoice;
@Column({ @Column({
type: DataType.INTEGER, type: DataType.INTEGER,
allowNull: true, allowNull: false,
}) })
quantity: number; quantity: number;
@Column({ @Column({
type: DataType.DECIMAL(10, 2), type: DataType.DECIMAL(10, 2),
allowNull: true, allowNull: false,
}) })
productPrice: number; productPrice: number;
@Column({ @Column({
type: DataType.ENUM("open", "closed"), type: DataType.DECIMAL(10, 2),
allowNull: false, allowNull: false,
defaultValue: "open",
}) })
status: "open" | "closed"; totalPrice: number;
@Column({
type: DataType.STRING,
allowNull: false,
})
productName: string;
} }

@ -1,14 +1,6 @@
import { import { Table, Model, Column, BelongsTo, ForeignKey } from "sequelize-typescript";
Table,
Column,
ForeignKey,
BelongsTo,
DataType,
Model,
HasMany,
} from "sequelize-typescript";
import { User } from "../../users/entities/user.entity"; import { User } from "../../users/entities/user.entity";
import { Cart } from "src/cart/entities/cart.entity"; import { Product } from "../../products/entities/product.entity";
@Table @Table
export class Invoice extends Model<Invoice> { export class Invoice extends Model<Invoice> {
@ -16,22 +8,9 @@ export class Invoice extends Model<Invoice> {
@Column @Column
userId: number; userId: number;
@BelongsTo(() => User, { onDelete: "CASCADE" }) @BelongsTo(() => User, { onDelete: 'CASCADE' })
user: User; user: User;
@HasMany(() => Cart) @Column
carts: Cart[]; totalAmount: number;
@Column({
type: DataType.FLOAT,
allowNull: false,
})
totalPaymentAmount: number;
@Column({
type: DataType.ENUM("pending", "paid"),
allowNull: false,
defaultValue: "pending",
})
status: string;
} }

@ -1,11 +1,17 @@
import { Controller, Get, Post, Body, Patch, Param, Delete } from "@nestjs/common"; import { Controller, Get, Post, Body, Patch, Param, Delete } from "@nestjs/common";
import { InvoiceService } from "./invoice.service"; import { InvoiceService } from "./invoice.service";
import { Invoice } from "./entities/invoice.entity";
@Controller("invoice") @Controller("invoice")
export class InvoiceController { export class InvoiceController {
constructor(private readonly invoiceService: InvoiceService) {} constructor(private readonly invoiceService: InvoiceService) {}
// @Get(":userId") @Post("create")
// async getInvoices(@Param("userId") userId: number): Promise<any> { async createInvoice(@Body() body: { userId: number; totalAmount: number }): Promise<Invoice> {
// return this.invoiceService.getInvoicesByUser(userId); const { userId, totalAmount } = body;
// } return this.invoiceService.createInvoice(userId, totalAmount);
}
@Get(":userId")
async getInvoices(@Param("userId") userId: number): Promise<any> {
return this.invoiceService.getInvoicesByUser(userId);
}
} }

@ -1,14 +1,12 @@
import { Module, forwardRef } from "@nestjs/common"; import { Module } from '@nestjs/common';
import { SequelizeModule } from "@nestjs/sequelize"; import { InvoiceService } from './invoice.service';
import { InvoiceController } from "./invoice.controller"; import { InvoiceController } from './invoice.controller';
import { InvoiceService } from "./invoice.service"; import { SequelizeModule } from '@nestjs/sequelize';
import { Invoice } from "./entities/invoice.entity"; import { Invoice } from './entities/invoice.entity';
import { CartModule } from "src/cart/cart.module";
@Module({ @Module({
imports: [SequelizeModule.forFeature([Invoice]), forwardRef(()=>CartModule)], imports : [SequelizeModule.forFeature([Invoice])],
controllers: [InvoiceController], controllers: [InvoiceController],
providers: [InvoiceService], providers: [InvoiceService],
exports: [InvoiceService],
}) })
export class InvoiceModule {} export class InvoiceModule {}

@ -1,71 +1,52 @@
import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from "@nestjs/common"; import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
import { InjectModel } from "@nestjs/sequelize"; import { InjectModel } from "@nestjs/sequelize";
import { Invoice } from "./entities/invoice.entity"; import { Invoice } from "./entities/invoice.entity";
import { CartService } from "src/cart/cart.service";
import { User } from "src/users/entities/user.entity";
import { where } from "sequelize"; import { where } from "sequelize";
@Injectable() @Injectable()
export class InvoiceService { export class InvoiceService {
constructor( constructor(@InjectModel(Invoice) private readonly invoiceModel: typeof Invoice) {}
@InjectModel(Invoice) private readonly invoiceModel: typeof Invoice,
@Inject(forwardRef(() => CartService))
private cartService: CartService,
) {}
async createInvoiceFromCart(userId: number): Promise<Invoice> { async createInvoice(userId: number, totalAmount: number): Promise<Invoice> {
const user = await User.findByPk(userId); try {
if (!user) { if (!userId) {
throw new HttpException("User not found", HttpStatus.NOT_FOUND); throw new HttpException("User id not found!", HttpStatus.BAD_REQUEST);
}
const invoice = await this.invoiceModel.create({
userId,
totalPaymentAmount: 0,
});
return invoice;
}
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.getUserCart(userId); const newInvoice = await this.invoiceModel.create({ userId, totalAmount });
if (!userCartItems || !userCartItems.cartItems || userCartItems.cartItems.length === 0) { return newInvoice;
throw new HttpException("Cart is empty", HttpStatus.BAD_REQUEST); } catch (error) {
}
let invoice = await this.invoiceModel.findOne({ where: { userId, status: "pending" } }); if (error instanceof HttpException) {
if (!invoice) { throw error;
throw new HttpException("Invoice not found", HttpStatus.NOT_FOUND);
} }
invoice.totalPaymentAmount = userCartItems.totalPrice; throw new HttpException("An error occurred while creating the invoice.", HttpStatus.INTERNAL_SERVER_ERROR);
await invoice.save();
} }
}
async getInvoicesByUser(userId: number): Promise<Invoice[]> {
async getInvoiceByUserAndCart(userId: number): Promise<Invoice> {
try { try {
if (!userId) { if (!userId) {
throw new HttpException("User ID are required.", HttpStatus.BAD_REQUEST); throw new HttpException('User ID is required.', HttpStatus.BAD_REQUEST);
} }
const invoice = await this.invoiceModel.findOne({ const invoices = await this.invoiceModel.findAll({
where: { userId, status:'pending' }, where: { userId },
}); });
if (!invoice) { if (!invoices || invoices.length === 0) {
throw new HttpException("Invoice not found for this user and cart.", HttpStatus.NOT_FOUND); throw new HttpException('No invoices found for this user.', HttpStatus.NOT_FOUND);
} }
return invoice; return invoices;
} catch (error) { } catch (error) {
if (error instanceof HttpException) { if (error instanceof HttpException) {
throw error; throw error;
} }
throw new HttpException("An error occurred while retrieving the invoice.", HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException(
'An error occurred while retrieving invoices.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
} }

@ -1,11 +1,9 @@
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common'; import { ValidationPipe } from '@nestjs/common';
import { Sequelize } from 'sequelize-typescript';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule);
const sequelize = app.get(Sequelize);
await sequelize.sync({ alter: true });
app.useGlobalPipes(new ValidationPipe()); app.useGlobalPipes(new ValidationPipe());
await app.listen(process.env.PORT ?? 3000); await app.listen(process.env.PORT ?? 3000);
} }

@ -1,59 +1,25 @@
import { Controller, Post, Body, Param, Get, Query } from "@nestjs/common"; import { Controller, Get, Query } from '@nestjs/common';
import { PaymentService } from "./payment.service"; import { PaymentService } from './payment.service';
import { CartService } from "../cart/cart.service";
import { InvoiceService } from "../invoice/invoice.service";
import { WalletService } from "src/wallet/wallet.service";
@Controller("payment") @Controller('payment')
export class PaymentController { export class PaymentController {
constructor( constructor(private readonly paymentService: PaymentService) {}
private readonly wallet: WalletService, @Get('request')
private readonly paymentService: PaymentService, async requestPayment(){
private readonly cartService: CartService, const amount = 10000;
) {} const description = 'Test payment';
const callbackUrl = 'http://localhost:3000/payment/verify';
@Post("request/:userId") const paymentUrl = await this.paymentService.requestPayment(amount, description, callbackUrl);
async requestPayment(@Param("userId") userId: number): Promise<{ url: string }> { return { paymentUrl };
const userCartItems = await this.cartService.getUserCart(userId); }
@Get('verify')
if (!userCartItems || userCartItems.cartItems.length === 0) { async verifyPayment(@Query('Authority') authority: string, @Query('Status') status: string) {
throw new Error("Cart is empty!"); if (status === 'OK') {
} const amount = 10000;
const refId = await this.paymentService.verifyPayment(authority, amount);
const totalAmount = userCartItems.totalPrice; return { success: true, refId };
} else {
const callbackUrl = `http://localhost:3000/payment/verify?userId=${userId}`; return { success: false, message: 'Payment canceled' };
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 }): Promise<any> {
const { Authority, Status, userId } = query;
if (Status !== "OK") {
throw new Error("Payment failed");
}
if (!userId) {
throw new Error("User ID is required.");
}
try {
const userCartItems = await this.cartService.getUserCart(userId);
if (!userCartItems || userCartItems.cartItems.length === 0) {
throw new Error("Cart is empty!");
}
const totalAmount = userCartItems.totalPrice;
const refId = await this.paymentService.verifyPayment(Authority, totalAmount);
await this.wallet.addBalance(userId,totalAmount)
return { message: "Payment successful", refId};
} catch (error) {
console.log(error)
throw new Error(`Error during payment verification: ${error.message}`);
} }
} }
} }

@ -1,12 +1,8 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { PaymentService } from './payment.service'; import { PaymentService } from './payment.service';
import { PaymentController } from './payment.controller'; 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';
@Module({ @Module({
imports:[CartModule,WalletModule],
controllers: [PaymentController], controllers: [PaymentController],
providers: [PaymentService], providers: [PaymentService],
}) })

@ -1,24 +1,15 @@
import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
const ZarinpalCheckout = require('zarinpal-checkout'); const ZarinpalCheckout = require('zarinpal-checkout');
@Injectable() @Injectable()
export class PaymentService { export class PaymentService {
private zarinpal; private zarinpal;
constructor( constructor() {
) { this.zarinpal = ZarinpalCheckout.create('00000000-0000-0000-0000-000000000000', true);
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> { async requestPayment(amount: number, description: string, callbackUrl: string) {
try {
const result = await this.zarinpal.PaymentRequest({ const result = await this.zarinpal.PaymentRequest({
Amount: amount, Amount: amount,
CallbackURL: callbackUrl, CallbackURL: callbackUrl,
@ -28,16 +19,10 @@ export class PaymentService {
if (result.status === 100) { if (result.status === 100) {
return result.url; return result.url;
} else { } else {
throw new Error(`Payment request failed with status: ${result.status}`); throw new Error(`Error in payment request: ${result.status}`);
} }
} catch (error) {
console.log('Error in PaymentRequest:', error);
throw new InternalServerErrorException(`Error in payment request: ${error.message}`);
} }
} async verifyPayment(authority: string, amount: number) {
async verifyPayment(authority: string, amount: number): Promise<string> {
try {
const result = await this.zarinpal.PaymentVerification({ const result = await this.zarinpal.PaymentVerification({
Amount: amount, Amount: amount,
Authority: authority, Authority: authority,
@ -46,10 +31,7 @@ export class PaymentService {
if (result.status === 100) { if (result.status === 100) {
return result.RefID; return result.RefID;
} else { } else {
throw new Error(`Payment verification failed with status: ${result.status}`); throw new Error(`Payment verification failed: ${result.status}`);
}
} catch (error) {
throw new InternalServerErrorException(`Error in payment verification: ${error.message}`);
} }
} }
} }

@ -1,16 +1,19 @@
import { Controller, Get, Post, Body, Patch, Param, Delete } from "@nestjs/common"; import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { WalletService } from "./wallet.service"; import { WalletService } from './wallet.service';
import { AddBalanceResponse } from "./add-balance-response.interface"; import { AddBalanceResponse } from './add-balance-response.interface';
@Controller("wallet") @Controller('wallet')
export class WalletController { export class WalletController {
constructor(private readonly walletService: WalletService) {} constructor(private readonly walletService: WalletService) {}
@Get(":userId") @Get(':userId')
async getBalance(@Param("userId") userId: number): Promise<number> { async getBalance(@Param('userId') userId: number): Promise<number> {
return this.walletService.getBalance(userId); return this.walletService.getBalance(userId);
} }
@Post(":userId/add") @Post(':userId/add')
async addBalance(@Param("userId") userId: number, @Body("amount") amount: number): Promise<AddBalanceResponse> { async addBalance(
@Param('userId') userId: number,
@Body('amount') amount: number
): Promise<AddBalanceResponse> {
return this.walletService.addBalance(userId, amount); return this.walletService.addBalance(userId, amount);
} }
} }

@ -8,6 +8,5 @@ import { SequelizeModule } from '@nestjs/sequelize';
imports: [SequelizeModule.forFeature([Wallet])], imports: [SequelizeModule.forFeature([Wallet])],
controllers: [WalletController], controllers: [WalletController],
providers: [WalletService], providers: [WalletService],
exports: [WalletService],
}) })
export class WalletModule {} export class WalletModule {}

@ -28,20 +28,4 @@ export class WalletService {
throw new HttpException("An error occurred while adding balance to the wallet.", HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException("An error occurred while adding balance to the wallet.", HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
async processPayment(userId: number, amount: number): Promise<string> {
const wallet = await this.walletModel.findOne({ where: { userId } });
if (!wallet) {
throw new Error("Wallet not found");
}
if (wallet.balance < amount) {
throw new Error("Insufficient funds");
}
wallet.balance -= amount;
await wallet.save();
return "Payment processed successfully";
}
} }

Loading…
Cancel
Save