Files
MinglarBackendNestJS/src/modules/user/services/payment.service.ts

178 lines
4.7 KiB
TypeScript

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<string, string | number | boolean | null>;
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,
};
}
}