Drive the payment countdown from the booking's expires_at (15-min lock) instead of a fake local 10-min timer; when it hits zero, show an expiry notice and an 'choose time again' button back to slot selection. Show the booked time (Jalali) and doctor on the payment step, and replace the fabricated success amount with a confirmation/SMS note. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
166 lines
5.6 KiB
JavaScript
166 lines
5.6 KiB
JavaScript
import { useState, useEffect } from "react";
|
|
import moment from "moment-jalaali";
|
|
import ArrowLeftB from "@/components/icons/ArrowLeftB";
|
|
import { Button, MenuItem, Select, FormControl, InputLabel } from "@mui/material";
|
|
import ButtonFixed from "./ButtonFixed";
|
|
import { request } from "@/services/response";
|
|
|
|
function Paying({ setStep, appointmentId, expiresAt, doctor, selectedSlot, selectedDate }) {
|
|
const [selectedBank, setSelectedBank] = useState("");
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const computeLeft = () =>
|
|
expiresAt
|
|
? Math.max(0, Math.floor((expiresAt * 1000 - Date.now()) / 1000))
|
|
: 900;
|
|
const [timeLeft, setTimeLeft] = useState(computeLeft);
|
|
|
|
const banks = [
|
|
{ id: "mellat", title: "بانک ملت" },
|
|
{ id: "sep", title: "سامان (سپ)" }
|
|
];
|
|
|
|
useEffect(() => {
|
|
const timer = setInterval(() => {
|
|
setTimeLeft(computeLeft());
|
|
}, 1000);
|
|
|
|
return () => clearInterval(timer);
|
|
}, [expiresAt]);
|
|
|
|
const formatTime = (seconds) => {
|
|
const minutes = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
|
};
|
|
|
|
const handlePayment = async () => {
|
|
if (!selectedBank) {
|
|
alert("لطفا بانک خود را انتخاب کنید");
|
|
return;
|
|
}
|
|
|
|
if (!appointmentId) {
|
|
alert("شناسه نوبت یافت نشد");
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
const paymentPayload = {
|
|
appointment_uuid: appointmentId,
|
|
gateway: selectedBank,
|
|
frontend_address: `${window.location.origin}/payment/result`,
|
|
};
|
|
|
|
const res = await request.postAppointmentPayment(paymentPayload);
|
|
|
|
const redirectUrl = res?.data?.redirect_url;
|
|
if (redirectUrl) {
|
|
window.location.href = redirectUrl;
|
|
} else {
|
|
setStep((prev) => prev + 1);
|
|
}
|
|
} catch (error) {
|
|
console.error("Payment error:", error);
|
|
alert("خطا در پرداخت");
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="w-full lg:w-[59%]">
|
|
<div
|
|
className={`opacity-page bg-[#FFF] border border-solid border-[#EFEFEF]
|
|
rounded-[8px] p-[12px] sm:p-[16px] md:p-[20px] lg:p-[24px]`}
|
|
>
|
|
<p className="text-[#3B3B3B] text-[14px] sm:text-[16px] md:text-[18px] lg:text-[20px] font-bold">
|
|
اطلاعات پرداخت
|
|
</p>
|
|
|
|
{selectedSlot?.start && (
|
|
<div className="flex justify-between items-center mt-[16px] text-[14px] md:text-[16px]">
|
|
<span className="text-[#616161] font-medium">زمان نوبت:</span>
|
|
<span className="text-[#3B3B3B] font-bold">
|
|
{moment.unix(selectedSlot.start).locale("fa").format("jD jMMMM jYYYY - HH:mm")}
|
|
{doctor?.name ? ` با ${doctor.name}` : ""}
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
<div className="mt-[12px] sm:mt-[18px] md:mt-[24px] lg:mt-[30px]">
|
|
<div className="flex justify-between items-center mb-4">
|
|
<p className="text-[#616161] text-[14px] md:text-[16px] font-medium">
|
|
زمان باقیمانده:
|
|
</p>
|
|
<p className={`text-[14px] md:text-[16px] font-bold ${timeLeft < 60 ? 'text-red-500' : 'text-[#525252]'}`}>
|
|
{formatTime(timeLeft)}
|
|
</p>
|
|
</div>
|
|
|
|
<FormControl fullWidth className="!mb-4">
|
|
<InputLabel id="bank-select-label">انتخاب بانک</InputLabel>
|
|
<Select
|
|
labelId="bank-select-label"
|
|
value={selectedBank}
|
|
label="انتخاب بانک"
|
|
onChange={(e) => setSelectedBank(e.target.value)}
|
|
>
|
|
{banks.map((bank) => (
|
|
<MenuItem key={bank.id} value={bank.id}>
|
|
{bank.title}
|
|
</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
|
|
{/* <div className="flex justify-between items-center">
|
|
<p className="text-[#616161] text-[14px] md:text-[16px] font-medium">
|
|
مبلغ پیش پرداخت (بیعانه) :
|
|
</p>
|
|
<p className="text-[#525252] text-[14px] md:text-[16px] font-bold">
|
|
10،000 تومان
|
|
</p>
|
|
</div> */}
|
|
</div>
|
|
|
|
<span className="bg-[#D7D7D7] hidden md:block h-px w-full mt-[32px] mb-[25px]"></span>
|
|
|
|
{timeLeft === 0 && (
|
|
<p className="text-red-500 text-[14px] font-medium mb-3">
|
|
مهلت پرداخت تمام شد؛ لطفاً دوباره زمان نوبت را انتخاب کنید.
|
|
</p>
|
|
)}
|
|
|
|
<ButtonFixed>
|
|
<div className="flex items-center justify-end gap-2">
|
|
{timeLeft === 0 ? (
|
|
<Button
|
|
className="!gap-[4px] !mr-auto !flex !px-[12px] !p-[7px] md:!py-[9px] !min-w-[157px]"
|
|
onClick={() => setStep(0)}
|
|
variant="contained"
|
|
>
|
|
<p className="text-[#EFEFEF] text-[16px] font-medium">انتخاب مجدد زمان</p>
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
className="!gap-[4px] !mr-auto !flex !px-[12px] !p-[7px] md:!py-[9px] !min-w-[157px]"
|
|
onClick={handlePayment}
|
|
variant="contained"
|
|
disabled={loading || !selectedBank}
|
|
>
|
|
<p className={`${loading && "opacity-0"} text-[#EFEFEF] text-[16px] font-medium`}>
|
|
پرداخت نهایی
|
|
</p>
|
|
<ArrowLeftB />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</ButtonFixed>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Paying;
|