chore: add direct payment for invoice
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user