Add endpoints for wallet charge and transaction history

master
nicekid1 1 month ago
parent 04716f6973
commit 0359e22e40
  1. 43
      migrations/20250111110343-create-transactions.js
  2. 36
      src/payment/payment.controller.ts
  3. 9
      src/payment/payment.module.ts
  4. 11
      src/payment/payment.service.ts
  5. 19
      src/wallet/entities/transaction.entity.ts
  6. 35
      src/wallet/wallet.controller.ts
  7. 27
      src/wallet/wallet.module.ts
  8. 46
      src/wallet/wallet.service.ts

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

@ -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<any> {
const { Authority, Status, userId } = query;
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");
@ -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}`);

@ -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],
})

@ -23,22 +23,21 @@ export class PaymentService {
async requestPayment(amount: number, description: string, callbackUrl: string): Promise<string> {
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<string> {
try {

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

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

@ -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<AddBalanceResponse> {
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<string> {
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 },
});
}
}

Loading…
Cancel
Save