Skip to content

Commit

Permalink
razorpay initiail setup client side commplted
Browse files Browse the repository at this point in the history
  • Loading branch information
sinanptm committed Sep 16, 2024
1 parent 342be40 commit 82ee6ad
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 39 deletions.
69 changes: 61 additions & 8 deletions client/components/forms/patient/AppointmentForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
'use client'
'use client';

import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
Expand All @@ -11,7 +11,7 @@ import { SelectItem } from "@/components/ui/select";
import Image from "next/image";
import { AppointmentTypes } from "@/constants";
import { FormFieldType } from "@/types/fromTypes";
import { useCompletePaymentAppointment, useCreateAppointment, useGetDoctorsList } from "@/lib/hooks/appointment/useAppointment";
import { useVerifyPaymentAppointment, useCreateAppointment, useGetDoctorsList } from "@/lib/hooks/appointment/useAppointment";
import { useGetSlotsOfDoctor } from "@/lib/hooks/slots/useSlot";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
Expand All @@ -20,12 +20,20 @@ import { FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/for
import { toast } from "@/components/ui/use-toast";
import { AppointmentType } from "@/types";
import { useQueryClient } from "@tanstack/react-query";
import loadRazorpayScript from '@/lib/utils/loadRazorpayScript'

// Declare Razorpay in the global scope
declare global {
interface Window {
Razorpay: any;
}
}

const AppointmentForm = () => {
const { data: doctorsData, isLoading: isDoctorsLoading } = useGetDoctorsList();
const { mutate: verifyPayment } = useVerifyPaymentAppointment();
const [isDoctorSelected, setIsDoctorSelected] = useState(false);
const { mutate: createAppointment, isPending } = useCreateAppointment();
const {mutate:completePayment} = useCompletePaymentAppointment()
const query = useQueryClient();

const form = useForm<z.infer<typeof appointmentFormValidation>>({
Expand Down Expand Up @@ -77,15 +85,60 @@ const AppointmentForm = () => {
},
},
{
onSuccess: async({paymentSessionId})=> {
onSuccess: async ({ appointmentId, orderId, patient }) => {
toast({
title: "Appointment Created",
description: "We will notify you once the doctor approves your appointment",
description: "Redirecting to payment...",
variant: "success",
});
query.invalidateQueries({
queryKey: ["doctorSlots", slotFilter.doctorId, slotFilter.date instanceof Date ? slotFilter.date.toISOString() : ""]
})

// Load Razorpay SDK
const isRazorpayLoaded = await loadRazorpayScript();
if (!isRazorpayLoaded) {
toast({
title: "Payment Error",
description: "Razorpay SDK failed to load. Please try again.",
variant: "destructive",
});
return;
}

const paymentOptions = {
key: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID,
amount: 300 * 100,
currency: "INR",
name: "Your App",
description: "Appointment Payment",
order_id: orderId,
handler: async function (response: { razorpay_order_id: string, razorpay_payment_id: string, razorpay_signature: string }) {
verifyPayment({
data: {
appointmentId,
paymentData: {
razorpay_order_id: response.razorpay_order_id,
razorpay_payment_id: response.razorpay_payment_id,
razorpay_signature: response.razorpay_signature,
}
}
});
toast({
title: "Payment Success",
description: "Your payment was successful and the appointment is confirmed.",
variant: "success",
});
},
prefill: {
name: patient.name,
email: patient.email,
contact: patient.phone,
},
theme: {
color: "#3399cc",
},
};

const paymentObject = new window.Razorpay(paymentOptions);
paymentObject.open();
},
onError(error) {
const message =
Expand Down
13 changes: 9 additions & 4 deletions client/lib/api/appointment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,17 @@ export const getDoctorsList = async () => {
return response.data;
};

export const createAppointment = async (appointment:IAppointment)=>{
const response = await axiosInstance.post('/',{appointment});
export const createAppointment = async (appointment: IAppointment) => {
const response = await axiosInstance.post('/', { appointment });
return response.data;
}

export const completePayment = async (data:any)=>{
const response = await axiosInstance.put('/',data);
export const verifyPayment = async ({ appointmentId, paymentData }: verifyPaymentProps) => {
const response = await axiosInstance.post('/verify-payment', { paymentData, appointmentId });
return response.data;
}

export type verifyPaymentProps = {
paymentData: { razorpay_order_id: string; razorpay_payment_id: string; razorpay_signature: string };
appointmentId: string
}
28 changes: 12 additions & 16 deletions client/lib/hooks/appointment/useAppointment.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { completePayment, createAppointment, getDoctorsList } from "@/lib/api/appointment"
import IAppointment, { ErrorResponse, IDoctor, MessageResponse, PaginatedResult } from "@/types"
import { verifyPayment, createAppointment, getDoctorsList, verifyPaymentProps } from "@/lib/api/appointment"
import IAppointment, { ErrorResponse, IDoctor, IPatient, MessageResponse, PaginatedResult } from "@/types"
import { useMutation, useQuery } from "@tanstack/react-query"
import { AxiosError } from "axios"

interface MessageWithSessionId extends MessageResponse{
paymentSessionId:string
}

export const useGetDoctorsList = () => {
return useQuery<PaginatedResult<IDoctor>, AxiosError<ErrorResponse>>({
queryFn: () => getDoctorsList(),
Expand All @@ -15,20 +11,20 @@ export const useGetDoctorsList = () => {
});
};

export const useCreateAppointment = ()=>{
return useMutation<MessageWithSessionId,AxiosError<ErrorResponse>,{appointment:IAppointment}>({
mutationFn:({appointment})=>createAppointment(appointment),
onError:(error)=>{
console.log("Error in creating appointment",error);
export const useCreateAppointment = () => {
return useMutation<{ orderId: string, appointmentId: string, patient:IPatient }, AxiosError<ErrorResponse>, { appointment: IAppointment }>({
mutationFn: ({ appointment }) => createAppointment(appointment),
onError: (error) => {
console.log("Error in creating appointment", error);
}
})
}

export const useCompletePaymentAppointment = ()=>{
return useMutation<MessageWithSessionId,AxiosError<ErrorResponse>,{data:any}>({
mutationFn:({data})=>completePayment(data),
onError:(error)=>{
console.log("Error in completing payment ",error);
export const useVerifyPaymentAppointment = () => {
return useMutation<MessageResponse, AxiosError<ErrorResponse>, { data: verifyPaymentProps }>({
mutationFn: ({ data }) => verifyPayment(data),
onError: (error) => {
console.log("Error in Verifying payment ", error);
}
})
}
11 changes: 11 additions & 0 deletions client/lib/utils/loadRazorpayScript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const loadRazorpayScript = (): Promise<boolean> => {
return new Promise((resolve) => {
const script = document.createElement("script");
script.src = "https://checkout.razorpay.com/v1/checkout.js";
script.onload = () => resolve(true);
script.onerror = () => resolve(false);
document.body.appendChild(script);
});
};

export default loadRazorpayScript
10 changes: 10 additions & 0 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"next": "^14.2.7",
"next-themes": "^0.3.0",
"otp-timer-ts": "^3.0.1",
"razorpay": "^2.9.4",
"react": "^18",
"react-datepicker": "^7.3.0",
"react-dom": "^18",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ export default class AppointmentController {
try {
const { appointment } = req.body;
const patientId = req.patient?.id;
const { appointmentId, orderId } = await this.appointmentUseCase.createAppointment(appointment, patientId!);
res.status(StatusCode.Success).json({ orderId, appointmentId });
const { appointmentId, orderId, patient } = await this.appointmentUseCase.createAppointment(appointment, patientId!);
res.status(StatusCode.Success).json({ orderId, appointmentId, patient });
} catch (error: any) {
next(error);
}
}

async completePayment(req: CustomRequest, res: Response, next: NextFunction) {
try {
const { razorpay_order_id, razorpay_payment_id, razorpay_signature, appointmentId } = req.body;
await this.appointmentUseCase.verifyPayment({ razorpay_order_id, razorpay_payment_id, razorpay_signature }, appointmentId)
const { appointmentId,paymentData } = req.body;
await this.appointmentUseCase.verifyPayment(paymentData, appointmentId)
res.status(StatusCode.Success).json({ message: "Payment Verification Completed" });
} catch (error) {
next(error)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import JWTService from '../../../infrastructure/services/JWTService';
import JoiService from '../../../infrastructure/services/JoiService';
import RazorPayService from '../../../infrastructure/services/RazorPayService';
import PaymentRepository from '../../../infrastructure/repositories/PaymentRepository';
import PatientRepository from '../../../infrastructure/repositories/PatientRepository';

const router = express.Router();

Expand All @@ -17,9 +18,10 @@ const slotRepository = new SlotRepository();
const tokenService = new JWTService();
const validatorService = new JoiService();
const paymentService = new RazorPayService();
const paymentRepository = new PaymentRepository()
const paymentRepository = new PaymentRepository();
const patientRepository = new PatientRepository()

const appointmentUseCase = new AppointmentUseCase(appointmentRepository, slotRepository, validatorService, paymentService, paymentRepository);
const appointmentUseCase = new AppointmentUseCase(appointmentRepository, slotRepository, validatorService, paymentService, paymentRepository, patientRepository);

const appointmentController = new AppointmentController(appointmentUseCase);

Expand Down
15 changes: 10 additions & 5 deletions server/src/use_case/appointment/AppointmentUseCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { StatusCode } from "../../types";
import IPaymentService from "../../domain/interface/services/IPaymentService";
import IPaymentRepository from "../../domain/interface/repositories/IPaymentRepository";
import { PaymentStatus } from "../../domain/entities/IPayment";
import IPatientRepository from "../../domain/interface/repositories/IPatientRepository";
import { IPatient } from "../../domain/entities/IPatient";

export default class AppointmentUseCase {
bookingAmount: number;
Expand All @@ -16,15 +18,16 @@ export default class AppointmentUseCase {
private slotRepository: ISlotRepository,
private validatorService: IValidatorService,
private paymentService: IPaymentService,
private paymentRepository: IPaymentRepository
private paymentRepository: IPaymentRepository,
private patientRepository: IPatientRepository
) {
this.bookingAmount = 300;
}

async createAppointment(
appointmentData: IAppointment,
patientId: string
): Promise<{ appointmentId: string, orderId: string }> {
): Promise<{ appointmentId: string, orderId: string, patient: IPatient }> {
this.validateAppointmentData(appointmentData, patientId);

const slot = await this.slotRepository.findById(appointmentData.slotId!);
Expand All @@ -48,7 +51,7 @@ export default class AppointmentUseCase {

const razorpayOrder = await this.paymentService.createOrder(this.bookingAmount, 'INR', `receipt_${payment._id}`);


const appointmentId = await this.appointmentRepository.create({
...appointmentData,
patientId,
Expand All @@ -61,8 +64,10 @@ export default class AppointmentUseCase {
orderId: razorpayOrder.id!,
appointmentId
});

return { orderId: razorpayOrder.id, appointmentId };

const patient = await this.patientRepository.findById(patientId)!

return { orderId: razorpayOrder.id, appointmentId, patient: { email: patient?.email, name: patient?.name, phone: patient?.phone } };
}

async verifyPayment(paymentData: { razorpay_order_id: string, razorpay_payment_id: string, razorpay_signature: string }, appointmentId: string): Promise<void> {
Expand Down

1 comment on commit 82ee6ad

@vercel
Copy link

@vercel vercel bot commented on 82ee6ad Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.