Compare commits

...

6 Commits

  1. 44
      migrations/20250104074851-create-user.js
  2. 13
      migrations/20250104094702-create-admin.js
  3. 43
      migrations/20250111110343-create-transactions.js
  4. 21
      src/admin/admin.controller.ts
  5. 63
      src/admin/admin.service.ts
  6. 5
      src/admin/entities/admin.entity.ts
  7. 17
      src/cart/cart.controller.ts
  8. 5
      src/cart/cart.service.ts
  9. 21
      src/invoice/invoice.controller.ts
  10. 12
      src/invoice/invoice.module.ts
  11. 65
      src/invoice/invoice.service.ts
  12. 36
      src/payment/payment.controller.ts
  13. 9
      src/payment/payment.module.ts
  14. 3
      src/payment/payment.service.ts
  15. 7
      src/users/entities/user.entity.ts
  16. 25
      src/users/users.controller.ts
  17. 99
      src/users/users.service.ts
  18. 19
      src/wallet/entities/transaction.entity.ts
  19. 33
      src/wallet/wallet.controller.ts
  20. 25
      src/wallet/wallet.module.ts
  21. 42
      src/wallet/wallet.service.ts

@ -1,63 +1,69 @@
'use strict'; 'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = { module.exports = {
async up(queryInterface, Sequelize) { up: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Users',{cascade:true}) await queryInterface.dropTable('Users', { cascade: true });
await queryInterface.createTable('Users', { await queryInterface.createTable('Users', {
id: { id: {
allowNull: false, type: Sequelize.INTEGER,
autoIncrement: true, autoIncrement: true,
primaryKey: true, primaryKey: true,
type: Sequelize.INTEGER allowNull: false,
}, },
email: { email: {
type: Sequelize.STRING, type: Sequelize.STRING,
unique: true, unique: true,
allowNull: false allowNull: false,
}, },
password: { password: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false allowNull: false,
}, },
role: { role: {
type: Sequelize.STRING, type: Sequelize.STRING,
defaultValue: 'user' allowNull: false,
defaultValue: 'user',
}, },
firstName: { firstName: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false allowNull: true,
}, },
lastName: { lastName: {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false allowNull: true,
}, },
username: { username: {
type: Sequelize.STRING, type: Sequelize.STRING,
unique: true, unique: true,
allowNull: false allowNull: false,
}, },
phoneNumber: { phoneNumber: {
type: Sequelize.STRING, type: Sequelize.STRING,
unique: true, unique: true,
allowNull: false allowNull: false,
}, },
gender: { gender: {
type: Sequelize.ENUM("male", "female"), type: Sequelize.ENUM('male', 'female'),
allowNull: false allowNull: false,
},
refreshToken: {
type: Sequelize.STRING,
allowNull: true,
}, },
createdAt: { createdAt: {
type: Sequelize.DATE,
allowNull: false, allowNull: false,
type: Sequelize.DATE defaultValue: Sequelize.fn('NOW'),
}, },
updatedAt: { updatedAt: {
type: Sequelize.DATE,
allowNull: false, allowNull: false,
type: Sequelize.DATE defaultValue: Sequelize.fn('NOW'),
} },
}); });
}, },
async down(queryInterface, Sequelize) { down: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Users'); await queryInterface.dropTable('Users');
} },
}; };

