payment for rental from renter stripe integration
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user