chore: add direct payment for invoice

This commit is contained in:
Mahyargdz
2025-05-14 16:31:05 +03:30
parent eae5981ea1
commit 2ac29d1a91
9 changed files with 82 additions and 19 deletions
+2
View File
@@ -317,6 +317,8 @@ export const enum WalletMessage {
INVOICE_WALLET_TRANSFER = "پرداخت صورت حساب از طریق کیف پول",
TRANSACTION_NOT_FOUND = "تراکنش پیدا نشد",
REFERRAL_REWARD_WALLET_TRANSFER = "پرداخت پاداش ارجاع از طریق کیف پول",
INVOICE_ID_REQUIRED = "شناسه صورت حساب مورد نیاز است",
INVOICE_ID_SHOULD_BE_UUID = "شناسه صورت حساب باید یک UUID معتبر باشد",
}
export const enum PaymentMessage {
+1 -1
View File
@@ -13,7 +13,7 @@ export function databaseConfigs(): TypeOrmModuleAsyncOptions {
username: configService.getOrThrow<string>("DB_USER"),
password: configService.getOrThrow<string>("DB_PASS"),
autoLoadEntities: true,
synchronize: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
synchronize: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : false,
logging: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
migrationsTableName: "typeorm_migrations",
migrationsRun: false,
@@ -481,6 +481,33 @@ export class InvoicesService {
if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID);
let shouldCharge = false;
let remainingToCharge = 0;
if (!isAdmin) {
if (invoice.status === InvoiceStatus.WAIT_PAYMENT) {
const queryRunner = this.dataSource.createQueryRunner();
try {
await queryRunner.connect();
const userWallet = await this.walletsService.getWalletByUserId(userId, queryRunner);
const invoicePrice = new Decimal(invoice.totalPrice);
const walletBalance = new Decimal(userWallet.balance);
if (walletBalance.lessThan(invoicePrice)) {
remainingToCharge = invoicePrice.sub(walletBalance).round().toNumber();
shouldCharge = true;
} else {
remainingToCharge = 0;
}
} finally {
await queryRunner.release();
}
}
return {
invoice,
shouldCharge,
remainingToCharge,
};
}
return {
invoice,
};
@@ -1,5 +1,5 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsEnum, IsInt, IsNotEmpty, IsUUID, IsUrl, Min } from "class-validator";
import { IsEnum, IsInt, IsNotEmpty, IsOptional, IsUUID, IsUrl, Min } from "class-validator";
import { WalletMessage } from "../../../common/enums/message.enum";
import { TransferType } from "../enums/payment-type.enum";
@@ -22,6 +22,12 @@ export class GatewayDepositDto extends DepositDto {
@IsUUID("4", { message: WalletMessage.GATEWAY_ID_SHOULD_BE_UUID })
@ApiProperty({ description: "Gateway id to pay", example: "e6fdce2a-9f91-47c4-8561-48368fc275b5" })
gatewayId: string;
@IsOptional()
@IsNotEmpty({ message: WalletMessage.INVOICE_ID_REQUIRED })
@IsUUID("4", { message: WalletMessage.INVOICE_ID_SHOULD_BE_UUID })
@ApiProperty({ description: "Invoice id to pay", example: "e6fdce2a-9f91-47c4-8561-48368fc275b5" })
invoiceId?: string;
}
export class TransferDepositDto extends DepositDto {
@@ -1,4 +1,4 @@
import { IsEnum, IsNotEmpty } from "class-validator";
import { IsEnum, IsNotEmpty, IsOptional } from "class-validator";
import { GatewayEnum } from "../enums/gateway.enum";
import { GatewayType } from "../types/gateway.type";
@@ -8,6 +8,10 @@ export class VerifyParamDto {
@IsNotEmpty()
@IsEnum(GatewayEnum)
gateway: GatewayType;
@IsOptional()
@IsNotEmpty()
invoiceId?: string;
}
export class VerifyQueryDto {
@@ -34,7 +34,7 @@ export class ZarinpalGateway implements IPaymentGateway {
const purchaseData: ZarinPalPGNewArgs = {
merchant_id: this.config.merchantId,
amount: processParams.amount,
callback_url: `${this.config.callBackUrl}/${GatewayEnum.ZARINPAL}`,
callback_url: `${this.config.callBackUrl}/${GatewayEnum.ZARINPAL}${processParams.invoiceId ? "/" + processParams.invoiceId : ""}`,
description: processParams.description,
currency: "IRT",
metadata: { email: processParams.email, mobile: processParams.mobile },
@@ -13,6 +13,7 @@ export interface IProcessPaymentParams {
// callBackPath: string;
email?: string;
mobile?: string;
invoiceId?: string;
}
export interface IProcessPaymentData {
+15 -2
View File
@@ -87,9 +87,22 @@ export class PaymentsController {
return this.paymentsService.getTransactions(queryDto);
}
@Get("verify/:gateway/:invoiceId")
verifyPaymentWithInvoice(
@Param() paramDto: VerifyParamDto,
@Query() queryDto: VerifyQueryDto,
@Res({ passthrough: true }) rep: FastifyReply,
) {
return this.paymentsService.verifyPayment(paramDto, queryDto, rep);
}
@Get("verify/:gateway")
verifyPayment(@Param() paramDto: VerifyParamDto, @Query() queryDto: VerifyQueryDto, @Res({ passthrough: true }) rep: FastifyReply) {
return this.paymentsService.verifyPayment(paramDto.gateway, queryDto, rep);
verifyPaymentWithoutInvoice(
@Param() paramDto: VerifyParamDto,
@Query() queryDto: VerifyQueryDto,
@Res({ passthrough: true }) rep: FastifyReply,
) {
return this.paymentsService.verifyPayment(paramDto, queryDto, rep);
}
///------------------- bank account -----------------------------
@@ -23,7 +23,7 @@ import { PaymentTransactionQueryDto } from "../DTO/payment-transaction-query.dto
import { SearchTransactionQueryDto } from "../DTO/search-transaction-query.dto";
import { UpdateBankAccountDto } from "../DTO/update-bankaccount.dto";
import { RejectDepositRequestDto } from "../DTO/update-deposit-request-status.dto";
import { VerifyQueryDto } from "../DTO/verify-payment.dto";
import { VerifyParamDto, VerifyQueryDto } from "../DTO/verify-payment.dto";
import { BankAccount } from "../entities/bank-account.entity";
import { DepositRequest } from "../entities/deposit-request.entity";
import { PaymentGateway } from "../entities/payment-gateway.entity";
@@ -32,7 +32,7 @@ import { DepositRequestStatus } from "../enums/deposit-request-status.enum";
import { PaymentStatus } from "../enums/payment-status.enum";
import { TransferType } from "../enums/payment-type.enum";
import { PaymentGatewayFactory } from "../factories/payment.factory";
import { IVerifyPayment } from "../interfaces/IPayment";
import { IProcessPaymentParams, IVerifyPayment } from "../interfaces/IPayment";
import { BankAccountsRepository } from "../repositories/bank-accounts.repository";
import { DepositRequestsRepository } from "../repositories/deposit-requests.repository";
import { PaymentGatewaysRepository } from "../repositories/payment-gateway.repository";
@@ -58,16 +58,22 @@ export class PaymentsService {
//===============================================
async chargeWalletWithGateway(chargeDto: GatewayDepositDto, userId: string) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const { amount, gatewayId } = chargeDto;
await queryRunner.connect();
await queryRunner.startTransaction();
const { amount, gatewayId, invoiceId } = chargeDto;
const user = await this.getUserById(userId, queryRunner);
const paymentGateway = await this.getPaymentGatewayById(gatewayId, queryRunner);
const gatewayData = await this.processPayment(paymentGateway.name, amount, WalletMessage.DEPOSIT_WALLET_IPG, user.email, user.phone);
const gatewayData = await this.processPayment(paymentGateway.name, {
amount,
description: WalletMessage.DEPOSIT_WALLET_IPG,
email: user.email,
mobile: user.phone,
invoiceId,
});
const payment = await this.createGatewayPaymentForUser(user, amount, gatewayData.reference, paymentGateway.id, queryRunner);
@@ -113,21 +119,21 @@ export class PaymentsService {
}
//===============================================
async processPayment(provider: GatewayType, amount: number, description: string, email?: string, mobile?: string) {
async processPayment(provider: GatewayType, params: IProcessPaymentParams) {
const paymentGateway = this.gatewayFactory.getPaymentGateway(provider);
return paymentGateway.processPayment({ amount, description, email, mobile });
return paymentGateway.processPayment(params);
}
//===============================================
async verifyPayment(gateway: GatewayType, queryDto: VerifyQueryDto, rep: FRply) {
const frontUrl = this.buildFrontendRedirectUrl();
async verifyPayment(verifyParamDto: VerifyParamDto, queryDto: VerifyQueryDto, rep: FRply) {
const frontUrl = this.buildFrontendRedirectUrl(verifyParamDto.invoiceId);
const queryRunner = this.dataSource.createQueryRunner();
try {
await queryRunner.connect();
await queryRunner.startTransaction();
const paymentGateway = this.gatewayFactory.getPaymentGateway(gateway);
const paymentGateway = this.gatewayFactory.getPaymentGateway(verifyParamDto.gateway);
const payment = await this.getPaymentByReference(queryDto.Authority, queryRunner);
if (payment.status === PaymentStatus.COMPLETED) throw new BadRequestException(PaymentMessage.VALIDATED_BEFORE);
@@ -409,9 +415,13 @@ export class PaymentsService {
//-private methods
//----------------------------------------
private buildFrontendRedirectUrl(): URL {
private buildFrontendRedirectUrl(invoiceId?: string): URL {
const frontUrl = new URL(this.configService.getOrThrow<string>("SITE_URL"));
frontUrl.pathname = "/payment";
if (invoiceId) {
frontUrl.pathname = "/receipts/" + invoiceId;
} else {
frontUrl.pathname = "/payment";
}
return frontUrl;
}