@ -3,13 +3,12 @@
module.exports = { module.exports = {
up: async (queryInterface, Sequelize) => { up: async (queryInterface, Sequelize) => {
await queryInterface.dropTable('Admins', { cascade: true }); await queryInterface.dropTable('Admins', { cascade: true });
await queryInterface.createTable('Admins', { await queryInterface.createTable('Admins', {
id: { id: {
allowNull: false, type: Sequelize.INTEGER,
autoIncrement: true, autoIncrement: true,
primaryKey: true, primaryKey: true,
type: Sequelize.INTEGER allowNull: false,
}, },
email: { email: {
type: Sequelize.STRING, type: Sequelize.STRING,
@ -42,6 +41,10 @@ module.exports = {
allowNull: false, allowNull: false,
unique: true, unique: true,
}, },
refreshToken: {
type: Sequelize.STRING,
allowNull: true,
},
gender: { gender: {
type: Sequelize.ENUM('male', 'female'), type: Sequelize.ENUM('male', 'female'),
allowNull: false, allowNull: false,
@ -49,12 +52,12 @@ module.exports = {
createdAt: { createdAt: {
type: Sequelize.DATE, type: Sequelize.DATE,
allowNull: false, allowNull: false,
defaultValue: Sequelize.NOW, defaultValue: Sequelize.fn('NOW'),
}, },
updatedAt: { updatedAt: {
type: Sequelize.DATE, type: Sequelize.DATE,
allowNull: false, allowNull: false,
defaultValue: Sequelize.NOW, defaultValue: Sequelize.fn('NOW'),
}, },
}); });
}, },

@ -0,0 +1,43 @@
'use strict';
module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.dropTable("Transactions", { cascade: true });
await queryInterface.createTable('Transactions', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true,
allowNull: false,
},
walletId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Wallets',
key: 'id',
},
onDelete: 'CASCADE',
},
amount: {
type: Sequelize.STRING,
allowNull: false,
defaultValue: '0',
},
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('Transactions');
},
};

@ -8,17 +8,32 @@ import { RoleGuard } from "src/guard/role.guard";
@Controller("admin") @Controller("admin")
export class AdminController { export class AdminController {
constructor(private readonly adminService: AdminService) {} constructor(private readonly adminService: AdminService) {}
// register as a admin
@Post("register") @Post("register")
async register(@Body() createAdminDto: CreateAdminDto): Promise<{message}> { async register(@Body() createAdminDto: CreateAdminDto): Promise<{ message }> {
return this.adminService.register(createAdminDto); return this.adminService.register(createAdminDto);
} }
//login as admin
@Post("login") @Post("login")
async login(@Body() loginAdminDto: LoginAdminDto): Promise<{ token: string }> { async login(@Body() loginAdminDto: LoginAdminDto): Promise<{ accessToken; refreshToken }> {
return this.adminService.login(loginAdminDto); return this.adminService.login(loginAdminDto);
} }
//logout user
@UseGuards(RoleGuard)
@Get("logout")
async logout(@Request() req) {
const userId = req.user.id;
return this.adminService.logout(userId);
}
//get a new access token
@Post("new-token")
async newAccessToken(@Body("refreshToken") refreshToken: string) {
return this.adminService.newAccessToken(refreshToken);
}
//edit admin profile
@UseGuards(RoleGuard) @UseGuards(RoleGuard)
@Put() @Put()
async editAdminProfile(@Request() req, @Body() updateAdminDto: UpdateUserDto): Promise<{message}>{ async editAdminProfile(@Request() req, @Body() updateAdminDto: UpdateUserDto): Promise<{ message }> {
const userId = req.user.id; const userId = req.user.id;
return this.adminService.editAdminProfile(userId, updateAdminDto); return this.adminService.editAdminProfile(userId, updateAdminDto);
} }

@ -34,7 +34,7 @@ export class AdminService {
} }
} }
//login method //login method
async login(loginAdminDto: LoginAdminDto): Promise<{ token: string }> { async login(loginAdminDto: LoginAdminDto): Promise<{ accessToken: string; refreshToken: string }> {
try { try {
const admin = await this.adminModel.findOne({ const admin = await this.adminModel.findOne({
where: { email: loginAdminDto.email }, where: { email: loginAdminDto.email },
@ -49,7 +49,7 @@ export class AdminService {
throw new HttpException("Invalid email or password.", HttpStatus.UNAUTHORIZED); throw new HttpException("Invalid email or password.", HttpStatus.UNAUTHORIZED);
} }
const token = this.jwtService.sign( const accessToken = this.jwtService.sign(
{ id: admin.id, role: admin.role }, { id: admin.id, role: admin.role },
{ {
secret: this.configService.get<string>("JWT_SECRET"), secret: this.configService.get<string>("JWT_SECRET"),
@ -57,7 +57,19 @@ export class AdminService {
}, },
); );
return { token }; const refreshToken = this.jwtService.sign(
{ id: admin.id, role: admin.role },
{
secret: this.configService.get<string>("JWT_REFRESH_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_REFRESH_EXPIRES") || "7d",
},
);
await this.adminModel.update(
{ refreshToken }, //
{ where: { id: admin.id } },
);
return { accessToken, refreshToken };
} catch (error) { } catch (error) {
if (error instanceof HttpException) { if (error instanceof HttpException) {
throw error; throw error;
@ -65,6 +77,51 @@ export class AdminService {
throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
//logout (delete refresh token from database)
async logout(userId: number) {
const user = await this.adminModel.findOne({
where: { id: userId },
});
if (!user) {
throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
}
await this.adminModel.update({ refreshToken: null }, { where: { id: userId } });
return { message: "logout is successful" };
}
//getting new access token
async newAccessToken(refreshToken: string) {
if (!refreshToken) {
throw new HttpException({ message: "Refresh token is required." }, HttpStatus.BAD_REQUEST);
}
let decoded;
try {
decoded = this.jwtService.verify(refreshToken, { secret: process.env.JWT_REFRESH_SECRET });
} catch (error) {
throw new HttpException({ message: "Invalid or expired token." }, HttpStatus.UNAUTHORIZED);
}
const user = await this.adminModel.findOne({
where: { id: decoded.id },
});
if (!user) {
throw new HttpException({ message: "User not found." }, HttpStatus.NOT_FOUND);
}
if (user.role !== "admin") {
throw new HttpException({ message: "You are not authorized to get an admin token." }, HttpStatus.FORBIDDEN);
}
const accessToken = this.jwtService.sign(
{ id: user.id, role: user.role },
{
secret: this.configService.get<string>("JWT_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_EXPIRES") || "1h",
},
);
return { accessToken };
}
//edit admin profile method //edit admin profile method
async editAdminProfile(userId: number, updateAdminDto: UpdateUserDto) { async editAdminProfile(userId: number, updateAdminDto: UpdateUserDto) {
try { try {

@ -22,6 +22,11 @@ export class Admin extends Model<Admin> {
@Column({ unique: true }) @Column({ unique: true })
phoneNumber: string; phoneNumber: string;
@Column({
type: DataType.STRING,
allowNull: true,
})
refreshToken: string;
@Column({ @Column({
type: DataType.ENUM("male", "female"), type: DataType.ENUM("male", "female"),

@ -10,21 +10,21 @@ import { Invoice } from "src/invoice/entities/invoice.entity";
export class CartController { export class CartController {
constructor(private readonly cartService: CartService) {} constructor(private readonly cartService: CartService) {}
//create and a item to cart by user
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Post() @Post()
async createAndAddItemToCart(@Body() addToCartDto: AddToCartDto, @Request() req: any): Promise<{ message: string; cartItem: Cart }> { async createAndAddItemToCart(@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.createAndAddItemToCart({ ...addToCartDto, userId });
} }
//get user cart items
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get() @Get()
async getUserOpenCart(@Request() req: any): Promise<{ cartItems: Cart[]; totalPrice: number }> { async getUserOpenCart(@Request() req: any): Promise<{ cartItems: Cart[]; totalPrice: number }> {
const userId = req.user.id; const userId = req.user.id;
return this.cartService.getUserOpenCart(userId); return this.cartService.getUserOpenCart(userId);
} }
//edit quantity an item in cart by user
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Patch(":productId") @Patch(":productId")
async updateCart(@Param("productId") productId: number, @Body() updateCartDto: UpdateCartDto, @Request() req: any): Promise<{ message: string; updatedCart: Cart }> { async updateCart(@Param("productId") productId: number, @Body() updateCartDto: UpdateCartDto, @Request() req: any): Promise<{ message: string; updatedCart: Cart }> {
@ -35,14 +35,21 @@ export class CartController {
updatedCart, updatedCart,
}; };
} }
//delete an item from cart by user
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Delete(":productId") @Delete(":productId")
async removeFromCart(@Param("productId") productId: number, @Request() req: any) { async removeFromCart(@Param("productId") productId: number, @Request() req: any) {
const userId = req.user.id; const userId = req.user.id;
return await this.cartService.removeFromCart(userId, productId); 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) @UseGuards(JwtAuthGuard)
@Get("checkout") @Get("checkout")
async processOrder(@Request() req: any): Promise<{ message: string; invoice: Invoice }> { async processOrder(@Request() req: any): Promise<{ message: string; invoice: Invoice }> {

@ -125,9 +125,10 @@ export class CartService {
} }
} }
//delete whole cart //delete whole cart by user
async clearCart(userId: number): Promise<void> { async clearCart(userId: number) {
await this.cartModel.destroy({ where: { userId } }); await this.cartModel.destroy({ where: { userId } });
return { message: "cart cleared successful" };
} }
//order(clearCart disable) //order(clearCart disable)

@ -1,11 +1,26 @@
import { Controller, Get, Post, Body, Patch, Param, Delete } from "@nestjs/common"; import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Request } from "@nestjs/common";
import { InvoiceService } from "./invoice.service"; import { InvoiceService } from "./invoice.service";
import { JwtAuthGuard } from "src/guard/auth.guard";
import { RoleGuard } from "src/guard/role.guard";
@Controller("invoice") @Controller("invoice")
export class InvoiceController { export class InvoiceController {
constructor(private readonly invoiceService: InvoiceService) {} constructor(private readonly invoiceService: InvoiceService) {}
@Get(":userId") @UseGuards(JwtAuthGuard)
async getInvoices(@Param("userId") userId: number): Promise<any> { @Get()
async getInvoiceByUser(@Request() req) {
const userId = req.user.id;
return this.invoiceService.getInvoiceByUser(userId); 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);
}
} }

@ -4,11 +4,19 @@ import { InvoiceController } from "./invoice.controller";
import { InvoiceService } from "./invoice.service"; import { InvoiceService } from "./invoice.service";
import { Invoice } from "./entities/invoice.entity"; import { Invoice } from "./entities/invoice.entity";
import { CartModule } from "src/cart/cart.module"; 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({ @Module({
imports: [SequelizeModule.forFeature([Invoice]), forwardRef(()=>CartModule)], imports: [SequelizeModule.forFeature([Invoice]),
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: "1h" },
}),
forwardRef(()=>CartModule)],
controllers: [InvoiceController], controllers: [InvoiceController],
providers: [InvoiceService], providers: [InvoiceService,JwtAuthGuard,RoleGuard],
exports: [InvoiceService], exports: [InvoiceService],
}) })
export class InvoiceModule {} export class InvoiceModule {}

@ -43,14 +43,14 @@ export class InvoiceService {
await invoice.save(); await invoice.save();
} }
async getInvoiceByUser(userId: number): Promise<Invoice> { async getInvoicePendingByUser(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 are required.", HttpStatus.BAD_REQUEST);
} }
const invoice = await this.invoiceModel.findOne({ const invoice = await this.invoiceModel.findOne({
where: { userId, status:'pending' }, where: { userId, status: "pending" },
}); });
if (!invoice) { if (!invoice) {
@ -65,5 +65,64 @@ export class InvoiceService {
throw new HttpException("An error occurred while retrieving the invoice.", HttpStatus.INTERNAL_SERVER_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,10 +1,12 @@
import { Controller, Post, Body, Param, Get, Query } from "@nestjs/common"; import { Controller, Post, Body, Param, Get, Query, UseGuards, Request } from "@nestjs/common";
import { PaymentService } from "./payment.service"; import { PaymentService } from "./payment.service";
import { InvoiceService } from "../invoice/invoice.service"; import { InvoiceService } from "../invoice/invoice.service";
import { WalletService } from "src/wallet/wallet.service"; import { WalletService } from "src/wallet/wallet.service";
import { console } from "inspector"; import { console } from "inspector";
import { InjectModel } from "@nestjs/sequelize"; import { InjectModel } from "@nestjs/sequelize";
import { Payment } from "./entities/payment.entity"; import { Payment } from "./entities/payment.entity";
import { JwtAuthGuard } from "src/guard/auth.guard";
import { Transaction } from "src/wallet/entities/transaction.entity";
@Controller("payment") @Controller("payment")
export class PaymentController { export class PaymentController {
@ -13,21 +15,25 @@ export class PaymentController {
private readonly walletService: WalletService, private readonly walletService: WalletService,
private readonly paymentService: PaymentService, private readonly paymentService: PaymentService,
private readonly invoiceService: InvoiceService, private readonly invoiceService: InvoiceService,
@InjectModel(Transaction) private readonly transactionModel: typeof Transaction,
) {} ) {}
@UseGuards(JwtAuthGuard)
@Post("request/:userId") @Post("request/:userId")
async requestPayment(@Param("userId") userId: number): Promise<{ url: string }> { async requestPayment(@Request() req): Promise<{ url: string }> {
const invoice = await this.invoiceService.getInvoiceByUser(userId); const userId = req.user.id;
const invoice = await this.invoiceService.getInvoicePendingByUser(userId);
const totalAmount = invoice.totalPaymentAmount; const totalAmount = invoice.totalPaymentAmount;
const callbackUrl = `http://localhost:3000/payment/verify?userId=${userId}`; const callbackUrl = `http://localhost:3000/payment/verify?userId=${userId}&amount=${totalAmount}`;
const paymentUrl = await this.paymentService.requestPayment(totalAmount, "Purchase products", callbackUrl); const paymentUrl = await this.paymentService.requestPayment(totalAmount, "Purchase products", callbackUrl);
return { url: paymentUrl }; return { url: paymentUrl };
} }
@Get("verify") @Get("verify")
async verifyPayment(@Query() query: { Authority: string; Status: string; userId: number }): Promise<any> { async verifyPayment(@Query() query: { Authority: string; Status: string; userId: number; amount: number }): Promise<any> {
const { Authority, Status, userId } = query; const { Authority, Status, userId, amount } = query;
if (Status !== "OK") { if (Status !== "OK") {
throw new Error("Payment failed"); throw new Error("Payment failed");
@ -36,26 +42,28 @@ export class PaymentController {
if (!userId) { if (!userId) {
throw new Error("User ID is required."); throw new Error("User ID is required.");
} }
const invoice = await this.invoiceService.getInvoiceByUser(userId); const wallet = this.walletService.getWalletInfo(userId);
const totalAmount = invoice.totalPaymentAmount;
const wallet = this.walletService.getBalance(userId);
try { try {
const refId = await this.paymentService.verifyPayment(Authority, totalAmount); const refId = await this.paymentService.verifyPayment(Authority, amount);
await this.walletService.addBalance(userId, totalAmount); await this.walletService.addBalance(userId, amount);
const wallet = this.walletService.getBalance(userId); const wallet = this.walletService.getWalletInfo(userId);
await this.paymentModel.create({ await this.paymentModel.create({
userId, userId,
walletId: (await wallet).walletId, walletId: (await wallet).walletId,
paymentAmount: totalAmount, paymentAmount: amount,
status: "completed", status: "completed",
}); });
await this.transactionModel.create({
walletId:(await wallet).walletId,
amount:(String(amount).startsWith('+') ? String(amount) : `+${amount}`)
})
return { message: "Payment successful", refId }; return { message: "Payment successful", refId };
} catch (error) { } catch (error) {
console.log(error); console.log(error);
await this.paymentModel.create({ await this.paymentModel.create({
userId, userId,
walletId: (await wallet).walletId, walletId: (await wallet).walletId,
paymentAmount: totalAmount, paymentAmount: amount,
status: "failed", status: "failed",
}); });
throw new Error(`Error during payment verification: ${error.message}`); throw new Error(`Error during payment verification: ${error.message}`);

@ -7,9 +7,16 @@ import { WalletModule } from 'src/wallet/wallet.module';
import { InvoiceModule } from 'src/invoice/invoice.module'; import { InvoiceModule } from 'src/invoice/invoice.module';
import { Payment } from './entities/payment.entity'; import { Payment } from './entities/payment.entity';
import { SequelizeModule } from '@nestjs/sequelize'; import { SequelizeModule } from '@nestjs/sequelize';
import { JwtModule } from '@nestjs/jwt';
import { Transaction } from 'src/wallet/entities/transaction.entity';
@Module({ @Module({
imports:[SequelizeModule.forFeature([Payment]),CartModule,WalletModule,InvoiceModule], imports:[SequelizeModule.forFeature([Payment,Transaction]),
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: "1h" },
}),
CartModule,WalletModule,InvoiceModule],
controllers: [PaymentController], controllers: [PaymentController],
providers: [PaymentService], providers: [PaymentService],
}) })

@ -31,11 +31,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(`Payment request failed with status: ${result.status}`);
} }
} catch (error) { } catch (error) {
console.log('Error in PaymentRequest:', error); console.log('Error in PaymentRequest:', error.message || error);
throw new InternalServerErrorException(`Error in payment request: ${error.message}`); throw new InternalServerErrorException(`Error in payment request: ${error.message}`);
} }
} }

@ -28,6 +28,13 @@ export class User extends Model<User> {
allowNull: false, allowNull: false,
}) })
gender: string; gender: string;
@Column({
type: DataType.STRING,
allowNull: true,
})
refreshToken: string;
} }
export enum Gender { export enum Gender {
Male = 'male', Male = 'male',

@ -12,15 +12,26 @@ export class UsersController {
constructor(private readonly usersService: UsersService) {} constructor(private readonly usersService: UsersService) {}
//register as user //register as user
@Post("register") @Post("register")
async register(@Body() createUserDto: CreateUserDto): Promise<{message}> { async register(@Body() createUserDto: CreateUserDto): Promise<{ message }> {
return this.usersService.register(createUserDto); return this.usersService.register(createUserDto);
} }
//login as user //login as user
@Post("login") @Post("login")
async login(@Body() loginUserDto: LoginUserDto): Promise<{ token }> { async login(@Body() loginUserDto: LoginUserDto): Promise<{ accessToken; refreshToken }> {
return this.usersService.login(loginUserDto); return this.usersService.login(loginUserDto);
} }
//get access token
@Post("new-token")
async newAccessToken(@Body("refreshToken") refreshToken: string) {
return this.usersService.newAccessToken(refreshToken);
}
//logout user
@UseGuards(JwtAuthGuard)
@Get('logout')
async logout(@Request() req) {
const userId = req.user.id;
return this.usersService.logout(userId);
}
//retrieve a user information //retrieve a user information
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Get() @Get()
@ -35,15 +46,23 @@ export class UsersController {
const userId = req.user.id; const userId = req.user.id;
return this.usersService.editProfile(userId, updateUserDto); return this.usersService.editProfile(userId, updateUserDto);
} }
//admin endpoints/////////////////////////////////////////////////////////
//get users list (admin) //get users list (admin)
@UseGuards(RoleGuard) @UseGuards(RoleGuard)
@Get("users") @Get("users")
async findAll(): Promise<User[]> { async findAll(): Promise<User[]> {
return this.usersService.findAll(); return this.usersService.findAll();
} }
// get a specific user info by admin
@UseGuards(RoleGuard) @UseGuards(RoleGuard)
@Get("users/:id") @Get("users/:id")
async findSpecificUserInfoByUser(@Param("id") id): Promise<User> { async findSpecificUserInfoByUser(@Param("id") id): Promise<User> {
return this.usersService.findSpecificUserInfoByUser(id); return this.usersService.findSpecificUserInfoByUser(id);
} }
//delete a specific user by admin
@UseGuards(RoleGuard)
@Get("users/delete/:id")
async deleteUser(@Param("id") id){
return this.usersService.deleteUser(id);
}
} }

@ -36,7 +36,7 @@ export class UsersService {
} }
} }
// Login method // Login method
async login(loginUserDto: LoginUserDto): Promise<{ token: string }> { async login(loginUserDto: LoginUserDto): Promise<{ accessToken: string; refreshToken: string }> {
try { try {
const user = await this.userModel.findOne({ const user = await this.userModel.findOne({
where: { email: loginUserDto.email }, where: { email: loginUserDto.email },
@ -45,18 +45,34 @@ export class UsersService {
if (!user) { if (!user) {
throw new UnauthorizedException("Invalid email or password."); throw new UnauthorizedException("Invalid email or password.");
} }
const passwordMatch = await bcrypt.compare(loginUserDto.password, user.password); const passwordMatch = await bcrypt.compare(loginUserDto.password, user.password);
if (!passwordMatch) { if (!passwordMatch) {
throw new UnauthorizedException("Invalid email or password."); throw new UnauthorizedException("Invalid email or password.");
} }
const token = this.jwtService.sign(
const accessToken = this.jwtService.sign(
{ id: user.id, role: user.role }, { id: user.id, role: user.role },
{ {
secret: this.configService.get<string>("JWT_SECRET"), secret: this.configService.get<string>("JWT_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_EXPIRES") || "1h", expiresIn: this.configService.get<string | number>("JWT_EXPIRES") || "1h",
}, },
); );
return { token };
const refreshToken = this.jwtService.sign(
{ id: user.id },
{
secret: this.configService.get<string>("JWT_REFRESH_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_REFRESH_EXPIRES") || "7d",
},
);
await this.userModel.update(
{ refreshToken }, //
{ where: { id: user.id } },
);
return { accessToken: accessToken, refreshToken: refreshToken };
} catch (error) { } catch (error) {
if (error instanceof HttpException) { if (error instanceof HttpException) {
throw error; throw error;
@ -64,6 +80,47 @@ export class UsersService {
throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR); throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
} }
} }
// getting access token
async newAccessToken(refreshToken: string) {
if (!refreshToken) {
throw new HttpException({ message: "Refresh token is required." }, HttpStatus.BAD_REQUEST);
}
let decoded;
try {
decoded = this.jwtService.verify(refreshToken, { secret: process.env.JWT_REFRESH_SECRET });
} catch (error) {
throw new HttpException({ message: "Invalid or expired token." }, HttpStatus.UNAUTHORIZED);
}
const user = await this.userModel.findOne({
where: { id: decoded.id },
});
if (!user) {
throw new HttpException({ message: "User not found." }, HttpStatus.NOT_FOUND);
}
const accessToken = this.jwtService.sign(
{ id: user.id, role: user.role },
{
secret: this.configService.get<string>("JWT_SECRET"),
expiresIn: this.configService.get<string | number>("JWT_EXPIRES") || "1h",
},
);
return { accessToken };
}
//logout (delete refresh token from database)
async logout(userId: number) {
const user = await this.userModel.findOne({
where: { id: userId },
});
if (!user) {
throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
}
await this.userModel.update({ refreshToken: null }, { where: { id: userId } });
return { message: "logout is successful" };
}
//get information user method //get information user method
async getProfile(userId: number): Promise<User> { async getProfile(userId: number): Promise<User> {
try { try {
@ -84,7 +141,7 @@ export class UsersService {
try { try {
let user = await this.userModel.findOne({ where: { id: userId } }); let user = await this.userModel.findOne({ where: { id: userId } });
if (!user) { if (!user) {
throw new NotFoundException('User not found.'); throw new NotFoundException("User not found.");
} }
if (updateUserDto.password) { if (updateUserDto.password) {
updateUserDto.password = await bcrypt.hash(updateUserDto.password, 10); updateUserDto.password = await bcrypt.hash(updateUserDto.password, 10);
@ -92,14 +149,11 @@ export class UsersService {
await user.update(updateUserDto); await user.update(updateUserDto);
user = await this.userModel.findOne({ user = await this.userModel.findOne({
where: { id: userId }, where: { id: userId },
attributes: { exclude: ['password'] }, attributes: { exclude: ["password"] },
}); });
return { message: 'User account updated successfully.', user }; return { message: "User account updated successfully.", user };
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException("An unexpected error occurred. Please try again later.", HttpStatus.INTERNAL_SERVER_ERROR);
'An unexpected error occurred. Please try again later.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
//get users list //get users list
@ -107,10 +161,7 @@ export class UsersService {
try { try {
return await this.userModel.findAll(); return await this.userModel.findAll();
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException("An unexpected error occurred while fetching users.", HttpStatus.INTERNAL_SERVER_ERROR);
'An unexpected error occurred while fetching users.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
} }
} }
//get users list //get users list
@ -118,14 +169,24 @@ export class UsersService {
try { try {
const user = await this.userModel.findByPk(userId); const user = await this.userModel.findByPk(userId);
if (!user) { if (!user) {
throw new HttpException('User not found.', HttpStatus.NOT_FOUND); throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
} }
return user; return user;
} catch (error) { } catch (error) {
throw new HttpException( throw new HttpException("An unexpected error occurred while fetching user information.", HttpStatus.INTERNAL_SERVER_ERROR);
'An unexpected error occurred while fetching user information.', }
HttpStatus.INTERNAL_SERVER_ERROR, }
); //delete a specific user by admin
async deleteUser(userId: number) {
const user = await this.userModel.findOne({
where: { id: userId },
});
if (!user) {
throw new HttpException("User not found.", HttpStatus.NOT_FOUND);
} }
await this.userModel.destroy({
where: { id: userId },
});
return { message: "user deleted successful" };
} }
} }

@ -0,0 +1,19 @@
import { Model, Table, Column, ForeignKey, BelongsTo, DataType } from 'sequelize-typescript';
import { Wallet } from './wallet.entity';
@Table
export class Transaction extends Model<Transaction> {
@ForeignKey(() => Wallet)
@Column
walletId: number;
@BelongsTo(() => Wallet, { onDelete: 'CASCADE' })
wallet: Wallet;
@Column({
type: DataType.STRING,
allowNull: false,
defaultValue: "0",
})
amount: string;
}

@ -1,12 +1,37 @@
import { Controller, Get, Post, Body, Patch, Param, Delete } from "@nestjs/common"; import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Request, forwardRef, Inject } from "@nestjs/common";
import { WalletService } from "./wallet.service"; import { WalletService } from "./wallet.service";
import { JwtAuthGuard } from "src/guard/auth.guard";
import { PaymentService } from "src/payment/payment.service";
@Controller("wallet") @Controller("wallet")
export class WalletController { export class WalletController {
constructor(private readonly walletService: WalletService) {} constructor(
@Get(":userId") private readonly walletService: WalletService,
async getBalance(@Param("userId") userId: number) { @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); 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)
}
} }

@ -1,13 +1,24 @@
import { Module } from '@nestjs/common'; import { Module } from "@nestjs/common";
import { WalletService } from './wallet.service'; import { WalletService } from "./wallet.service";
import { WalletController } from './wallet.controller'; import { WalletController } from "./wallet.controller";
import { Wallet } from './entities/wallet.entity'; import { Wallet } from "./entities/wallet.entity";
import { SequelizeModule } from '@nestjs/sequelize'; 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({ @Module({
imports: [SequelizeModule.forFeature([Wallet])], imports: [
SequelizeModule.forFeature([Wallet,Transaction]),
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: "1h" },
}),
],
controllers: [WalletController], controllers: [WalletController],
providers: [WalletService], providers: [WalletService, JwtAuthGuard, RoleGuard,PaymentService],
exports: [WalletService], exports: [WalletService],
}) })
export class WalletModule {} export class WalletModule {}

@ -3,27 +3,42 @@ import { InjectModel } from "@nestjs/sequelize";
import { Wallet } from "./entities/wallet.entity"; import { Wallet } from "./entities/wallet.entity";
import { HttpException, HttpStatus } from "@nestjs/common"; import { HttpException, HttpStatus } from "@nestjs/common";
import { AddBalanceResponse } from "./add-balance-response.interface"; import { AddBalanceResponse } from "./add-balance-response.interface";
import { Transaction } from "./entities/transaction.entity";
@Injectable() @Injectable()
export class WalletService { export class WalletService {
constructor(@InjectModel(Wallet) private walletModel: typeof Wallet) {} constructor(
@InjectModel(Wallet) private walletModel: typeof Wallet,
async getBalance(userId: number){ @InjectModel(Transaction) private transactionModel: typeof Transaction,
) {}
//get wallet info
async getWalletInfo(userId: number) {
const wallet = await this.walletModel.findOne({ where: { userId } }); const wallet = await this.walletModel.findOne({ where: { userId } });
if (!wallet) { if (!wallet) {
throw new HttpException("Wallet not found", HttpStatus.NOT_FOUND); 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 }; 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) {
const newWallet = await this.walletModel.create({ userId, balance: 0 });
return { walletId: newWallet.id, userId: newWallet.userId, balance: newWallet.balance };
} }
return { balance: wallet.balance };
}
//charge balance of wallet by user
async addBalance(userId: number, amount: number): Promise<AddBalanceResponse> { async addBalance(userId: number, amount: number): Promise<AddBalanceResponse> {
try { try {
const wallet = await this.walletModel.findOne({ where: { userId } }); const wallet = await this.walletModel.findOne({ where: { userId } });
if (wallet) { if (wallet) {
wallet.balance += amount; wallet.balance += Number(amount);
await wallet.save(); await wallet.save();
return { message: "Balance updated successfully.", balance: wallet.balance }; return { message: "Balance updated successfully.", balance: wallet.balance };
} else { } else {
@ -34,6 +49,7 @@ 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);
} }
} }
//process of payment
async processPayment(userId: number, amount: number): Promise<string> { async processPayment(userId: number, amount: number): Promise<string> {
const wallet = await this.walletModel.findOne({ where: { userId } }); const wallet = await this.walletModel.findOne({ where: { userId } });
@ -46,8 +62,20 @@ export class WalletService {
} }
wallet.balance -= amount; wallet.balance -= amount;
await this.transactionModel.create({
walletId: wallet.id,
amount: String(amount).startsWith("-") ? String(amount) : `-${amount}`,
});
await wallet.save(); await wallet.save();
return "Payment processed successfully"; return "Payment processed successfully";
} }
//getting transaction
async getTransactionById(userId: number) {
const wallet = this.getWalletInfo(userId);
return await this.transactionModel.findAll({
where: { walletId: (await wallet).walletId },
});
}
} }

Loading…
Cancel
Save