diff --git a/backend/internal/handlers/stripe.go b/backend/internal/handlers/stripe.go index 83cb61b..624428c 100644 --- a/backend/internal/handlers/stripe.go +++ b/backend/internal/handlers/stripe.go @@ -84,7 +84,7 @@ func (h *StripeHandler) CreateCheckoutSession(w http.ResponseWriter, r *http.Req Quantity: stripe.Int64(1), }, }, - SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=success&session_id={CHECKOUT_SESSION_ID}"), + SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe/return/{CHECKOUT_SESSION_ID}"), CancelURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"), ClientReferenceID: stripe.String(userID), CustomerEmail: stripe.String(user.Email), @@ -246,7 +246,7 @@ func (h *StripeHandler) CreateOnboardingCheckout(w http.ResponseWriter, r *http. Quantity: stripe.Int64(1), }, }, - SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=success&session_id={CHECKOUT_SESSION_ID}"), + SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe/return/{CHECKOUT_SESSION_ID}"), CancelURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"), CustomerEmail: stripe.String(body.Email), } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 47fb0d3..014f142 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,6 +4,7 @@ import Navbar from "./components/Navbar"; import Login from "./pages/login/Login"; import Signup from "./pages/Signup"; import Subscribe from "./pages/subscribe/Subscribe"; +import CheckoutReturn from "./pages/subscribe/CheckoutReturn"; import Addresses from "./pages/addresses/Addresses"; import Alerts from "./pages/alerts/Alerts"; import AlertHistory from "./pages/alertHistory/AlertHistory"; @@ -18,6 +19,7 @@ export default function App() { } /> } /> } /> + } /> } /> ); @@ -27,6 +29,7 @@ export default function App() { return ( } /> + } /> } /> } /> @@ -43,6 +46,7 @@ export default function App() { } /> } /> } /> + } /> } /> diff --git a/frontend/src/pages/subscribe/CheckoutReturn.jsx b/frontend/src/pages/subscribe/CheckoutReturn.jsx new file mode 100644 index 0000000..52b667c --- /dev/null +++ b/frontend/src/pages/subscribe/CheckoutReturn.jsx @@ -0,0 +1,54 @@ +import { useState, useRef } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { verifyCheckoutSession } from "../../api/stripe"; +import { useAuth } from "../../contexts/AuthContext"; +import "./Subscribe.css"; + +export default function CheckoutReturn() { + const { sessionId } = useParams(); + const navigate = useNavigate(); + const { refreshAccount } = useAuth(); + const [error, setError] = useState(null); + const started = useRef(false); + + if (sessionId && !started.current) { + started.current = true; + verifyCheckoutSession(sessionId) + .then(() => refreshAccount()) + .then(() => navigate("/addresses", { replace: true })) + .catch((err) => setError(err.message || "Payment verification failed")); + } + + if (!sessionId) { + return ( +
+
+

Koin Ping

+
No session ID found.
+

Back to Subscribe

+
+
+ ); + } + + if (error) { + return ( +
+
+

Koin Ping

+
{error}
+

Try again

+
+
+ ); + } + + return ( +
+
+

Koin Ping

+

Verifying your payment...

+
+
+ ); +} diff --git a/frontend/src/pages/subscribe/Subscribe.jsx b/frontend/src/pages/subscribe/Subscribe.jsx index 7269afa..c7d7dbd 100644 --- a/frontend/src/pages/subscribe/Subscribe.jsx +++ b/frontend/src/pages/subscribe/Subscribe.jsx @@ -1,9 +1,8 @@ -import { useState, useEffect } from "react"; +import { useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import { useAuth } from "../../contexts/AuthContext"; import { - createOnboardingCheckout, - verifyCheckoutSession, + createCheckoutSession, activateFreeTier, } from "../../api/stripe"; import Input from "../../components/Input"; @@ -13,6 +12,74 @@ import "./Subscribe.css"; const STEPS = ["Create Account", "Choose Plan"]; export default function Subscribe() { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const { currentUser, signup, refreshAccount } = useAuth(); + + const [step, setStep] = useState(currentUser ? 2 : 1); + const [data, setData] = useState({ + email: currentUser?.email || "", + password: "", + confirmPassword: "", + selectedTier: null, + }); + const [error, setError] = useState( + searchParams.get("payment") === "cancelled" + ? "Payment was cancelled. Please try again." + : "" + ); + const [loading, setLoading] = useState(false); + + function set(field, value) { + setData((prev) => ({ ...prev, [field]: value })); + } + + async function handleStep1() { + setError(""); + if (!data.email || !data.password) { + setError("Email and password are required"); + return; + } + if (data.password.length < 6) { + setError("Password must be at least 6 characters"); + return; + } + if (data.password !== data.confirmPassword) { + setError("Passwords do not match"); + return; + } + setLoading(true); + try { + await signup(data.email, data.password); + setStep(2); + } catch (err) { + setError(err.message || "Failed to create account"); + } finally { + setLoading(false); + } + } + + async function handleStep2() { + setError(""); + if (!data.selectedTier) { + setError("Please select a plan"); + return; + } + setLoading(true); + try { + if (data.selectedTier === "free") { + await activateFreeTier(); + await refreshAccount(); + navigate("/addresses", { replace: true }); + } else { + const { url } = await createCheckoutSession(data.selectedTier); + window.location.href = url; + } + } catch (err) { + setError(err.message || "Something went wrong"); + setLoading(false); + } + }