UNPKG

unipayconnect

Version:

<!-- **unipayconnect/unipayconnect** is a ✨ _special_ ✨ repository because its `README.md` (this file) appears on your GitHub profile.

289 lines (256 loc) 14.3 kB
import React, { useState, useEffect, useMemo } from 'react'; import { useForm } from 'react-hook-form'; import DeleteIcon from '@mui/icons-material/Delete'; import axios from 'axios'; const CheckoutPage = () => { const { register, handleSubmit, formState: { errors }, watch, reset, setError } = useForm(); const [providers, setProviders] = useState([ 'stripe', 'paypal', 'razorpay', ]); const [currencies, setCurrencies] = useState([ 'USD', 'INR', 'EUR', 'GBP', 'JPY' ]); const selectProvider = watch('provider'); const selectedCurrency = watch('currency', 'USD'); const [products, setProducts] = useState([]); const totalAmount = useMemo(() => { return products?.reduce((acc, product) => acc + (product.price * product.quantity), 0) || 0; }, [products]); useEffect(() => { const script = document.createElement("script"); script.src = process.env.REACT_APP_RAZORPAY_URL; script.async = true; document.body.appendChild(script); return () => { document.body.removeChild(script); }; }, []); const onSubmit = async (data) => { const apiKeys = { razorpay: { key_id: process.env.REACT_APP_RAZORPAY_KEY_ID, key_secret: process.env.REACT_APP_RAZORPAY_KEY_SECRET }, stripe: process.env.REACT_APP_STRIPE_SECRET_KEY, paypal: process.env.REACT_APP_PAYPAL_CLIENT_SECRET }; try { const body = { providerName: selectProvider, apiKey: apiKeys[selectProvider] } const result = await axios.post(`${process.env.REACT_APP_API_URL}/api/v1/payments/provider/register`, body) const payload = { price: totalAmount, currency: selectedCurrency, providers: [selectProvider], name: data.name, email: data.email, products: products, }; if (result.data.message) { const response = await axios.post(`${process.env.REACT_APP_API_URL}/api/v1/payments/create-checkout-session`, payload); // Handle redirect/payment flow here... const session = response.data.successfulSessions?.find(session => session.provider === selectProvider); if (session.provider === 'razorpay') { // Frontend Razorpay payment options const razorOptions = { key: `${process.env.REACT_APP_RAZORPAY_KEY_ID}`, // Razorpay Key ID, passed from backend if needed amount: session.sessionData.amount, // In paise, ensure this is calculated correctly currency: session.sessionData.currency, // Set currency // "name": name, // Name of the payer description: "Transaction", // Short description order_id: session.sessionData.id, // Razorpay order ID (should be generated from the backend) handler: async function (response) { // Handle successful payment response from Razorpay console.log("Payment ID:", response.razorpay_payment_id); console.log("Order ID:", response.razorpay_order_id); console.log("Signature:", response.razorpay_signature); }, prefill: { name: data.name, // Pre-fill user’s name email: data.email // Pre-fill user’s email }, theme: { "color": "#3399cc" // Customize the theme color of Razorpay checkout } }; // Initialize Razorpay payment with the options const rzp1 = new window.Razorpay(razorOptions); // Start the Razorpay payment process rzp1.open(); } else { if (session.url) { window.location.href = session.url; // Redirect to the payment gateway } } reset(); setProducts([]); } else { console.error("Error: Registering provider", selectProvider); } } catch (error) { console.error('Error creating checkout session:', error); // Handle API error response if (error.response && error.response.data) { const apiError = error.response.data.error; // Adjust based on your API's error structure setError("apiError", { type: "manual", message: apiError || "An error occurred. Please try again.", }); } } }; const handleAddProduct = () => { setProducts([...products, { name: '', price: 0, quantity: 1 }]); }; // Handle removing a product const handleRemoveProduct = (index) => { const updatedProducts = products?.filter((_, i) => i !== index); setProducts(updatedProducts); }; const handleProductChange = (index, field, value) => { const updatedProducts = products.map((product, i) => i === index ? { ...product, [field]: field === 'price' || field === 'quantity' ? Number(value) : value } : product ); setProducts(updatedProducts); }; return ( <> <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> {/* Left Column: User Details & Payment Form */} <div className="p-6"> <h3 className="text-2xl font-semibold mb-4">Billing Information</h3> <form onSubmit={handleSubmit(onSubmit)}> {/* Name Field */} <div className="mb-4"> <label className="block text-gray-700 text-sm font-bold mb-2">Name</label> <input type="text" {...register('name', { required: 'Name is required' })} className={`w-full px-3 py-2 border ${errors.name ? 'border-red-500' : 'border-gray-300'} rounded-md`} /> {errors.name && <p className="text-red-500 text-sm mt-1">{errors.name.message}</p>} </div> {/* Email Field */} <div className="mb-4"> <label className="block text-gray-700 text-sm font-bold mb-2">Email</label> <input type="email" {...register('email', { required: 'Email is required', pattern: { value: /^\S+@\S+$/, message: 'Invalid email format' } })} className={`w-full px-3 py-2 border ${errors.email ? 'border-red-500' : 'border-gray-300'} rounded-md`} /> {errors.email && <p className="text-red-500 text-sm mt-1">{errors.email.message}</p>} </div> {/* Currency Selection */} <div className="mb-4"> <label className="block text-gray-700 text-sm font-bold mb-2">Currency</label> <select {...register('currency', { required: 'Currency is required' })} className="w-full px-3 py-2 border border-gray-300 rounded-md" > {currencies?.map((currency) => ( <option key={currency} value={currency}> {currency} </option> ))} </select> {errors.currency && <p className="text-red-500 text-sm mt-1">{errors.currency.message}</p>} </div> {/* Payment Provider Selection */} <div className="mb-4"> <label className="block text-gray-700 text-sm font-bold mb-2">Payment Provider</label> <div className="flex flex-row space-x-2"> {providers?.map((provider) => { return ( <label key={provider} className="flex items-center cursor-pointer"> <input type="radio" value={provider} {...register('provider', { required: true })} className="mr-2 cursor-pointer" /> {provider.charAt(0).toUpperCase() + provider?.slice(1)} </label> ) })} </div> {errors.provider && <p className="text-red-500">Provider selection is required</p>} </div> {/* Submit Button */} <button type="submit" className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700" > Pay Now </button> {errors.apiError && <p className="text-red-500 text-center">{errors.apiError.message}</p>} </form> </div> {/* Right Column: Order Summary */} <div className="p-6 bg-gray-100 rounded-md"> <h3 className="text-2xl font-semibold mb-4">Order Summary</h3> {/* Wrapping the product list and total in a flex column container */} <div className="flex flex-col h-[80%]"> {/* Product list container with scroll */} <div className="flex-grow border-t border-gray-300 pt-4 overflow-y-scroll"> {products?.map((product, index) => ( <div key={index} className="mb-2 flex justify-between items-center"> {/* Editable fields for product name, quantity, price */} <input type="text" value={product.name} onChange={(e) => handleProductChange(index, 'name', e.target.value)} className="w-1/3 px-2 py-1 border border-gray-300 rounded-md mr-2" placeholder="Product Name" /> <input type="number" value={product.quantity} onChange={(e) => handleProductChange(index, 'quantity', e.target.value)} className="w-1/5 px-2 py-1 border border-gray-300 rounded-md mr-2" placeholder="Quantity" /> <input type="number" value={product.price} onChange={(e) => handleProductChange(index, 'price', e.target.value)} className="w-1/5 px-2 py-1 border border-gray-300 rounded-md mr-2" placeholder="Price" /> {/* Remove product button */} <button type="button" onClick={() => handleRemoveProduct(index)} className="text-red-500 hover:text-red-700" > <DeleteIcon /> </button> </div> ))} </div> {/* Button to add a new product */} <button type="button" onClick={handleAddProduct} className="mt-4 bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700" > Add Product </button> {/* Fixed total at the bottom */} <div className="border-t border-gray-300 pt-4 flex justify-between sticky bottom-0 bg-gray-100"> <span className="text-gray-700 font-semibold">Total:</span> <span className="font-bold"> {selectedCurrency} {totalAmount.toFixed(2)} </span> </div> </div> </div> </div> </> ); }; export default CheckoutPage;