import React, { useState, useEffect, useCallback, useRef } from "react"; import { loadStripe } from "@stripe/stripe-js"; import { EmbeddedCheckoutProvider, EmbeddedCheckout, } from "@stripe/react-stripe-js"; import { stripeAPI, rentalAPI } from "../services/api"; const stripePromise = loadStripe( import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY || "" ); interface UpdatePaymentMethodProps { rentalId: string; itemName: string; onSuccess: () => void; onError: (error: string) => void; onCancel: () => void; } const UpdatePaymentMethod: React.FC = ({ rentalId, itemName, onSuccess, onError, onCancel, }) => { const [clientSecret, setClientSecret] = useState(""); const [creating, setCreating] = useState(false); const [sessionId, setSessionId] = useState(""); const [updating, setUpdating] = useState(false); const hasCreatedSession = useRef(false); // Use refs to avoid recreating handleComplete when props change const rentalIdRef = useRef(rentalId); const onSuccessRef = useRef(onSuccess); const onErrorRef = useRef(onError); const sessionIdRef = useRef(sessionId); // Keep refs up to date rentalIdRef.current = rentalId; onSuccessRef.current = onSuccess; onErrorRef.current = onError; sessionIdRef.current = sessionId; const createCheckoutSession = useCallback(async () => { // Prevent multiple session creations if (hasCreatedSession.current) return; try { setCreating(true); hasCreatedSession.current = true; // Create a setup checkout session without rental data // (we're updating an existing rental, not creating a new one) const response = await stripeAPI.createSetupCheckoutSession({}); setClientSecret(response.data.clientSecret); setSessionId(response.data.sessionId); } catch (error: any) { hasCreatedSession.current = false; // Reset on error so it can be retried onError( error.response?.data?.error || "Failed to create checkout session" ); } finally { setCreating(false); } }, [onError]); useEffect(() => { createCheckoutSession(); }, [createCheckoutSession]); // Use useCallback with empty deps - refs provide access to latest values const handleComplete = useCallback(() => { (async () => { try { setUpdating(true); if (!sessionIdRef.current) { throw new Error("No session ID available"); } // Get the completed checkout session const sessionResponse = await stripeAPI.getCheckoutSession( sessionIdRef.current ); const { status: sessionStatus, setup_intent } = sessionResponse.data; if (sessionStatus !== "complete") { throw new Error("Payment setup was not completed"); } if (!setup_intent?.payment_method) { throw new Error("No payment method found in setup intent"); } // Extract payment method ID - handle both string ID and object cases const paymentMethodId = typeof setup_intent.payment_method === "string" ? setup_intent.payment_method : setup_intent.payment_method.id; if (!paymentMethodId) { throw new Error("No payment method ID found"); } // Update the rental's payment method await rentalAPI.updatePaymentMethod(rentalIdRef.current, paymentMethodId); onSuccessRef.current(); } catch (error: any) { onErrorRef.current( error.response?.data?.error || error.message || "Failed to update payment method" ); } finally { setUpdating(false); } })(); }, []); if (creating) { return (
Loading...

Preparing secure checkout...

); } if (updating) { return (
Updating...

Updating payment method...

); } if (!clientSecret) { return (

Unable to load checkout

); } return (
Update Payment Method

Update your payment method for {itemName}. Once updated, the owner will be notified and can re-attempt to approve your rental request.

); }; export default UpdatePaymentMethod;