payment for rental from renter stripe integration

This commit is contained in:
jackiettran
2025-08-27 19:46:27 -04:00
parent 601e11b7e8
commit 38346bec27
13 changed files with 1090 additions and 421 deletions

View File

@@ -3,6 +3,7 @@ import { useParams, useNavigate, useSearchParams } from "react-router-dom";
import { Item } from "../types";
import { useAuth } from "../contexts/AuthContext";
import { itemAPI, rentalAPI } from "../services/api";
import StripePaymentForm from "../components/StripePaymentForm";
const RentItem: React.FC = () => {
const { id } = useParams<{ id: string }>();
@@ -11,16 +12,11 @@ const RentItem: React.FC = () => {
const [searchParams] = useSearchParams();
const [item, setItem] = useState<Item | null>(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const [formData, setFormData] = useState({
deliveryMethod: "pickup" as "pickup" | "delivery",
deliveryAddress: "",
cardNumber: "",
cardExpiry: "",
cardCVC: "",
cardName: "",
});
const [manualSelection, setManualSelection] = useState({
@@ -85,6 +81,29 @@ const RentItem: React.FC = () => {
calculateTotalCost();
}, [item, manualSelection]);
// Save rental data to localStorage whenever the form is ready
useEffect(() => {
if (
item &&
manualSelection.startDate &&
manualSelection.endDate &&
totalCost > 0
) {
const rentalData = {
itemId: item.id,
startDate: manualSelection.startDate,
endDate: manualSelection.endDate,
startTime: manualSelection.startTime,
endTime: manualSelection.endTime,
totalAmount: totalCost,
deliveryMethod: "pickup",
};
localStorage.setItem("pendingRental", JSON.stringify(rentalData));
localStorage.setItem("lastItemId", item.id);
}
}, [item, manualSelection, totalCost]);
const fetchItem = async () => {
try {
const response = await itemAPI.getItem(id!);
@@ -106,36 +125,11 @@ const RentItem: React.FC = () => {
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!user || !item) return;
setSubmitting(true);
setError(null);
try {
if (!manualSelection.startDate || !manualSelection.endDate) {
setError("Please select a rental period");
setSubmitting(false);
return;
}
const rentalData = {
itemId: item.id,
startDate: manualSelection.startDate,
endDate: manualSelection.endDate,
startTime: manualSelection.startTime,
endTime: manualSelection.endTime,
totalAmount: totalCost,
deliveryMethod: "pickup",
};
await rentalAPI.createRental(rentalData);
navigate("/my-rentals");
} catch (err: any) {
setError(err.response?.data?.message || "Failed to create rental");
setSubmitting(false);
}
const handlePaymentSuccess = () => {
// This is called when Stripe checkout session is created successfully
// The rental data is already saved to localStorage via useEffect
// The actual rental creation happens in CheckoutReturn component after payment
console.log("Stripe checkout session created successfully");
};
const handleChange = (
@@ -144,41 +138,7 @@ const RentItem: React.FC = () => {
>
) => {
const { name, value } = e.target;
if (name === "cardNumber") {
// Remove all non-digits
const cleaned = value.replace(/\D/g, "");
// Add spaces every 4 digits
const formatted = cleaned.match(/.{1,4}/g)?.join(" ") || cleaned;
// Limit to 16 digits (19 characters with spaces)
if (cleaned.length <= 16) {
setFormData((prev) => ({ ...prev, [name]: formatted }));
}
} else if (name === "cardExpiry") {
// Remove all non-digits
const cleaned = value.replace(/\D/g, "");
// Add slash after 2 digits
let formatted = cleaned;
if (cleaned.length >= 3) {
formatted = cleaned.slice(0, 2) + "/" + cleaned.slice(2, 4);
}
// Limit to 4 digits
if (cleaned.length <= 4) {
setFormData((prev) => ({ ...prev, [name]: formatted }));
}
} else if (name === "cardCVC") {
// Only allow digits and limit to 4
const cleaned = value.replace(/\D/g, "");
if (cleaned.length <= 4) {
setFormData((prev) => ({ ...prev, [name]: cleaned }));
}
} else {
setFormData((prev) => ({ ...prev, [name]: value }));
}
setFormData((prev) => ({ ...prev, [name]: value }));
};
if (loading) {
@@ -224,128 +184,29 @@ const RentItem: React.FC = () => {
<div className="row">
<div className="col-md-8">
<form onSubmit={handleSubmit}>
<div className="card mb-4">
<div className="card-body">
<h5 className="card-title">Payment</h5>
<div className="card mb-4">
<div className="card-body">
<h5 className="card-title">Payment</h5>
<div className="mb-3">
<label className="form-label">Payment Method *</label>
<div className="form-check">
<input
className="form-check-input"
type="radio"
name="paymentMethod"
id="creditCard"
value="creditCard"
checked
readOnly
/>
<label
className="form-check-label"
htmlFor="creditCard"
>
Credit/Debit Card
</label>
</div>
</div>
<StripePaymentForm
total={totalCost}
itemName={item.name}
onSuccess={handlePaymentSuccess}
onError={(error) => setError(error)}
disabled={
!manualSelection.startDate || !manualSelection.endDate
}
/>
<div className="row mb-3">
<div className="col-12">
<label htmlFor="cardNumber" className="form-label">
Card Number *
</label>
<input
type="text"
className="form-control"
id="cardNumber"
name="cardNumber"
value={formData.cardNumber}
onChange={handleChange}
placeholder="1234 5678 9012 3456"
required
/>
</div>
</div>
<div className="row mb-3">
<div className="col-md-6">
<label htmlFor="cardExpiry" className="form-label">
Expiry Date *
</label>
<input
type="text"
className="form-control"
id="cardExpiry"
name="cardExpiry"
value={formData.cardExpiry}
onChange={handleChange}
placeholder="MM/YY"
required
/>
</div>
<div className="col-md-6">
<label htmlFor="cardCVC" className="form-label">
CVC *
</label>
<input
type="text"
className="form-control"
id="cardCVC"
name="cardCVC"
value={formData.cardCVC}
onChange={handleChange}
placeholder="123"
required
/>
</div>
</div>
<div className="mb-3">
<label htmlFor="cardName" className="form-label">
Name on Card *
</label>
<input
type="text"
className="form-control"
id="cardName"
name="cardName"
value={formData.cardName}
onChange={handleChange}
placeholder=""
required
/>
</div>
<div className="alert alert-info small">
<i className="bi bi-info-circle"></i> Your payment
information is secure and encrypted. You will only be
charged after the owner accepts your rental request.
</div>
<div className="d-grid gap-2">
<button
type="submit"
className="btn btn-primary"
disabled={
!manualSelection.startDate || !manualSelection.endDate
}
>
{submitting
? "Processing..."
: `Confirm Rental - $${totalCost}`}
</button>
<button
type="button"
className="btn btn-secondary"
onClick={() => navigate(`/items/${id}`)}
>
Cancel
</button>
</div>
</div>
<button
type="button"
className="btn btn-secondary mt-2"
onClick={() => navigate(`/items/${id}`)}
>
Cancel
</button>
</div>
</form>
</div>
</div>
<div className="col-md-4">