diff --git a/migrations/20250111110343-create-transactions.js b/migrations/20250111110343-create-transactions.js new file mode 100644 index 0000000..86580de --- /dev/null +++ b/migrations/20250111110343-create-transactions.js @@ -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'); + }, +}; diff --git a/src/payment/payment.controller.ts b/src/payment/payment.controller.ts index ebef011..314eb51 100644 --- a/src/payment/payment.controller.ts +++ b/src/payment/payment.controller.ts @@ -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 { InvoiceService } from "../invoice/invoice.service"; import { WalletService } from "src/wallet/wallet.service"; 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"; @Controller("payment") export class PaymentController { @@ -13,21 +15,25 @@ export class PaymentController { private readonly walletService: WalletService, private readonly paymentService: PaymentService, private readonly invoiceService: InvoiceService, + @InjectModel(Transaction) private readonly transactionModel: typeof Transaction, + ) {} + @UseGuards(JwtAuthGuard) @Post("request/:userId") - async requestPayment(@Param("userId") userId: number): Promise<{ url: string }> { - const invoice = await this.invoiceService.getInvoiceByUser(userId); + async requestPayment(@Request() req): Promise<{ url: string }> { + const userId = req.user.id; + const invoice = await this.invoiceService.getInvoicePendingByUser(userId); 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); return { url: paymentUrl }; } @Get("verify") - async verifyPayment(@Query() query: { Authority: string; Status: string; userId: number }): Promise { - const { Authority, Status, userId } = query; + async verifyPayment(@Query() query: { Authority: string; Status: string; userId: number; amount: number }): Promise { + const { Authority, Status, userId, amount } = query; if (Status !== "OK") { throw new Error("Payment failed"); @@ -36,26 +42,28 @@ export class PaymentController { if (!userId) { throw new Error("User ID is required."); } - const invoice = await this.invoiceService.getInvoiceByUser(userId); - const totalAmount = invoice.totalPaymentAmount; - const wallet = this.walletService.getBalance(userId); + const wallet = this.walletService.getWalletInfo(userId); try { - const refId = await this.paymentService.verifyPayment(Authority, totalAmount); - await this.walletService.addBalance(userId, totalAmount); - const wallet = this.walletService.getBalance(userId); + 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: totalAmount, + 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: totalAmount, + paymentAmount: amount, status: "failed", }); throw new Error(`Error during payment verification: ${error.message}`); diff --git a/src/payment/payment.module.ts b/src/payment/payment.module.ts index d90f0c9..dbf4664 100644 --- a/src/payment/payment.module.ts +++ b/src/payment/payment.module.ts @@ -7,9 +7,16 @@ 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]),CartModule,WalletModule,InvoiceModule], + imports:[SequelizeModule.forFeature([Payment,Transaction]), + JwtModule.register({ + secret: process.env.JWT_SECRET, + signOptions: { expiresIn: "1h" }, + }), + CartModule,WalletModule,InvoiceModule], controllers: [PaymentController], providers: [PaymentService], }) diff --git a/src/payment/payment.service.ts b/src/payment/payment.service.ts index 0ce823e..bc5373a 100644 --- a/src/payment/payment.service.ts +++ b/src/payment/payment.service.ts @@ -23,22 +23,21 @@ export class PaymentService { async requestPayment(amount: number, description: string, callbackUrl: string): Promise { try { const result = await this.zarinpal.PaymentRequest({ - Amount: amount, + Amount: amount, CallbackURL: callbackUrl, Description: description, }); - + if (result.status === 100) { - return result.url; + return result.url; } else { - throw new Error(`Payment request failed with status: ${result.status}`); } } 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}`); } - } + } async verifyPayment(authority: string, amount: number): Promise { try { diff --git a/src/wallet/entities/transaction.entity.ts b/src/wallet/entities/transaction.entity.ts new file mode 100644 index 0000000..10e27e6 --- /dev/null +++ b/src/wallet/entities/transaction.entity.ts @@ -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 { + @ForeignKey(() => Wallet) + @Column + walletId: number; + + @BelongsTo(() => Wallet, { onDelete: 'CASCADE' }) + wallet: Wallet; + + @Column({ + type: DataType.STRING, + allowNull: false, + defaultValue: "0", + }) + amount: string; +} diff --git a/src/wallet/wallet.controller.ts b/src/wallet/wallet.controller.ts index f660740..3dcb708 100644 --- a/src/wallet/wallet.controller.ts +++ b/src/wallet/wallet.controller.ts @@ -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 { JwtAuthGuard } from "src/guard/auth.guard"; +import { PaymentService } from "src/payment/payment.service"; @Controller("wallet") export class WalletController { - constructor(private readonly walletService: WalletService) {} - @Get(":userId") - async getBalance(@Param("userId") userId: number) { + 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) + } } diff --git a/src/wallet/wallet.module.ts b/src/wallet/wallet.module.ts index 62c6385..a0d2b95 100644 --- a/src/wallet/wallet.module.ts +++ b/src/wallet/wallet.module.ts @@ -1,13 +1,24 @@ -import { Module } from '@nestjs/common'; -import { WalletService } from './wallet.service'; -import { WalletController } from './wallet.controller'; -import { Wallet } from './entities/wallet.entity'; -import { SequelizeModule } from '@nestjs/sequelize'; +import { Module } from "@nestjs/common"; +import { WalletService } from "./wallet.service"; +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])], + imports: [ + SequelizeModule.forFeature([Wallet,Transaction]), + JwtModule.register({ + secret: process.env.JWT_SECRET, + signOptions: { expiresIn: "1h" }, + }), + ], controllers: [WalletController], - providers: [WalletService], - exports: [WalletService], + providers: [WalletService, JwtAuthGuard, RoleGuard,PaymentService], + exports: [WalletService], }) export class WalletModule {} diff --git a/src/wallet/wallet.service.ts b/src/wallet/wallet.service.ts index 5415885..d9b63b7 100644 --- a/src/wallet/wallet.service.ts +++ b/src/wallet/wallet.service.ts @@ -3,27 +3,42 @@ 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) {} + 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 } }); - async getBalance(userId: number){ + 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); + 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 { balance: wallet.balance }; } - + //charge balance of wallet by user async addBalance(userId: number, amount: number): Promise { try { const wallet = await this.walletModel.findOne({ where: { userId } }); - if (wallet) { - wallet.balance += amount; + wallet.balance += Number(amount); await wallet.save(); return { message: "Balance updated successfully.", balance: wallet.balance }; } else { @@ -34,6 +49,7 @@ export class WalletService { 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 { const wallet = await this.walletModel.findOne({ where: { userId } }); @@ -46,8 +62,20 @@ export class WalletService { } wallet.balance -= amount; + await this.transactionModel.create({ + walletId: wallet.id, + amount: String(amount).startsWith("-") ? String(amount) : `-${amount}`, + }); await wallet.save(); 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 }, + }); + } }