import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { RestaurantPaymentMethod } from '../entities/restaurant-payment-method.entity'; import axios from 'axios'; import { IPaymentRequest, IPaymentResponse, PaymentStatus } from '../interface/payment-status'; import { Payment } from '../entities/payment.entity'; import { EntityManager, RequiredEntityData } from '@mikro-orm/core'; import { Order } from '../../orders/entities/order.entity'; @Injectable() export class PaymentsService { constructor(private readonly em: EntityManager) {} async initializePayment( restaurantPaymentMethodId: 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 restaurantPaymentMethod = await this.em.findOne( RestaurantPaymentMethod, { id: restaurantPaymentMethodId }, { populate: ['paymentMethod'] }, ); if (!restaurantPaymentMethod) { throw new NotFoundException('Restaurant payment method not found'); } if (!restaurantPaymentMethod.paymentMethod?.isOnline) { return { paymentUrl: null }; } if (!restaurantPaymentMethod.callbackUrl) { throw new BadRequestException('Callback URL is not configured'); } if (!restaurantPaymentMethod.merchantId) { throw new BadRequestException('Merchant ID is not provided'); } if (!restaurantPaymentMethod.paymentMethod?.paymentUrl) { throw new BadRequestException('Payment URL is not configured'); } // Request to payment gateway with error handling let gatewayResponse: IPaymentResponse; try { gatewayResponse = await this.requestToPaymentGateway(restaurantPaymentMethod.paymentMethod.paymentUrl, { amount, callbackUrl: restaurantPaymentMethod.callbackUrl, merchantId: restaurantPaymentMethod.merchantId, description: `Payment for order #${orderId}`, }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; throw new BadRequestException(`Failed to connect to payment gateway: ${errorMessage}`); } // 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'); } // Create payment record and save authority const payment = this.em.create(Payment, { amount, authority: gatewayResponse.authority, order: this.em.getReference(Order, orderId), gateway: restaurantPaymentMethod.paymentMethod.name, status: PaymentStatus.Pending, } as RequiredEntityData); await this.em.persistAndFlush(payment); // Return payment URL const paymentUrl = `${restaurantPaymentMethod.paymentMethod.paymentUrl}/${gatewayResponse.authority}`; return { paymentUrl }; } private async requestToPaymentGateway( gatewayPaymentUrl: string, requestPayment: IPaymentRequest, ): Promise { const response = await axios.post(gatewayPaymentUrl, requestPayment); return response.data; } // eslint-disable-next-line @typescript-eslint/no-unused-vars 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`; } }