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'; import { Order } from '../../orders/entities/order.entity'; import { PaymentMethod } from '../entities/payment-method.entity'; import { CreatePaymentDto } from '../dto/create-payment.dto'; @Injectable() export class PaymentsService { constructor( private readonly em: EntityManager, private readonly paymentGatewayService: PaymentGatewayService, ) {} 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 createPayment(domain: string, dto: CreatePaymentDto) { const { amount, orderId, merchantId, gateway, paymentMethod } = dto; const { authority } = await this.paymentGatewayService.requestToGateway( paymentMethod, amount, orderId, merchantId, domain, gateway, ); const payment = this.em.create(Payment, { amount, authority: authority, order: this.em.getReference(Order, orderId), gateway, status: PaymentStatusEnum.Pending, } as RequiredEntityData); await this.em.persistAndFlush(payment); return { authority, payment }; } async verifyPayment(authority: string, orderId: string): Promise { // Find payment by authority and orderId const payment = await this.em.findOne( Payment, { authority, order: { id: orderId } }, { populate: ['order', 'order.paymentMethod'] }, ); if (!payment) { throw new NotFoundException('Payment not found'); } // If payment is already verified, return it if (payment.status === PaymentStatusEnum.Paid) { return payment; } // Get payment method from order (already populated) if (!payment.order.paymentMethod) { throw new NotFoundException('Payment method not found for this order'); } const paymentMethod = await this.em.findOne(PaymentMethod, { id: payment.order.paymentMethod.id }); if (!paymentMethod) { throw new NotFoundException('Payment method not found'); } // For non-online payments, mark as paid directly // if (paymentMethod.method !== PaymentMethodEnum.Online) { // payment.status = PaymentStatusEnum.Paid; // payment.paidAt = new Date(); // await this.em.persistAndFlush(payment); // return payment; // } // Online payments require gateway verification if (!paymentMethod.merchantId) { throw new BadRequestException('Merchant ID is required for online payment verification'); } if (!payment.gateway) { throw new BadRequestException('Payment gateway is required for online payment verification'); } // Use payment amount const verifyAmount = payment.amount; // Handle ZarinPal gateway verification if (payment.gateway === PaymentGatewayEnum.ZarinPal) { try { const verifyResponse = await this.paymentGatewayService.verifyZarinPalPayment({ merchantId: paymentMethod.merchantId, amount: verifyAmount, authority, }); // Type guard to ensure we have a valid response if (!verifyResponse || typeof verifyResponse !== 'object') { throw new BadRequestException('Invalid verification response from payment gateway'); } const response = verifyResponse; if (response.code === 100) { // Payment successful payment.status = PaymentStatusEnum.Paid; payment.refId = String(response.refId); payment.cardPan = response.cardPan; payment.verifyResponse = response as unknown as Record; payment.paidAt = new Date(); payment.order.paymentStatus = PaymentStatusEnum.Paid; } else { // Payment failed payment.status = PaymentStatusEnum.Failed; payment.verifyResponse = response as unknown as Record; payment.failedAt = new Date(); payment.order.paymentStatus = PaymentStatusEnum.Failed; } await this.em.persistAndFlush(payment); return payment; } catch (error: unknown) { // Mark payment as failed on error payment.status = PaymentStatusEnum.Failed; payment.failedAt = new Date(); payment.order.paymentStatus = PaymentStatusEnum.Failed; const errorMessage = error instanceof Error ? error.message : 'Unknown error'; payment.verifyResponse = { error: errorMessage, }; await this.em.persistAndFlush(payment); throw new BadRequestException(`Payment verification failed: ${errorMessage}`); } } // If gateway is not supported, throw an error throw new BadRequestException(`Unsupported payment gateway: ${String(payment.gateway)}`); } // update(_id: number, _updatePaymentDto: unknown) { // return `This action updates a #${_id} payment`; // } // findAll() { // return this.em.find(Payment, {}); // } // remove(id: number) { // return `This action removes a #${id} payment`; // } }