From bbab991e31d118507a2dc48e210f2ca21af6fac4 Mon Sep 17 00:00:00 2001 From: jackiettran <41605212+jackiettran@users.noreply.github.com> Date: Thu, 4 Sep 2025 18:02:01 -0400 Subject: [PATCH] refund confirmation --- .../src/components/EmbeddedStripeCheckout.tsx | 7 + .../components/RentalCancellationModal.tsx | 174 +++++++++++++----- frontend/src/pages/MyRentals.tsx | 37 ++-- 3 files changed, 154 insertions(+), 64 deletions(-) diff --git a/frontend/src/components/EmbeddedStripeCheckout.tsx b/frontend/src/components/EmbeddedStripeCheckout.tsx index a8b0756..01006d1 100644 --- a/frontend/src/components/EmbeddedStripeCheckout.tsx +++ b/frontend/src/components/EmbeddedStripeCheckout.tsx @@ -24,10 +24,15 @@ const EmbeddedStripeCheckout: React.FC = ({ const [clientSecret, setClientSecret] = useState(""); const [creating, setCreating] = useState(false); const [sessionId, setSessionId] = useState(""); + const hasCreatedSession = useRef(false); const createCheckoutSession = useCallback(async () => { + // Prevent multiple session creations + if (hasCreatedSession.current) return; + try { setCreating(true); + hasCreatedSession.current = true; const response = await stripeAPI.createSetupCheckoutSession({ rentalData @@ -36,6 +41,7 @@ const EmbeddedStripeCheckout: React.FC = ({ 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" ); @@ -113,6 +119,7 @@ const EmbeddedStripeCheckout: React.FC = ({ return (
= ({ const [processing, setProcessing] = useState(false); const [error, setError] = useState(null); const [reason, setReason] = useState(""); + const [success, setSuccess] = useState(false); + const [processedRefund, setProcessedRefund] = useState<{ + amount: number; + refundId?: string; + } | null>(null); + const [updatedRental, setUpdatedRental] = useState(null); useEffect(() => { if (show && rental) { @@ -52,8 +58,19 @@ const RentalCancellationModal: React.FC = ({ setError(null); const response = await rentalAPI.cancelRental(rental.id, reason.trim()); - onCancellationComplete(response.data.rental); - onHide(); + + // Store refund details for confirmation screen + setProcessedRefund({ + amount: refundPreview.refundAmount, + refundId: response.data.rental.stripeRefundId + }); + + // Store updated rental data for later callback + setUpdatedRental(response.data.rental); + + // Show success confirmation instead of closing immediately + setSuccess(true); + // Don't call onCancellationComplete here - wait until user clicks "Done" } catch (error: any) { setError(error.response?.data?.error || "Failed to cancel rental"); } finally { @@ -61,6 +78,24 @@ const RentalCancellationModal: React.FC = ({ } }; + const handleClose = () => { + // Call parent callback with updated rental data if we have it + if (updatedRental) { + onCancellationComplete(updatedRental); + } + + // Reset all states when closing + setRefundPreview(null); + setLoading(false); + setProcessing(false); + setError(null); + setReason(""); + setSuccess(false); + setProcessedRefund(null); + setUpdatedRental(null); + onHide(); + }; + const formatCurrency = (amount: number | string | undefined) => { const numAmount = Number(amount) || 0; return `$${numAmount.toFixed(2)}`; @@ -75,19 +110,19 @@ const RentalCancellationModal: React.FC = ({ const handleBackdropClick = useCallback( (e: React.MouseEvent) => { if (e.target === e.currentTarget) { - onHide(); + handleClose(); } }, - [onHide] + [handleClose] ); const handleKeyDown = useCallback( (e: KeyboardEvent) => { if (e.key === "Escape") { - onHide(); + handleClose(); } }, - [onHide] + [handleClose] ); useEffect(() => { @@ -116,31 +151,60 @@ const RentalCancellationModal: React.FC = ({
-
Cancel Rental
+
+ {success ? "Refund Confirmation" : "Cancel Rental"} +
- {loading && ( + {success && processedRefund ? (
-
- Loading... +
+
- Calculating refund... +

Refund Processed Successfully!

+
+
+ {formatCurrency(processedRefund.amount)} has been refunded +
+
+

+ + Your refund will appear in your payment method within 3-5 business days +

+

+ + Refund will be processed to your original payment method +

+
+
+

+ Thank you for using our platform. We hope you'll rent with us again soon! +

- )} + ) : ( + <> + {loading && ( +
+
+ Loading... +
+ Calculating refund... +
+ )} - {error && ( -
- {error} -
- )} + {error && ( +
+ {error} +
+ )} - {refundPreview && !loading && ( + {refundPreview && !loading && ( <>
Rental Details
@@ -206,41 +270,55 @@ const RentalCancellationModal: React.FC = ({ )} + + )}
- - {refundPreview && ( + {success ? ( + ) : ( + <> + + {refundPreview && ( + + )} + )}
diff --git a/frontend/src/pages/MyRentals.tsx b/frontend/src/pages/MyRentals.tsx index 35ef9f6..672ac70 100644 --- a/frontend/src/pages/MyRentals.tsx +++ b/frontend/src/pages/MyRentals.tsx @@ -52,8 +52,8 @@ const MyRentals: React.FC = () => { const handleCancellationComplete = (updatedRental: Rental) => { // Update the rental in the list - setRentals(prev => - prev.map(rental => + setRentals((prev) => + prev.map((rental) => rental.id === updatedRental.id ? updatedRental : rental ) ); @@ -155,15 +155,22 @@ const MyRentals: React.FC = () => { : "bg-danger" }`} > - {rental.status === "pending" + {rental.status === "pending" ? "Awaiting Owner Approval" : rental.status === "confirmed" ? "Confirmed & Paid" - : rental.status.charAt(0).toUpperCase() + rental.status.slice(1) - } + : rental.status.charAt(0).toUpperCase() + + rental.status.slice(1)}
+ {rental.status === "pending" && ( +
+ You'll only be charged if the owner approves your + request. +
+ )} +

Rental Period:
@@ -185,14 +192,6 @@ const MyRentals: React.FC = () => {

)} - {rental.status === "pending" && ( -
- - Awaiting Approval: Your payment method is saved. - You'll only be charged if the owner approves your request. -
- )} - {rental.renterPrivateMessage && rental.renterReviewVisible && (
@@ -222,11 +221,16 @@ const MyRentals: React.FC = () => { ${rental.refundAmount?.toFixed(2) || "0.00"} {rental.refundProcessedAt && ( - Processed: {new Date(rental.refundProcessedAt).toLocaleDateString()} + Processed:{" "} + {new Date( + rental.refundProcessedAt + ).toLocaleDateString()} )} {rental.refundReason && ( - {rental.refundReason} + + {rental.refundReason} + )}
)} @@ -234,7 +238,8 @@ const MyRentals: React.FC = () => { )}
- {(rental.status === "pending" || rental.status === "confirmed") && ( + {(rental.status === "pending" || + rental.status === "confirmed") && (