payment for rental from renter stripe integration
This commit is contained in:
247
frontend/src/components/CheckoutReturn.tsx
Normal file
247
frontend/src/components/CheckoutReturn.tsx
Normal file
@@ -0,0 +1,247 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useSearchParams, useNavigate } from "react-router-dom";
|
||||
import { stripeAPI, rentalAPI } from "../services/api";
|
||||
|
||||
const CheckoutReturn: React.FC = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const [status, setStatus] = useState<
|
||||
"loading" | "success" | "error" | "failed" | "rental_error"
|
||||
>("loading");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const hasProcessed = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasProcessed.current) return;
|
||||
|
||||
const sessionId = searchParams.get("session_id");
|
||||
|
||||
if (!sessionId) {
|
||||
setStatus("error");
|
||||
setError("No session ID found in URL");
|
||||
return;
|
||||
}
|
||||
|
||||
hasProcessed.current = true;
|
||||
checkSessionStatus(sessionId);
|
||||
}, [searchParams]);
|
||||
|
||||
const createRental = async () => {
|
||||
try {
|
||||
// Get rental data from localStorage (set before payment)
|
||||
const rentalDataString = localStorage.getItem("pendingRental");
|
||||
|
||||
if (!rentalDataString) {
|
||||
console.error("No rental data found in localStorage");
|
||||
throw new Error("No rental data found in localStorage");
|
||||
}
|
||||
|
||||
const rentalData = JSON.parse(rentalDataString);
|
||||
|
||||
const response = await rentalAPI.createRental(rentalData);
|
||||
|
||||
// Clear the pending rental data
|
||||
localStorage.removeItem("pendingRental");
|
||||
localStorage.removeItem("lastItemId");
|
||||
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
const errorMessage =
|
||||
error.response?.data?.message ||
|
||||
error.message ||
|
||||
"Failed to create rental";
|
||||
console.error("Throwing error:", errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const checkSessionStatus = async (sessionId: string) => {
|
||||
try {
|
||||
setProcessing(true);
|
||||
|
||||
// Get checkout session status
|
||||
const response = await stripeAPI.getCheckoutSession(sessionId);
|
||||
|
||||
const { status: sessionStatus, payment_status } = response.data;
|
||||
|
||||
if (sessionStatus === "complete" && payment_status === "paid") {
|
||||
// Payment was successful - now create the rental
|
||||
try {
|
||||
const rentalResult = await createRental();
|
||||
setStatus("success");
|
||||
} catch (rentalError: any) {
|
||||
// Payment succeeded but rental creation failed
|
||||
setStatus("rental_error");
|
||||
setError(rentalError.message || "Failed to create rental record");
|
||||
}
|
||||
} else if (sessionStatus === "open") {
|
||||
// Payment was not completed
|
||||
setStatus("failed");
|
||||
setError("Payment was not completed. Please try again.");
|
||||
} else {
|
||||
setStatus("error");
|
||||
setError("Payment failed or was cancelled.");
|
||||
}
|
||||
} catch (error: any) {
|
||||
setStatus("error");
|
||||
setError(
|
||||
error.response?.data?.error || "Failed to verify payment status"
|
||||
);
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRetry = () => {
|
||||
// Go back to the item page to try payment again
|
||||
const itemId = localStorage.getItem("lastItemId");
|
||||
if (itemId) {
|
||||
navigate(`/items/${itemId}`);
|
||||
} else {
|
||||
navigate("/");
|
||||
}
|
||||
};
|
||||
|
||||
const handleRetryRentalCreation = async () => {
|
||||
setProcessing(true);
|
||||
try {
|
||||
await createRental();
|
||||
setStatus("success");
|
||||
setError(null);
|
||||
} catch (error: any) {
|
||||
setError(error.message || "Failed to create rental record");
|
||||
} finally {
|
||||
setProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGoToRentals = () => {
|
||||
navigate("/my-rentals");
|
||||
};
|
||||
|
||||
if (status === "loading" || processing) {
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-md-6 text-center">
|
||||
<div className="spinner-border mb-3" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<h3>Processing your payment...</h3>
|
||||
<p className="text-muted">
|
||||
Please wait while we confirm your payment and set up your rental.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === "success") {
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-md-6 text-center">
|
||||
<div className="alert alert-success p-4">
|
||||
<i className="bi bi-check-circle-fill display-1 text-success mb-3"></i>
|
||||
<h2>Payment Successful!</h2>
|
||||
<p className="mb-4">
|
||||
Your rental has been confirmed. You can view the details in your
|
||||
rentals page.
|
||||
</p>
|
||||
<div className="d-grid gap-2 d-md-block">
|
||||
<button
|
||||
className="btn btn-primary btn-lg me-2"
|
||||
onClick={handleGoToRentals}
|
||||
>
|
||||
View My Rentals
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary btn-lg"
|
||||
onClick={() => navigate("/")}
|
||||
>
|
||||
Continue Browsing
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === "rental_error") {
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-md-6 text-center">
|
||||
<div className="alert alert-warning p-4">
|
||||
<i className="bi bi-exclamation-triangle-fill display-1 text-warning mb-3"></i>
|
||||
<h2>Payment Successful - Rental Setup Issue</h2>
|
||||
<p className="mb-4">
|
||||
Your payment was processed successfully, but we encountered an
|
||||
issue creating your rental record:
|
||||
<br />
|
||||
<strong>{error}</strong>
|
||||
</p>
|
||||
<div className="d-grid gap-2 d-md-block">
|
||||
<button
|
||||
className="btn btn-primary btn-lg me-2"
|
||||
onClick={handleRetryRentalCreation}
|
||||
disabled={processing}
|
||||
>
|
||||
{processing ? "Retrying..." : "Retry Rental Creation"}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary btn-lg"
|
||||
onClick={() => navigate("/")}
|
||||
>
|
||||
Contact Support
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === "failed" || status === "error") {
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-md-6 text-center">
|
||||
<div className="alert alert-danger p-4">
|
||||
<i className="bi bi-exclamation-triangle-fill display-1 text-danger mb-3"></i>
|
||||
<h2>
|
||||
{status === "failed" ? "Payment Incomplete" : "Payment Error"}
|
||||
</h2>
|
||||
<p className="mb-4">
|
||||
{error || "There was an issue processing your payment."}
|
||||
</p>
|
||||
<div className="d-grid gap-2 d-md-block">
|
||||
<button
|
||||
className="btn btn-primary btn-lg me-2"
|
||||
onClick={handleRetry}
|
||||
>
|
||||
Try Again
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-outline-secondary btn-lg"
|
||||
onClick={() => navigate("/")}
|
||||
>
|
||||
Go Home
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default CheckoutReturn;
|
||||
Reference in New Issue
Block a user