more
This commit is contained in:
@@ -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),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
54
frontend/src/pages/subscribe/CheckoutReturn.jsx
Normal file
54
frontend/src/pages/subscribe/CheckoutReturn.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user