From 239ea7db85cb31f9cfc398a1e387dfa0b420aa7c Mon Sep 17 00:00:00 2001 From: KS Jannette Date: Wed, 4 Mar 2026 21:38:36 -0500 Subject: [PATCH] addition Stripe payment integration work --- backend-go/README.md | 5 +- backend-go/internal/handlers/stripe.go | 4 +- frontend/src/App.jsx | 4 +- frontend/src/components/Input.css | 4 +- frontend/src/pages/Onboarding.jsx | 132 +++++++++---------------- frontend/src/pages/Signup.jsx | 2 +- 6 files changed, 54 insertions(+), 97 deletions(-) diff --git a/backend-go/README.md b/backend-go/README.md index 105153a..4af37f2 100644 --- a/backend-go/README.md +++ b/backend-go/README.md @@ -2,7 +2,6 @@ Start DB: brew services start postgresql@15 - From the backend-go directory, you have a few options: Option 1: Single command (both API + poller) @@ -10,9 +9,9 @@ cd /Users/kjannette/workspace/koin_ping_0.2.0/backend-gomake dev-all Option 2: Two separate terminals Terminal 1 (API server): -cd /Users/kjannette/workspace/koin_ping_0.2.0/backend-gogo run ./cmd/api +cd /Users/kjannette/workspace/koin_ping_0.2.0/backend-go go run ./cmd/api Terminal 2 (Poller): -cd /Users/kjannette/workspace/koin_ping_0.2.0/backend-gogo run ./cmd/poller +cd /Users/kjannette/workspace/koin_ping_0.2.0/backend-go go run ./cmd/poller make run — Builds and runs the API server. make dev — Runs the API server with auto-reload via air (falls back to go run if air isn't installed). diff --git a/backend-go/internal/handlers/stripe.go b/backend-go/internal/handlers/stripe.go index 555f129..666b204 100644 --- a/backend-go/internal/handlers/stripe.go +++ b/backend-go/internal/handlers/stripe.go @@ -46,8 +46,8 @@ func (h *StripeHandler) CreateCheckoutSession(w http.ResponseWriter, r *http.Req Quantity: stripe.Int64(1), }, }, - SuccessURL: stripe.String(h.cfg.FrontendURL + "/onboarding?payment=success&session_id={CHECKOUT_SESSION_ID}"), - CancelURL: stripe.String(h.cfg.FrontendURL + "/onboarding?payment=cancelled"), + SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=success&session_id={CHECKOUT_SESSION_ID}"), + CancelURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"), ClientReferenceID: stripe.String(userID), CustomerEmail: stripe.String(user.Email), } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c94c658..60dd37a 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -16,7 +16,7 @@ export default function App() { } /> } /> - } /> + } /> } /> ); @@ -30,7 +30,7 @@ export default function App() { } /> } /> } /> - } /> + } /> } /> diff --git a/frontend/src/components/Input.css b/frontend/src/components/Input.css index 9b1452b..a12be6f 100644 --- a/frontend/src/components/Input.css +++ b/frontend/src/components/Input.css @@ -1,6 +1,6 @@ .input__label { display: block; - margin-bottom: 0.4rem; + margin: 0.8rem 0rem 0.4rem 0rem; font-size: 0.9rem; } @@ -18,4 +18,4 @@ outline: none; border-color: var(--color-primary); box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.25); -} +} \ No newline at end of file diff --git a/frontend/src/pages/Onboarding.jsx b/frontend/src/pages/Onboarding.jsx index 786bcc6..022e2b0 100644 --- a/frontend/src/pages/Onboarding.jsx +++ b/frontend/src/pages/Onboarding.jsx @@ -1,7 +1,9 @@ /** - * Onboarding Wizard + * Subscribe / Onboarding Wizard * - * 6-step guided flow: Create Account -> Subscribe -> Add Wallet -> Alert Rules -> Notifications -> Done + * 5-step guided flow: Create Account -> Add Wallet -> Alert Rules -> Notifications -> Done + * After account creation, user is redirected to Stripe Checkout for payment. + * On successful payment, they return here at step 2 (Add Wallet). */ import { useState, useEffect } from "react"; @@ -20,7 +22,6 @@ import "./Onboarding.css"; const STEPS = [ "Create Account", - "Subscribe", "Add Wallet", "Alert Rules", "Notifications", @@ -80,11 +81,11 @@ export default function Onboarding() { const payment = searchParams.get("payment"); if (payment === "success") { setSearchParams({}, { replace: true }); - setStep(3); + setStep(2); } else if (payment === "cancelled") { setSearchParams({}, { replace: true }); - setStep(2); - setError("Payment was cancelled. Please subscribe to continue."); + setStep(1); + setError("Payment was cancelled. Please try again."); } }, [currentUser, searchParams, setSearchParams]); @@ -104,47 +105,33 @@ export default function Onboarding() { setError("Password must be at least 6 characters"); return; } - if (!currentUser) { - try { - setLoading(true); - await signup(data.email, data.password); - } catch (err) { - if (err.code === "auth/email-already-in-use") { - setError("Email already in use. Try logging in instead."); - } else if (err.code === "auth/invalid-email") { - setError("Invalid email address"); - } else if (err.code === "auth/weak-password") { - setError("Password is too weak"); - } else { - setError("Failed to create account: " + err.message); - } - setLoading(false); - return; - } finally { - setLoading(false); - } - } - setStep(2); - } - - async function handleStep2Subscribe() { - setError(""); try { setLoading(true); + if (!currentUser) { + await signup(data.email, data.password); + } const status = await getSubscriptionStatus(); if (status.subscription_status === "active" || status.subscription_status === "trialing") { - setStep(3); + setStep(2); return; } const { url } = await createCheckoutSession(); window.location.href = url; } catch (err) { - setError(err.message); + if (err.code === "auth/email-already-in-use") { + setError("Email already in use. Try logging in instead."); + } else if (err.code === "auth/invalid-email") { + setError("Invalid email address"); + } else if (err.code === "auth/weak-password") { + setError("Password is too weak"); + } else { + setError("Failed to create account: " + err.message); + } setLoading(false); } } - async function handleStep3() { + async function handleStep2() { setError(""); if (!data.walletAddress) { setError("Please enter a wallet address"); @@ -161,7 +148,7 @@ export default function Onboarding() { label: data.walletLabel || undefined, }); set("createdAddressId", created.id); - setStep(4); + setStep(3); } catch (err) { setError(err.message); } finally { @@ -169,7 +156,7 @@ export default function Onboarding() { } } - async function handleStep4() { + async function handleStep3() { setError(""); const rules = []; if (data.alertIncomingTx) rules.push({ type: "incoming_tx" }); @@ -190,7 +177,7 @@ export default function Onboarding() { } if (rules.length === 0) { - setStep(5); + setStep(4); return; } @@ -202,7 +189,7 @@ export default function Onboarding() { created.push(result); } set("alertsCreated", created); - setStep(5); + setStep(4); } catch (err) { setError(err.message); } finally { @@ -210,12 +197,12 @@ export default function Onboarding() { } } - async function handleStep5() { + async function handleStep4() { setError(""); const hasAny = data.discordWebhookUrl || data.slackWebhookUrl || data.notificationEmail; if (!hasAny) { - setStep(6); + setStep(5); return; } try { @@ -227,7 +214,7 @@ export default function Onboarding() { email: data.notificationEmail || undefined, }); set("notificationConfigured", true); - setStep(6); + setStep(5); } catch (err) { setError(err.message); } finally { @@ -331,33 +318,6 @@ export default function Onboarding() { } function Step2() { - return ( - <> -

Subscribe to Koin Ping

-

- A subscription is required to use Koin Ping. -

- -
-
- $1.99 - /month -
-
    -
  • Unlimited wallet monitoring
  • -
  • Real-time alert notifications
  • -
  • Discord, Slack, Telegram & email alerts
  • -
  • Email digest reports
  • -
-

- 6-month minimum commitment -

-
- - ); - } - - function Step3() { return ( <>

Add a wallet address

@@ -383,7 +343,7 @@ export default function Onboarding() { ); } - function Step4() { + function Step3() { return ( <>

Configure alert rules

@@ -443,7 +403,7 @@ export default function Onboarding() { ); } - function Step5() { + function Step4() { return ( <>

Set up notifications

@@ -508,7 +468,7 @@ export default function Onboarding() { ); } - function Step6() { + function Step5() { const alertCount = data.alertsCreated.length; const hasNotif = data.notificationConfigured; @@ -602,18 +562,17 @@ export default function Onboarding() { // ── Footer navigation ───────────────────────────────────────────────────── function Footer() { - if (step === 6) return null; + if (step === 5) return null; - const canSkip = step === 4 || step === 5; - const canBack = step > 1 && step !== 2; + const canSkip = step === 3 || step === 4; + const canBack = step > 2; async function handleNext() { setSkipWarning(""); if (step === 1) await handleStep1(); - else if (step === 2) await handleStep2Subscribe(); + else if (step === 2) await handleStep2(); else if (step === 3) await handleStep3(); else if (step === 4) await handleStep4(); - else if (step === 5) await handleStep5(); } function handleSkip() { @@ -628,9 +587,9 @@ export default function Onboarding() { setStep((s) => s - 1); } - const nextLabel = step === 2 - ? "Subscribe — $1.99/mo" - : step === 5 + const nextLabel = step === 1 + ? "Create Account & Subscribe" + : step === 4 ? "Finish" : "Next →"; @@ -673,12 +632,11 @@ export default function Onboarding() { // ── Render ──────────────────────────────────────────────────────────────── const stepContent = { - 1: , - 2: , - 3: , - 4: , - 5: , - 6: , + 1: Step1(), + 2: Step2(), + 3: Step3(), + 4: Step4(), + 5: Step5(), }; return ( @@ -686,7 +644,7 @@ export default function Onboarding() {

Koin Ping

- + {ProgressBar()} {error && (
{error}
@@ -698,7 +656,7 @@ export default function Onboarding() {
{stepContent[step]} -
+ {Footer()}
{step === 1 && ( diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index bbdd78e..1945c2c 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -1,5 +1,5 @@ import { Navigate } from "react-router-dom"; export default function Signup() { - return ; + return ; }