Skip to content

feat: add stripe membership dues checkout - #39

Open
StevScripts wants to merge 1 commit into
mainfrom
feature/stripe-membership
Open

StevScripts wants to merge 1 commit into
mainfrom
feature/stripe-membership

Conversation

@StevScripts

@StevScripts StevScripts commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

Pull Request

Description

Lets students pay GPK dues on the site through Stripe hosted Checkout and collects T-shirt size and Discord username with the payment, so the Stripe Dashboard is the member list and the shirt order. Phase 1 only: no login, no database, no webhook.

  • New #membership section on the homepage (price, perks, "Pay dues") and a "Pay dues" button in the navbar.
  • POST /api/checkout creates a Checkout Session (mode: payment, customer_creation: "always", required T-shirt size dropdown XS–3XL, required Discord username text field, term metadata on the session and the PaymentIntent) and 303-redirects to Stripe.
  • /join/success thank-you page with the Discord invite.
  • lib/stripe.ts creates the client lazily, so npm run lint, npm run build and the Docker image all work with zero env vars (verified locally with the vars explicitly unset).
  • .env.example with STRIPE_SECRET_KEY, STRIPE_PRICE_ID, NEXT_PUBLIC_SITE_URL; .gitignore now keeps the example file; docker-compose.yml reads an optional .env.
  • Navbar highlights Membership on scroll; footer links to it. The navbar also gets a phone layout (GPK + Pay dues on one row, section links below) since five links plus a button no longer fit in one row at 375px.

Price and copy live in lib/membership.ts ($20 per academic year, term 2026-27, placeholder perks marked TODO(officers)); the Stripe Price is the source of truth for the amount.

Type of Change

  • feat: New feature
  • fix: Bug fix
  • chore: Maintenance (dependencies, config, etc.)
  • docs: Documentation only
  • style: Code style (formatting, no logic change)
  • refactor: Code refactor (no new feature or fix)

Changes Made

  • app/api/checkout/route.ts, lib/stripe.ts, lib/membership.ts (new)
  • components/MembershipSection.tsx, app/join/success/page.tsx (new)
  • app/page.tsx, components/Navbar.tsx, components/Footer.tsx
  • .env.example (new), .gitignore, docker-compose.yml, README.md, package.json (+stripe)

Screenshots (if applicable)

Desktop membership

gpk-membership-desktop

Mobile membership and navigation (390 × 844)

gpk-membership-mobile

Success page after sandbox checkout

gpk-success

Sandbox receipt

The receipt shows T-shirt size L and Discord username Stevin_dev.

gpk-sandbox-receipt

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have tested my changes locally
  • npm run lint passes without errors
  • npm run build completes successfully

Testing notes

  • Without env vars: Pay dues returns a 503 page ("Dues payments aren't set up yet") with a link back; /join/success renders; GET /api/checkout is 405.
  • With an invalid key: 502 page, only Stripe's masked error message in the server log.
  • Sandbox checkout tested: With the $20 USD one-time default price price_1UYAufEhGiRjGRLNtgyUPbNg, the local Pay dues button opened Stripe Checkout in sandbox mode. Submitted test@example.com, Test Student, size M, Discord username teststudent, and Stripe's 4242 test card; the browser returned to /join/success and displayed “You're in!”.
  • Receipt confirmed: A later sandbox payment (size L, Discord username Stevin_dev) produced the receipt email shown under Screenshots, with both custom fields printed on it, showing both custom fields on the receipt. Still unchecked in the Dashboard: the term metadata and the Customers entry.
  • Checkout copy: The sandbox product description says “Renews every year” even though its default price is one-time. Update the product description to avoid implying an automatic recurring charge.
  • Repeat locally: Copy .env.example to .env.local if needed, set a sandbox secret key and the one-time test Price ID, leave NEXT_PUBLIC_SITE_URL empty, then run npm run dev. Open the printed local URL, choose Pay dues, and use Stripe test card 4242 4242 4242 4242 with a future expiry, any three-digit CVC, and a ZIP code. Confirm the sandbox label before submitting; inspect the payment and customer in the sandbox Dashboard afterward.

Before merge (not code)

Whose Stripe account this is and UCF RSO/SGA rules on collecting dues into it; card fee is baked into the price.

Follow-ups (out of scope here)

  • Webhook + Drizzle members table (chore: install drizzle orm dependencies #32), rate limiting on /api/checkout, a shared pill-button component (the hero classes are now copied in four places), and making navbar links work from /join/success (they are hash links and only work on /).

Related Issues

Closes #38

🤖 Generated with Claude Code

Adds a Membership section and navbar "Pay dues" button that post to
/api/checkout, which creates a Stripe hosted Checkout Session (one-time
payment, customer_creation always, required T-shirt size dropdown and
Discord username fields, term metadata) and redirects to it. Paid members
land on /join/success. Stripe client is created lazily so lint, build and
the Docker image work with no env vars set.

Closes #38

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Payment Handling

1 participant