195 lines
6.5 KiB
TypeScript
195 lines
6.5 KiB
TypeScript
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<Payment>);
|
|
|
|
await this.em.persistAndFlush(payment);
|
|
return { authority, payment };
|
|
}
|
|
|
|
async verifyPayment(authority: string, orderId: string): Promise<Payment> {
|
|
// 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<string, any>;
|
|
payment.paidAt = new Date();
|
|
payment.order.paymentStatus = PaymentStatusEnum.Paid;
|
|
} else {
|
|
// Payment failed
|
|
payment.status = PaymentStatusEnum.Failed;
|
|
payment.verifyResponse = response as unknown as Record<string, any>;
|
|
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`;
|
|
// }
|
|
}
|