import { Injectable } from '@nestjs/common'; import { Prisma, PrismaClient } from '@prisma/client'; import crypto from 'crypto'; import Razorpay from 'razorpay'; import ApiError from '../../../common/utils/helper/ApiError'; const razorpay = new Razorpay({ key_id: process.env.RAZORPAY_KEY_ID!, key_secret: process.env.RAZORPAY_KEY_SECRET!, }); @Injectable() export class PaymentService { constructor(private prisma: PrismaClient) {} async createOrder( userXid: number, payload: { amount: number; currency?: string; receipt?: string; notes?: Record; itineraryHeaderXid?: number; }, ) { if (!process.env.RAZORPAY_KEY_ID || !process.env.RAZORPAY_KEY_SECRET) { throw new ApiError(500, 'Razorpay credentials are not configured.'); } if (!Number.isFinite(payload.amount) || payload.amount <= 0) { throw new ApiError(400, 'amount must be a positive number.'); } if ( payload.itineraryHeaderXid !== undefined && payload.itineraryHeaderXid !== null ) { const itineraryHeader = await this.prisma.itineraryHeader.findFirst({ where: { id: payload.itineraryHeaderXid, ownerXid: userXid, isActive: true, deletedAt: null, }, select: { id: true, }, }); if (!itineraryHeader) { throw new ApiError( 404, 'Itinerary not found for the logged-in user.', ); } } const amountInPaise = Math.round(payload.amount * 100); const receipt = payload.receipt ?? `receipt_${Date.now()}`; const currency = payload.currency ?? 'INR'; const order = (await razorpay.orders.create({ amount: amountInPaise, currency, receipt, notes: payload.notes ?? undefined, } as any)) as any; const paymentOrder = await this.prisma.paymentOrders.create({ data: { userXid, itineraryHeaderXid: payload.itineraryHeaderXid ?? null, razorpayOrderId: order.id, receipt: order.receipt ?? receipt, amount: order.amount ?? amountInPaise, currency: order.currency ?? currency, paymentStatus: order.status ?? 'created', notes: payload.notes ? (payload.notes as Prisma.InputJsonValue) : null, isActive: true, }, }); return { paymentOrderId: paymentOrder.id, orderId: order.id, amount: paymentOrder.amount, currency: paymentOrder.currency, receipt: paymentOrder.receipt, status: paymentOrder.paymentStatus, }; } async verifyPayment( userXid: number, payload: { paymentId: string; orderId: string; signature: string; }, ) { if (!process.env.RAZORPAY_KEY_SECRET) { throw new ApiError(500, 'Razorpay credentials are not configured.'); } const paymentId = payload.paymentId?.trim(); const orderId = payload.orderId?.trim(); const signature = payload.signature?.trim(); if (!paymentId || !orderId || !signature) { throw new ApiError( 400, 'paymentId, orderId and signature are required.', ); } const generatedSignature = crypto .createHmac('sha256', process.env.RAZORPAY_KEY_SECRET) .update(`${orderId}|${paymentId}`) .digest('hex'); if (generatedSignature !== signature) { throw new ApiError(400, 'Invalid signature.'); } const paymentOrder = await this.prisma.paymentOrders.findFirst({ where: { razorpayOrderId: orderId, userXid, isActive: true, deletedAt: null, }, }); if (!paymentOrder) { throw new ApiError(404, 'Payment order not found.'); } if ( paymentOrder.paymentStatus === 'paid' && paymentOrder.razorpayPaymentId === paymentId ) { return { paymentOrderId: paymentOrder.id, orderId: paymentOrder.razorpayOrderId, paymentId: paymentOrder.razorpayPaymentId, status: paymentOrder.paymentStatus, verifiedAt: paymentOrder.verifiedAt, paidAt: paymentOrder.paidAt, }; } const updatedPaymentOrder = await this.prisma.paymentOrders.update({ where: { id: paymentOrder.id, }, data: { razorpayPaymentId: paymentId, razorpaySignature: signature, paymentStatus: 'paid', verifiedAt: new Date(), paidAt: new Date(), }, }); return { paymentOrderId: updatedPaymentOrder.id, orderId: updatedPaymentOrder.razorpayOrderId, paymentId: updatedPaymentOrder.razorpayPaymentId, status: updatedPaymentOrder.paymentStatus, verifiedAt: updatedPaymentOrder.verifiedAt, paidAt: updatedPaymentOrder.paidAt, }; } }