This commit is contained in:
KS Jannette
2026-03-29 18:36:28 -04:00
parent 9b0fc50ddc
commit 2ca10ea340
4 changed files with 130 additions and 5 deletions

View File

@@ -84,7 +84,7 @@ func (h *StripeHandler) CreateCheckoutSession(w http.ResponseWriter, r *http.Req
Quantity: stripe.Int64(1), 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"), CancelURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"),
ClientReferenceID: stripe.String(userID), ClientReferenceID: stripe.String(userID),
CustomerEmail: stripe.String(user.Email), CustomerEmail: stripe.String(user.Email),
@@ -246,7 +246,7 @@ func (h *StripeHandler) CreateOnboardingCheckout(w http.ResponseWriter, r *http.
Quantity: stripe.Int64(1), 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"), CancelURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"),
CustomerEmail: stripe.String(body.Email), CustomerEmail: stripe.String(body.Email),
} }

View File

@@ -4,6 +4,7 @@ import Navbar from "./components/Navbar";
import Login from "./pages/login/Login"; import Login from "./pages/login/Login";
import Signup from "./pages/Signup"; import Signup from "./pages/Signup";
import Subscribe from "./pages/subscribe/Subscribe"; import Subscribe from "./pages/subscribe/Subscribe";
import CheckoutReturn from "./pages/subscribe/CheckoutReturn";
import Addresses from "./pages/addresses/Addresses"; import Addresses from "./pages/addresses/Addresses";
import Alerts from "./pages/alerts/Alerts"; import Alerts from "./pages/alerts/Alerts";
import AlertHistory from "./pages/alertHistory/AlertHistory"; import AlertHistory from "./pages/alertHistory/AlertHistory";
@@ -18,6 +19,7 @@ export default function App() {
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} /> <Route path="/signup" element={<Signup />} />
<Route path="/subscribe" element={<Subscribe />} /> <Route path="/subscribe" element={<Subscribe />} />
<Route path="/subscribe/return/:sessionId" element={<CheckoutReturn />} />
<Route path="*" element={<Navigate to="/login" />} /> <Route path="*" element={<Navigate to="/login" />} />
</Routes> </Routes>
); );
@@ -27,6 +29,7 @@ export default function App() {
return ( return (
<Routes> <Routes>
<Route path="/subscribe" element={<Subscribe />} /> <Route path="/subscribe" element={<Subscribe />} />
<Route path="/subscribe/return/:sessionId" element={<CheckoutReturn />} />
<Route path="/account" element={<><Navbar /><Account /></>} /> <Route path="/account" element={<><Navbar /><Account /></>} />
<Route path="*" element={<Navigate to="/subscribe" />} /> <Route path="*" element={<Navigate to="/subscribe" />} />
</Routes> </Routes>
@@ -43,6 +46,7 @@ export default function App() {
<Route path="/alertevents" element={<AlertHistory />} /> <Route path="/alertevents" element={<AlertHistory />} />
<Route path="/account" element={<Account />} /> <Route path="/account" element={<Account />} />
<Route path="/subscribe" element={<Subscribe />} /> <Route path="/subscribe" element={<Subscribe />} />
<Route path="/subscribe/return/:sessionId" element={<CheckoutReturn />} />
<Route path="*" element={<Navigate to="/addresses" />} /> <Route path="*" element={<Navigate to="/addresses" />} />
</Routes> </Routes>
</div> </div>

View File

@@ -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 (
<div className="subscribe">
<div className="subscribe__container">
<h1 className="subscribe__title">Koin Ping</h1>
<div className="alert alert--error">No session ID found.</div>
<p><a href="/subscribe">Back to Subscribe</a></p>
</div>
</div>
);
}
if (error) {
return (
<div className="subscribe">
<div className="subscribe__container">
<h1 className="subscribe__title">Koin Ping</h1>
<div className="alert alert--error">{error}</div>
<p><a href="/subscribe">Try again</a></p>
</div>
</div>
);
}
return (
<div className="subscribe">
<div className="subscribe__container">
<h1 className="subscribe__title">Koin Ping</h1>
<p>Verifying your payment...</p>
</div>
</div>
);
}

View File

@@ -1,9 +1,8 @@
import { useState, useEffect } from "react"; import { useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom"; import { useNavigate, useSearchParams } from "react-router-dom";
import { useAuth } from "../../contexts/AuthContext"; import { useAuth } from "../../contexts/AuthContext";
import { import {
createOnboardingCheckout, createCheckoutSession,
verifyCheckoutSession,
activateFreeTier, activateFreeTier,
} from "../../api/stripe"; } from "../../api/stripe";
import Input from "../../components/Input"; import Input from "../../components/Input";
@@ -13,6 +12,74 @@ import "./Subscribe.css";
const STEPS = ["Create Account", "Choose Plan"]; const STEPS = ["Create Account", "Choose Plan"];
export default function Subscribe() { 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);
}
}