diff --git a/.env.example b/.env.example
index 988ada0..2db7528 100644
--- a/.env.example
+++ b/.env.example
@@ -1,5 +1,7 @@
VITE_API_BASE_URL=https://reservation-staging.winsoo.ir/api/
VITE_AUTH_BASE_URL=https://accounts-staging.gooyal.ir
+VITE_WALLET_BASE_URL=https://wallet-staging.gooyal.ir
+VITE_IPG_BASE_URL=https://ipg-staging.addwin.ir
VITE_AUTH_CLIENT_ID=
VITE_AUTH_CLIENT_SECRET=
VITE_APP_BASE_URL=
diff --git a/src/components/ReservationModal.tsx b/src/components/ReservationModal.tsx
index 9c1630b..6855011 100644
--- a/src/components/ReservationModal.tsx
+++ b/src/components/ReservationModal.tsx
@@ -1,7 +1,10 @@
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
import { Cafe } from "../services/campaignApi";
+import { chargePageUrl, createCharge } from "../services/ipgApi";
+import { fetchWalletBalances } from "../services/walletApi";
import { formatPersianNumber } from "../utils/formatNumber";
+import { getUserPhone } from "../utils/userPhone";
import InfoIcon from "./InfoIcon";
interface ReservationModalProps {
@@ -11,9 +14,9 @@ interface ReservationModalProps {
ticketPrice: number;
}
-const mockPhone = "۰۹۱۲۳۴۵۶۷۸۹";
-// mock wallet balance — will come from the user's wallet once auth is implemented
-const walletBalance = 10000;
+// TODO: there's no "get the logged-in customer's own wallet uuid" endpoint yet —
+// these are fixed test wallet ids given directly for now, replace once that exists
+const WALLET_UUIDS = ["e7c9d4d1-4d1f-43b2-96f7-4d4a168f480d", "af7d967f-30c0-409b-9066-2549f2da5e5e"];
function MinusIcon({ className = "" }: { className?: string }) {
return (
@@ -80,6 +83,9 @@ export default function ReservationModal({
}: ReservationModalProps) {
const [fullName, setFullName] = useState("");
const [count, setCount] = useState(1);
+ const [walletBalance, setWalletBalance] = useState(0);
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [error, setError] = useState("");
useEffect(() => {
if (!isOpen) return;
@@ -91,11 +97,34 @@ export default function ReservationModal({
};
}, [isOpen]);
+ useEffect(() => {
+ if (!isOpen) return;
+ fetchWalletBalances(WALLET_UUIDS)
+ .then((results) => setWalletBalance(results[0]?.balance ?? 0))
+ .catch(() => {});
+ }, [isOpen]);
+
if (!isOpen) return null;
const reservationAmount = ticketPrice * count;
const payableAmount = Math.max(0, reservationAmount - walletBalance);
+ const handlePay = async () => {
+ setError("");
+ setIsSubmitting(true);
+ try {
+ const appBaseUrl = (import.meta.env.VITE_APP_BASE_URL || window.location.origin).replace(/\/$/, "");
+ const { uuid } = await createCharge({
+ amount: payableAmount,
+ callback_url: `${appBaseUrl}/payment/success`,
+ });
+ window.location.href = chargePageUrl(uuid);
+ } catch {
+ setError("اتصال به درگاه پرداخت با خطا مواجه شد. لطفاً دوباره تلاش کنید.");
+ setIsSubmitting(false);
+ }
+ };
+
return createPortal(
<>
@@ -127,10 +156,9 @@ export default function ReservationModal({
- {/* readOnly mock for now — once OTP auth ships, this comes from the auth context */}