payment
This commit is contained in:
@@ -1,17 +1,24 @@
|
||||
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 {
|
||||
IPaymentRequest,
|
||||
IPaymentResponse,
|
||||
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) {}
|
||||
|
||||
async initializePayment(
|
||||
restaurantPaymentMethodId: string,
|
||||
paymentMethodId: string,
|
||||
amount: number,
|
||||
orderId: string,
|
||||
): Promise<{ paymentUrl: string | null }> {
|
||||
@@ -27,78 +34,97 @@ export class PaymentsService {
|
||||
}
|
||||
|
||||
// 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');
|
||||
const paymentMethod = await this.em.findOne(PaymentMethod, { id: paymentMethodId }, { populate: ['restaurant'] });
|
||||
if (!paymentMethod) {
|
||||
throw new NotFoundException('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) {
|
||||
if (paymentMethod.method !== PaymentMethodEnum.Online && !paymentMethod.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, {
|
||||
const restaurantDomain = paymentMethod.restaurant.domain;
|
||||
const { authority } = await this.createPayment(restaurantDomain, {
|
||||
amount,
|
||||
authority: gatewayResponse.authority,
|
||||
order: this.em.getReference(Order, orderId),
|
||||
gateway: restaurantPaymentMethod.paymentMethod.name,
|
||||
status: PaymentStatus.Pending,
|
||||
} as RequiredEntityData<Payment>);
|
||||
orderId,
|
||||
paymentMethod: paymentMethod.method,
|
||||
merchantId: paymentMethod.merchantId ?? null,
|
||||
gateway: paymentMethod.gateway as PaymentGatewayEnum | null,
|
||||
});
|
||||
|
||||
await this.em.persistAndFlush(payment);
|
||||
const paymentUrl = this.zarinpalPaymentUrl(paymentMethod.gateway, authority);
|
||||
|
||||
// Return payment URL
|
||||
const paymentUrl = `${restaurantPaymentMethod.paymentMethod.paymentUrl}/${gatewayResponse.authority}`;
|
||||
return { paymentUrl };
|
||||
}
|
||||
|
||||
private async requestToPaymentGateway(
|
||||
gatewayPaymentUrl: string,
|
||||
requestPayment: IPaymentRequest,
|
||||
): Promise<IPaymentResponse> {
|
||||
async createPayment(domain: string, dto: CreatePaymentDto) {
|
||||
const { amount, orderId, merchantId, gateway, paymentMethod } = dto;
|
||||
|
||||
const { authority } = await this.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<Payment>);
|
||||
|
||||
await this.em.persistAndFlush(payment);
|
||||
return { authority, payment };
|
||||
}
|
||||
|
||||
async requestToGateway(
|
||||
paymentMethod: PaymentMethodEnum,
|
||||
amount: number,
|
||||
orderId: string,
|
||||
merchantId: string | null | undefined,
|
||||
domain: string,
|
||||
gateway: PaymentGatewayEnum | null | undefined,
|
||||
): Promise<{ authority: string | null }> {
|
||||
if (paymentMethod === PaymentMethodEnum.Online && merchantId) {
|
||||
const callbackUrl = `${domain}/payments/callback`;
|
||||
if (gateway === PaymentGatewayEnum.ZarinPal) {
|
||||
try {
|
||||
const gatewayResponse = await this.requestToZarinPalGateway({
|
||||
amount,
|
||||
merchantId,
|
||||
description: `Payment for order #${orderId}`,
|
||||
callbackUrl,
|
||||
metadata: {
|
||||
orderId,
|
||||
},
|
||||
});
|
||||
// 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');
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
throw new BadRequestException(`Failed to connect to payment gateway: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { authority: null };
|
||||
}
|
||||
|
||||
private async requestToZarinPalGateway(requestPayment: IPaymentRequest): Promise<IPaymentResponse> {
|
||||
const gatewayPaymentUrl = 'https://payment.zarinpal.com/pg/v4/payment/request.json';
|
||||
const response = await axios.post<IPaymentResponse>(gatewayPaymentUrl, requestPayment);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
zarinpalPaymentUrl(gateway: PaymentGatewayEnum | null, authority: string | null) {
|
||||
if (gateway === PaymentGatewayEnum.ZarinPal) {
|
||||
return `https://payment.zarinpal.com/pg/StartPay/${authority}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
update(_id: number, _updatePaymentDto: unknown) {
|
||||
return `This action updates a #${_id} payment`;
|
||||
|
||||
Reference in New Issue
Block a user