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 (
+
+ );
+ }
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ 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);
+ }
+ }