payment refactor

This commit is contained in:
2025-12-15 23:42:28 +03:30
parent 3e4a4eebe4
commit 5a00874a4e
6 changed files with 344 additions and 432 deletions
@@ -1,5 +1,4 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { PaymentGatewayService } from './payment-gateway.service';
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
import { Payment } from '../entities/payment.entity';
import { EntityManager, RequiredEntityData } from '@mikro-orm/core';
@@ -7,56 +6,18 @@ import { Order } from '../../orders/entities/order.entity';
import { PaymentMethod } from '../entities/payment-method.entity';
import { CreatePaymentDto } from '../dto/create-payment.dto';
import { User } from '../../users/entities/user.entity';
import { ZarinpalGateway } from '../gateways/zarinpal.gateway';
import { Logger } from '@nestjs/common';
@Injectable()
export class PaymentsService {
private readonly logger = new Logger(PaymentsService.name);
constructor(
private readonly em: EntityManager,
private readonly paymentGatewayService: PaymentGatewayService,
private readonly zarinpalGateway: ZarinpalGateway,
) {}
// async initializePayment(
// paymentMethodId: string,
// amount: number,
// orderId: string,
// ): Promise<{ paymentUrl: string | null }> {
// // Validate amount
// if (amount <= 0) {
// throw new BadRequestException('Amount must be greater than zero');
// }
// // Validate order exists
// const order = await this.em.findOne(Order, { id: orderId });
// if (!order) {
// throw new NotFoundException('Order not found');
// }
// // Load restaurant payment method with payment method relationship
// const paymentMethod = await this.em.findOne(PaymentMethod, { id: paymentMethodId }, { populate: ['restaurant'] });
// if (!paymentMethod) {
// throw new NotFoundException('Payment method not found');
// }
// if (paymentMethod.method === PaymentMethodEnum.Online && !paymentMethod.merchantId) {
// throw new BadRequestException('Merchant ID is required for online payments');
// }
// // Create payment record and save authority
// const restaurantDomain = paymentMethod.restaurant.domain;
// const gateway = paymentMethod.gateway;
// const { authority } = await this.createPayment(restaurantDomain, {
// amount,
// orderId,
// paymentMethod: paymentMethod.method,
// merchantId: paymentMethod.merchantId ?? null,
// gateway,
// });
// const paymentUrl = this.paymentGatewayService.zarinpalPaymentUrl(gateway, authority);
// return { paymentUrl };
// }
async startPayment(orderId: string, restId: string): Promise<{ paymentUrl: string | null }> {
const { amount, method, restaurantDomain, gateway, merchantId, user } = await this.validateOrder(orderId, restId);
@@ -81,7 +42,7 @@ export class PaymentsService {
gateway,
});
const paymentUrl = this.paymentGatewayService.zarinpalPaymentUrl(gateway, authority);
const paymentUrl = this.zarinpalGateway.zarinpalPaymentUrl(gateway, authority);
return { paymentUrl };
}
@@ -123,14 +84,7 @@ export class PaymentsService {
async createPayment(domain: string, dto: CreatePaymentDto) {
const { amount, orderId, merchantId, gateway, method } = dto;
const { authority } = await this.paymentGatewayService.requestToGateway(
method,
amount,
orderId,
merchantId,
domain,
gateway,
);
const { authority } = await this.requestToGateway(method, amount, orderId, merchantId, domain, gateway);
const payment = this.em.create(Payment, {
amount,
@@ -242,7 +196,7 @@ export class PaymentsService {
// Handle ZarinPal gateway verification
if (payment.gateway === PaymentGatewayEnum.ZarinPal) {
try {
const verifyResponse = await this.paymentGatewayService.verifyZarinPalPayment({
const verifyResponse = await this.zarinpalGateway.verifyPayment({
merchantId: paymentMethod.merchantId,
amount: verifyAmount,
authority,
@@ -293,10 +247,6 @@ export class PaymentsService {
throw new BadRequestException(`Unsupported payment gateway: ${String(payment.gateway)}`);
}
// update(_id: number, _updatePaymentDto: unknown) {
// return `This action updates a #${_id} payment`;
// }
findAllByRestaurantId(restId: string, userId: string) {
return this.em.find(
Payment,
@@ -305,7 +255,65 @@ export class PaymentsService {
);
}
// remove(id: number) {
// return `This action removes a #${id} payment`;
// }
async requestToGateway(
paymentMethod: PaymentMethodEnum,
amount: number,
orderId: string,
merchantId: string | null | undefined,
domain: string,
gateway: PaymentGatewayEnum | null | undefined,
): Promise<{ authority: string | null }> {
// For non-online payment methods, no gateway request is needed
if (paymentMethod !== PaymentMethodEnum.Online) {
return { authority: null };
}
// Online payments require merchantId and gateway
if (!merchantId) {
throw new BadRequestException('Merchant ID is required for online payments');
}
if (!gateway) {
throw new BadRequestException('Payment gateway is required for online payments');
}
// Handle ZarinPal gateway
if (gateway === PaymentGatewayEnum.ZarinPal) {
const callbackUrl = `${domain}/verify/${orderId}`;
try {
const gatewayResponse = await this.zarinpalGateway.processPayment({
amount,
merchantId,
description: `Payment for order #${orderId}`,
callbackUrl,
metadata: {
orderId,
},
});
this.logger.log('gatewayResponse', gatewayResponse);
// Check gateway response code (typically 100 means success for Iranian gateways)
if (gatewayResponse.code !== 100 && gatewayResponse.code !== 0) {
throw new BadRequestException(`Payment gateway error: ${gatewayResponse.message || 'Unknown error'}`);
}
if (!gatewayResponse.authority) {
throw new BadRequestException('Payment gateway did not return an authority token');
}
return { authority: gatewayResponse.authority };
} catch (error) {
this.logger.error('Error in request to gateway', error);
if (error instanceof BadRequestException) {
throw error;
}
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
throw new BadRequestException(`Failed to connect to payment gateway: ${errorMessage}`);
}
}
// If gateway is not supported, throw an error
throw new BadRequestException(`Unsupported payment gateway: ${String(gateway)}`);
}
}