chore: add direct payment for invoice
This commit is contained in:
@@ -317,6 +317,8 @@ export const enum WalletMessage {
|
|||||||
INVOICE_WALLET_TRANSFER = "پرداخت صورت حساب از طریق کیف پول",
|
INVOICE_WALLET_TRANSFER = "پرداخت صورت حساب از طریق کیف پول",
|
||||||
TRANSACTION_NOT_FOUND = "تراکنش پیدا نشد",
|
TRANSACTION_NOT_FOUND = "تراکنش پیدا نشد",
|
||||||
REFERRAL_REWARD_WALLET_TRANSFER = "پرداخت پاداش ارجاع از طریق کیف پول",
|
REFERRAL_REWARD_WALLET_TRANSFER = "پرداخت پاداش ارجاع از طریق کیف پول",
|
||||||
|
INVOICE_ID_REQUIRED = "شناسه صورت حساب مورد نیاز است",
|
||||||
|
INVOICE_ID_SHOULD_BE_UUID = "شناسه صورت حساب باید یک UUID معتبر باشد",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum PaymentMessage {
|
export const enum PaymentMessage {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function databaseConfigs(): TypeOrmModuleAsyncOptions {
|
|||||||
username: configService.getOrThrow<string>("DB_USER"),
|
username: configService.getOrThrow<string>("DB_USER"),
|
||||||
password: configService.getOrThrow<string>("DB_PASS"),
|
password: configService.getOrThrow<string>("DB_PASS"),
|
||||||
autoLoadEntities: true,
|
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,
|
logging: configService.getOrThrow<string>("NODE_ENV") == "production" ? false : true,
|
||||||
migrationsTableName: "typeorm_migrations",
|
migrationsTableName: "typeorm_migrations",
|
||||||
migrationsRun: false,
|
migrationsRun: false,
|
||||||
|
|||||||
@@ -481,6 +481,33 @@ export class InvoicesService {
|
|||||||
|
|
||||||
if (!invoice) throw new BadRequestException(InvoiceMessage.NOT_FOUND_BY_ID);
|
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 {
|
return {
|
||||||
invoice,
|
invoice,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ApiProperty } from "@nestjs/swagger";
|
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 { WalletMessage } from "../../../common/enums/message.enum";
|
||||||
import { TransferType } from "../enums/payment-type.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 })
|
@IsUUID("4", { message: WalletMessage.GATEWAY_ID_SHOULD_BE_UUID })
|
||||||
@ApiProperty({ description: "Gateway id to pay", example: "e6fdce2a-9f91-47c4-8561-48368fc275b5" })
|
@ApiProperty({ description: "Gateway id to pay", example: "e6fdce2a-9f91-47c4-8561-48368fc275b5" })
|
||||||
gatewayId: string;
|
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 {
|
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 { GatewayEnum } from "../enums/gateway.enum";
|
||||||
import { GatewayType } from "../types/gateway.type";
|
import { GatewayType } from "../types/gateway.type";
|
||||||
@@ -8,6 +8,10 @@ export class VerifyParamDto {
|
|||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@IsEnum(GatewayEnum)
|
@IsEnum(GatewayEnum)
|
||||||
gateway: GatewayType;
|
gateway: GatewayType;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty()
|
||||||
|
invoiceId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VerifyQueryDto {
|
export class VerifyQueryDto {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class ZarinpalGateway implements IPaymentGateway {
|
|||||||
const purchaseData: ZarinPalPGNewArgs = {
|
const purchaseData: ZarinPalPGNewArgs = {
|
||||||
merchant_id: this.config.merchantId,
|
merchant_id: this.config.merchantId,
|
||||||
amount: processParams.amount,
|
amount: processParams.amount,
|
||||||
callback_url: `${this.config.callBackUrl}/${GatewayEnum.ZARINPAL}`,
|
callback_url: `${this.config.callBackUrl}/${GatewayEnum.ZARINPAL}${processParams.invoiceId ? "/" + processParams.invoiceId : ""}`,
|
||||||
description: processParams.description,
|
description: processParams.description,
|
||||||
currency: "IRT",
|
currency: "IRT",
|
||||||
metadata: { email: processParams.email, mobile: processParams.mobile },
|
metadata: { email: processParams.email, mobile: processParams.mobile },
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export interface IProcessPaymentParams {
|
|||||||
// callBackPath: string;
|
// callBackPath: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
mobile?: string;
|
mobile?: string;
|
||||||
|
invoiceId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProcessPaymentData {
|
export interface IProcessPaymentData {
|
||||||
|
|||||||
@@ -87,9 +87,22 @@ export class PaymentsController {
|
|||||||
return this.paymentsService.getTransactions(queryDto);
|
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")
|
@Get("verify/:gateway")
|
||||||
verifyPayment(@Param() paramDto: VerifyParamDto, @Query() queryDto: VerifyQueryDto, @Res({ passthrough: true }) rep: FastifyReply) {
|
verifyPaymentWithoutInvoice(
|
||||||
return this.paymentsService.verifyPayment(paramDto.gateway, queryDto, rep);
|
@Param() paramDto: VerifyParamDto,
|
||||||
|
@Query() queryDto: VerifyQueryDto,
|
||||||
|
@Res({ passthrough: true }) rep: FastifyReply,
|
||||||
|
) {
|
||||||
|
return this.paymentsService.verifyPayment(paramDto, queryDto, rep);
|
||||||
}
|
}
|
||||||
|
|
||||||
///------------------- bank account -----------------------------
|
///------------------- bank account -----------------------------
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { PaymentTransactionQueryDto } from "../DTO/payment-transaction-query.dto
|
|||||||
import { SearchTransactionQueryDto } from "../DTO/search-transaction-query.dto";
|
import { SearchTransactionQueryDto } from "../DTO/search-transaction-query.dto";
|
||||||
import { UpdateBankAccountDto } from "../DTO/update-bankaccount.dto";
|
import { UpdateBankAccountDto } from "../DTO/update-bankaccount.dto";
|
||||||
import { RejectDepositRequestDto } from "../DTO/update-deposit-request-status.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 { BankAccount } from "../entities/bank-account.entity";
|
||||||
import { DepositRequest } from "../entities/deposit-request.entity";
|
import { DepositRequest } from "../entities/deposit-request.entity";
|
||||||
import { PaymentGateway } from "../entities/payment-gateway.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 { PaymentStatus } from "../enums/payment-status.enum";
|
||||||
import { TransferType } from "../enums/payment-type.enum";
|
import { TransferType } from "../enums/payment-type.enum";
|
||||||
import { PaymentGatewayFactory } from "../factories/payment.factory";
|
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 { BankAccountsRepository } from "../repositories/bank-accounts.repository";
|
||||||
import { DepositRequestsRepository } from "../repositories/deposit-requests.repository";
|
import { DepositRequestsRepository } from "../repositories/deposit-requests.repository";
|
||||||
import { PaymentGatewaysRepository } from "../repositories/payment-gateway.repository";
|
import { PaymentGatewaysRepository } from "../repositories/payment-gateway.repository";
|
||||||
@@ -58,16 +58,22 @@ export class PaymentsService {
|
|||||||
//===============================================
|
//===============================================
|
||||||
async chargeWalletWithGateway(chargeDto: GatewayDepositDto, userId: string) {
|
async chargeWalletWithGateway(chargeDto: GatewayDepositDto, userId: string) {
|
||||||
const queryRunner = this.dataSource.createQueryRunner();
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
await queryRunner.connect();
|
|
||||||
await queryRunner.startTransaction();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { amount, gatewayId } = chargeDto;
|
await queryRunner.connect();
|
||||||
|
await queryRunner.startTransaction();
|
||||||
|
const { amount, gatewayId, invoiceId } = chargeDto;
|
||||||
|
|
||||||
const user = await this.getUserById(userId, queryRunner);
|
const user = await this.getUserById(userId, queryRunner);
|
||||||
const paymentGateway = await this.getPaymentGatewayById(gatewayId, 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);
|
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);
|
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) {
|
async verifyPayment(verifyParamDto: VerifyParamDto, queryDto: VerifyQueryDto, rep: FRply) {
|
||||||
const frontUrl = this.buildFrontendRedirectUrl();
|
const frontUrl = this.buildFrontendRedirectUrl(verifyParamDto.invoiceId);
|
||||||
const queryRunner = this.dataSource.createQueryRunner();
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
await queryRunner.startTransaction();
|
await queryRunner.startTransaction();
|
||||||
|
|
||||||
const paymentGateway = this.gatewayFactory.getPaymentGateway(gateway);
|
const paymentGateway = this.gatewayFactory.getPaymentGateway(verifyParamDto.gateway);
|
||||||
const payment = await this.getPaymentByReference(queryDto.Authority, queryRunner);
|
const payment = await this.getPaymentByReference(queryDto.Authority, queryRunner);
|
||||||
|
|
||||||
if (payment.status === PaymentStatus.COMPLETED) throw new BadRequestException(PaymentMessage.VALIDATED_BEFORE);
|
if (payment.status === PaymentStatus.COMPLETED) throw new BadRequestException(PaymentMessage.VALIDATED_BEFORE);
|
||||||
@@ -409,9 +415,13 @@ export class PaymentsService {
|
|||||||
//-private methods
|
//-private methods
|
||||||
//----------------------------------------
|
//----------------------------------------
|
||||||
|
|
||||||
private buildFrontendRedirectUrl(): URL {
|
private buildFrontendRedirectUrl(invoiceId?: string): URL {
|
||||||
const frontUrl = new URL(this.configService.getOrThrow<string>("SITE_URL"));
|
const frontUrl = new URL(this.configService.getOrThrow<string>("SITE_URL"));
|
||||||
|
if (invoiceId) {
|
||||||
|
frontUrl.pathname = "/receipts/" + invoiceId;
|
||||||
|
} else {
|
||||||
frontUrl.pathname = "/payment";
|
frontUrl.pathname = "/payment";
|
||||||
|
}
|
||||||
return frontUrl;
|
return frontUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user