Skip to content

Wire up Supabase auth, Creem billing, and credits#1

Merged
ubergonmx merged 49 commits into
mainfrom
feat/supabase-creem-integration
Mar 31, 2026
Merged

Wire up Supabase auth, Creem billing, and credits#1
ubergonmx merged 49 commits into
mainfrom
feat/supabase-creem-integration

Conversation

@ubergonmx
Copy link
Copy Markdown
Owner

Problem / Intent

The starter had a complete UI shell but zero backend — Supabase clients were empty stubs, no auth or billing logic existed, no middleware protected routes, and no webhook handled Creem events. This PR wires everything up so the template is actually usable as a production SaaS starting point.

Approach

All business logic lives in server actions (no API routes except the single required webhook endpoint):

  • Auth — login/signup/OAuth server actions with Zod validation, middleware route guards, useUser hook, and an auth-aware header
  • Billing — full Creem API wrapper, handleWebhookEvent dispatcher covering the full subscription and checkout lifecycle, createCheckoutSession/openCustomerPortal actions, and checkout buttons wired to real product IDs on the pricing page
  • Creditsspend_credits Postgres RPC with a row-level lock to prevent race conditions, balance/purchase/spend/transaction-history server actions, and associated UI components
  • Dashboard — layout moved to a server component with auth check and real user data; sidebar and nav links point to real routes; /billing, /credits, and /settings sub-pages
  • Advanced billing — cancel (scheduled or immediate), resume, pause, and upgrade flows with an AlertDialog confirmation UI
  • Testing — vitest config, 12 passing unit tests covering webhook handlers, HMAC signature verification, and spend_credits edge cases; CI extended with test and build jobs

Implements all backend logic from PLAN.md across 7 phases:

Phase 1 – Foundation: populate Supabase browser/server/admin clients, full
Creem API wrapper (checkout, subscriptions, portal, webhook verification),
Next.js middleware with auth-redirect guards, migration 004 adds
creem_customer_id to profiles.

Phase 2 – Auth: server actions for login/signup/OAuth/logout with Zod
validation, OAuth callback route, useUser hook, auth-aware header.

Phase 3 – Billing core: Zod webhook schemas, handleWebhookEvent dispatcher
(checkout.completed, subscription lifecycle, refund.created), single
/api/webhooks/creem route, createCheckoutSession/openCustomerPortal actions,
CheckoutButton component, pricing section wired to real Creem product IDs.

Phase 4 – Dashboard: layout moved to server component with auth check and
real user passed to sidebar; dashboard page adds SubscriptionCard +
CreditsCard above existing demo; settings/billing/credits sub-pages;
AppSidebar accepts user prop with real nav links; NavUser wires logout.

Phase 5 – Credits: migration 005 adds spend_credits RPC with row-level
lock; credits types, actions (balance/purchase/spend/transactions),
CreditsBalanceCard, TransactionHistory, useCredits hook.

Phase 6 – Advanced billing: cancel (scheduled/immediate), resume, pause,
upgrade server actions; ManageSubscription component with AlertDialog
confirmation; useSubscription hook.

Phase 7 – Polish: Toaster added to root layout; ActionResult shared type;
vitest config + test setup; 12 passing tests covering webhook handlers,
webhook signature verification, and spend_credits edge cases; CI extended
with test and build jobs.
Expand README with quick start guide, table of contents, full setup
instructions, and webhook integration docs. Add testing strategy section
to ARCHITECTURE.md with colocated vs. cross-feature test conventions.
Unignore TODO.md so it's tracked.
@ubergonmx ubergonmx marked this pull request as ready for review March 29, 2026 15:18
Copilot AI review requested due to automatic review settings March 29, 2026 15:18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR turns the starter UI shell into a usable SaaS template by wiring Supabase auth/session handling, Creem billing (API + webhooks), and a credits ledger/RPC, plus adding unit tests and CI jobs.

Changes:

  • Add Supabase SSR/browser/admin clients, middleware route protection, and auth server actions (email + OAuth) with UI wiring.
  • Implement Creem client + webhook dispatcher to sync subscriptions and credit purchases/top-ups/refunds into Supabase.
  • Add credits feature (RPC + server actions + UI) and introduce Vitest tests + CI test/build jobs.

Reviewed changes

Copilot reviewed 50 out of 52 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
vitest.config.ts Adds Vitest config (jsdom, setup file, path alias).
supabase/migrations/004_profiles_creem_customer_id.sql Adds creem_customer_id to profiles and indexes it for lookups.
supabase/migrations/005_spend_credits_rpc.sql Introduces spend_credits RPC with row locking and ledger insert.
src/test/setup.ts Adds Testing Library jest-dom matchers for Vitest.
src/middleware.ts Adds Supabase SSR session refresh + route guards for auth/protected paths.
src/lib/supabase/server.ts Implements server-side Supabase client using Next cookies.
src/lib/supabase/client.ts Implements browser Supabase client.
src/lib/supabase/admin.ts Implements admin Supabase client using service role key.
src/lib/creem/client.ts Adds Creem API wrapper and webhook signature verification.
src/lib/creem/tests/client.test.ts Unit tests for webhook signature verification and Creem fetch error handling.
src/lib/action-result.ts Adds shared ActionResult type helper.
src/features/dashboard/components/nav-user.tsx Adds settings/billing/credits links and logout form; dynamic avatar initials.
src/features/dashboard/components/nav-main.tsx Converts nav items to real route links.
src/features/dashboard/components/app-sidebar.tsx Replaces placeholder nav data with real dashboard routes and accepts user props.
src/features/credits/types.ts Adds types for credit balance and credit transactions.
src/features/credits/hooks/use-credits.ts Adds client hook to fetch/refresh credit balance.
src/features/credits/components/transaction-history.tsx Adds UI for credit transaction history list.
src/features/credits/components/credits-balance-card.tsx Adds UI card for credit balance + “Buy Credits” action.
src/features/credits/actions/index.ts Adds server actions for balance, purchase checkout, spend RPC, and transaction history.
src/features/credits/actions/tests/spend-credits.test.ts Unit tests for spend credits action behavior.
src/features/billing/webhooks/index.ts Adds Creem webhook dispatcher and handlers for subscription + credits lifecycle.
src/features/billing/webhooks/tests/index.test.ts Unit tests for webhook routing/validation/logging.
src/features/billing/types.ts Adds webhook schemas/types and plan config constants.
src/features/billing/hooks/use-subscription.ts Adds client hook to fetch/refresh user subscription.
src/features/billing/components/subscription-card.tsx Adds dashboard billing summary UI (subscription + credits).
src/features/billing/components/pricing-section.tsx Wires pricing CTAs to signup/checkout.
src/features/billing/components/manage-subscription.tsx Adds UI/actions for cancel/resume/pause via Creem.
src/features/billing/components/checkout-button.tsx Adds client checkout button triggering server action.
src/features/billing/actions/index.ts Adds billing server actions for checkout, portal, cancel/resume/upgrade/pause, subscription query.
src/features/auth/types.ts Adds auth-related shared types.
src/features/auth/hooks/use-user.ts Adds client hook to track current Supabase user.
src/features/auth/components/signup-form.tsx Wires signup form to server action w/ validation errors.
src/features/auth/components/settings-profile-card.tsx Adds profile update form wired to server action.
src/features/auth/components/login-form.tsx Wires login form and OAuth buttons to server actions w/ validation errors.
src/features/auth/actions/profile.ts Adds server action for updating profile full name.
src/features/auth/actions/index.ts Adds login/signup/OAuth/logout server actions with Zod validation.
src/components/header.tsx Makes header auth-aware (Dashboard vs Login/Sign up).
src/app/layout.tsx Adds global Sonner toaster.
src/app/auth/callback/route.ts Adds OAuth callback route exchanging code for session.
src/app/api/webhooks/creem/route.ts Adds Creem webhook endpoint with signature verification + dispatcher.
src/app/(dashboard)/layout.tsx Adds server-side auth check and shared sidebar layout for dashboard routes.
src/app/(dashboard)/dashboard/page.tsx Adds server-side data fetching for subscription + credits on dashboard home.
src/app/(dashboard)/dashboard/settings/page.tsx Adds settings page showing profile card.
src/app/(dashboard)/dashboard/credits/page.tsx Adds credits page (balance + transaction history).
src/app/(dashboard)/dashboard/billing/page.tsx Adds billing page (subscription summary + management + plan CTAs).
package.json Adds Supabase + Vitest/testing deps and test scripts.
package-lock.json Locks added dependencies for Supabase SSR, Vitest, Testing Library, etc.
README.md Expands docs (quick start, setup flow, demo info).
ARCHITECTURE.md Documents testing strategy and folder layout additions.
.gitignore Stops ignoring TODO.md (commented out ignore entry).
.github/workflows/ci.yml Adds test and build jobs to CI.
.env.example Adds Business product ID env var.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread supabase/migrations/005_spend_credits_rpc.sql Outdated
Comment thread src/features/billing/webhooks/index.ts Outdated
Comment thread src/features/billing/webhooks/index.ts Outdated
Comment thread README.md Outdated
Comment thread src/features/credits/actions/credits.ts
Comment thread src/lib/creem/client.ts Outdated
Comment thread src/features/billing/webhooks/index.ts Outdated
Comment thread src/features/billing/components/pricing-section.tsx Outdated
Comment thread src/features/billing/components/pricing-section.tsx Outdated
Comment thread src/features/billing/actions/index.ts Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 29, 2026

Greptile Summary

This PR completes the backend wiring for the NextJS + Supabase + Creem starter: Supabase auth (login/signup/OAuth/middleware), the full Creem billing API wrapper, webhook event dispatch (subscriptions + one-time purchases + refunds), a credit system backed by safe Postgres RPCs, and dashboard/settings sub-pages — all tested with 12 unit tests and a CI pipeline. The overall implementation is solid and all five issues from the prior review round have been addressed. Two new issues were found that are worth resolving before shipping.

  • P1 — Wrong credit deduction on refund: handleRefundCreated passes refund.amount (a monetary value in cents from the Creem payload, e.g. 1900 for a $19 plan) directly to deduct_credits as if it were a credit count. A Pro subscriber received 5000 credits, but a refund would deduct only 1900 — and a credits-pack refund deducts ~499 instead of 500. The fix is to resolve the credit grant amount from the refunded product ID, not from the monetary amount.
  • P1 — Unhandled Creem API errors in checkout/portal flows: createCheckoutSession, openCustomerPortal (src/features/billing/actions/index.ts), and purchaseCredits (src/features/credits/actions/index.ts) call the Creem API without a try/catch. An API outage or 4xx response throws an unhandled exception and Next.js shows a generic error page. The other billing actions (cancel, resume, pause, upgrade) already have the correct try/catch → redirect pattern.
  • P2 — No idempotency guard against webhook replays: The add_credits atomic upsert (correctly introduced to fix the prior concurrent-update race) prevents two simultaneous credit grants, but does not prevent the same event being processed twice on sequential Creem retries. Storing and checking a processed-event-ID table is the standard mitigation.

Confidence Score: 4/5

Safe to merge after fixing the refund credit-unit mismatch and adding error handling to the three checkout/portal actions.

All five prior review findings are resolved. Two new P1 issues remain: the refund handler silently deducts the wrong number of credits (monetary cents vs. credit units), and three server actions lack error handling for Creem API failures.

src/features/billing/webhooks/index.ts (handleRefundCreated), src/features/billing/actions/index.ts (createCheckoutSession, openCustomerPortal), src/features/credits/actions/index.ts (purchaseCredits)

Important Files Changed

Filename Overview
src/features/billing/webhooks/index.ts Core webhook dispatcher; P1 bug in handleRefundCreated (monetary amount used as credit units), and no idempotency guard against duplicate event delivery.
src/features/billing/actions/index.ts Billing server actions; createCheckoutSession and openCustomerPortal lack try/catch around Creem API calls, letting errors surface as generic Next.js error pages.
src/lib/creem/client.ts Creem API wrapper with timing-safe HMAC verification, clean error propagation, and correct environment-based base URL switching.
supabase/migrations/003_credits.sql Adds spend_credits (FOR UPDATE row lock), add_credits (atomic upsert), and deduct_credits RPCs with correct security-definer / search_path hardening and REVOKE on public roles.
src/middleware.ts Supabase SSR middleware with correct getUser() (not getSession()) refresh, protected-path and auth-page redirect guards, and cookie passthrough.

Sequence Diagram

sequenceDiagram
    participant User
    participant Next as Next.js (Server Action)
    participant Creem as Creem API
    participant Webhook as /api/webhooks/creem
    participant DB as Supabase (Admin RPC)

    User->>Next: createCheckoutSession(productId)
    Next->>Creem: POST /v1/checkouts
    Creem-->>Next: { checkout_url }
    Next-->>User: redirect(checkout_url)

    User->>Creem: Complete payment
    Creem->>Webhook: POST checkout.completed
    Webhook->>Webhook: verifyWebhookSignature (HMAC)
    Webhook->>DB: profiles.update(creem_customer_id)
    alt Has subscription
        Webhook->>DB: subscriptions.upsert(creem_subscription_id)
    else One-time purchase
        Webhook->>DB: rpc add_credits(user_id, amount)
    end

    Creem->>Webhook: POST subscription.paid (renewal)
    Webhook->>DB: subscriptions.upsert
    Webhook->>DB: rpc add_credits(user_id, plan_credits)

    Creem->>Webhook: POST refund.created
    Webhook->>DB: profiles.select(creem_customer_id)
    Webhook->>DB: rpc deduct_credits(user_id, refund.amount ⚠️)

    User->>Next: spendCredits(amount, desc)
    Next->>DB: rpc spend_credits(user_id, amount) [FOR UPDATE lock]
    DB-->>Next: true / false
Loading

Reviews (2): Last reviewed commit: "fix(faqs): clarify deployment instructio..." | Re-trigger Greptile

Comment thread src/lib/creem/client.ts Outdated
Comment thread src/features/billing/actions/index.ts Outdated
Comment thread src/features/billing/webhooks/index.ts Outdated
Comment thread src/features/billing/actions/index.ts Outdated
Comment thread src/features/billing/webhooks/index.ts Outdated
…E_PUBLISHABLE_KEY

Update all references in Supabase clients, middleware, CI, and env example
to use the new publishable key env var name.
- Colocate loginSchema, signupSchema, profileSchema in auth/schema.ts
- Handle email confirmation flow (no session after signUp)
- Fix OAuth redirect URL to respect NEXT_PUBLIC_APP_URL and x-forwarded-* headers
- Sanitise open-redirect in /auth/callback (reject non-relative or protocol-relative paths)
- Show confirmation message in signup form
…dit RPCs

- Move Creem webhook schemas + createCheckoutSchema to billing/schema.ts
- billing/types.ts now contains TS-only types and PLANS
- Fix cancel/resume/pause to filter by creem_subscription_id not user_id
- Replace manual credit mutations with add_credits/deduct_credits RPCs
- Guard unknown product IDs in creditsForProductId (warn + return 0)
- Conditionally disable pricing buttons when product IDs are unconfigured
- Colocate spendCreditsSchema in credits/schema.ts
- Replace manual Number.isInteger guard with Zod safeParse
- Use z.int().min(1) per Zod v4 API
…tion

Replace string equality check with crypto.timingSafeEqual to prevent
timing-based signature oracle attacks on the webhook endpoint.
Middleware was accidentally deleted. Restored with NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY
to match the renamed env var. Required for Supabase session cookie refresh
and route protection on /dashboard.
Comment thread src/features/billing/webhooks/index.ts Outdated
Comment thread src/features/billing/actions/index.ts Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 63 out of 65 changed files in this pull request and generated 10 comments.

Comments suppressed due to low confidence (1)

README.md:168

  • These commands reference scripts that don’t exist in package.json (check and test:coverage). Either update the README to match the actual scripts (lint, typecheck, test, build) or add the missing scripts so the documented verification steps work.
cp .env.example .env.local
</details>



---

💡 <a href="/ubergonmx/nextjs-supabase-creem-starter/new/main?filename=.github/instructions/*.instructions.md" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Add Copilot custom instructions</a> for smarter, more guided reviews. <a href="https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot" class="Link--inTextBlock" target="_blank" rel="noopener noreferrer">Learn how to get started</a>.

Comment thread README.md Outdated
Comment thread src/features/auth/components/login-form.tsx
Comment thread src/app/(dashboard)/dashboard/billing/page.tsx Outdated
Comment thread src/app/(dashboard)/dashboard/billing/page.tsx Outdated
Comment thread src/features/billing/actions/index.ts Outdated
Comment thread src/features/billing/actions/index.ts Outdated
Comment thread src/features/billing/actions/index.ts Outdated
Comment thread src/features/credits/actions/index.ts Outdated
Comment thread src/features/billing/webhooks/index.ts Outdated
Comment thread src/features/billing/webhooks/index.ts Outdated
- Add emailRedirectTo pointing to /auth/callback so Supabase sends the
  PKCE code to the callback route instead of the landing page
- Redirect to /login?signup=pending after signup instead of returning
  an inline message; show a top-center Sonner toast on the login page
- Add useRef guard in SignupPendingToast to prevent double-fire under
  React Strict Mode in development
- Add useTransition to OAuth buttons so UI shows "Redirecting…" state
- Clean up auth layout title template (remove redundant "Auth" segment)
- Replace manual HMAC verification with @creem_io/nextjs Webhook()
  adapter; route is now 5 lines, all business logic lives in
  src/features/billing/webhooks/index.ts per feature-based layout
- Add webhook_events table (migration 004) as idempotency log and
  admin audit trail; duplicate webhookIds are skipped before any
  DB write
- Remove verifyWebhookSignature from creem client (handled by SDK)
- Export creditsForProductId, mapStatus, isDuplicate as named exports
  for testability; update tests to use typed event objects
- Add CheckoutSuccessToast for /dashboard?checkout=success feedback
- Add UpgradeButton component; billing page now uses UpgradeButton
  for existing subscribers vs CheckoutButton for new ones
- Guard purchaseCredits against missing product env var
- Add nativeButton={false} to Link-rendered Buttons in subscription card
- Replace manual IconInnerShadowTop + text markup in AppSidebar header
  with the shared <Logo> component; scale via [&>svg]:w-36 h-auto
- Remove stray sr-only spans from inside render={<Link/>} props in
  AppSidebar and NavSecondary; these conflicted with base-ui useRender
  children merging and caused logo/text to disappear
- Delete nav-documents.tsx (unused shadcn scaffold component)
- Add smooth scroll behaviour to root html element
- Add allowed ngrok dev origin to next.config.ts
Creem displays webhook secrets with a whsec_ prefix in the dashboard
(visual indicator, like sk_test_ for API keys) but the actual HMAC key
is the value after the prefix. The @creem_io/nextjs library uses the
secret as-is, causing every signature verification to fail.
Debug confirmed Creem signs with the entire whsec_ string as the key —
do not strip the prefix. Also move env var read inside the handler to
avoid module-level evaluation before env is loaded.
- Implement security headers to enhance application security, including X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Permissions-Policy.
- Update next.config.ts to include an async headers function that returns the defined security headers for all routes.
…hema functions

- Revoke EXECUTE permissions on trigger and utility functions from anon and authenticated roles to enhance security.
- Grant EXECUTE permissions on credit RPCs exclusively to the service_role, ensuring continued access for admin clients through future permission resets.
…ler directly

- Replace the existing POST function in the Creem webhook route with a direct export of the handleCreemWebhook function from the billing webhooks feature.
- Update import statements in billing actions for consistency and clarity, including normalization of string quotes and minor adjustments to error handling and redirection logic.
- Remove unnecessary options from .oxfmtrc.json, including semi and trailingComma.
- Simplify ignorePatterns in .oxlintrc.json by consolidating array elements into a single line for better readability.
- Introduce a new tsconfig.build.json file to exclude test files during production builds.
- Update next.config.ts to conditionally set the TypeScript configuration path based on the environment.
- Minor formatting adjustments in postcss.config.mjs for consistency.
…ion and resume actions

- Add revalidation of the billing dashboard path after canceling or resuming a user subscription to ensure the UI reflects the latest subscription status.
- Remove pause subscription functionality from the ManageSubscription component and update related UI elements for clarity.
- Change handleCancel and handleResume functions to remove async keyword, improving readability and consistency in the ManageSubscription component.
- Ensure that the subscription management logic remains intact while enhancing code clarity.
…d dashboard layouts

- Add ProgressProvider component to wrap children in both AuthLayout and DashboardLayout, enhancing user experience with loading indicators.
- Introduce @bprogress/next dependency for improved progress management.
- Remove unused action-result.ts file to clean up the codebase.
…features

- Update import paths for billing and credits actions to improve clarity and maintainability.
- Consolidate action imports in dashboard components to reflect new file structure.
- Introduce new action files for handling credits and subscriptions, enhancing modularity.
Enable experimental viewTransition flag in next.config.ts, wrap page
content in <ViewTransition> in the marketing layout, and pin the header
nav with a named view-transition to prevent it from cross-fading.
Tag dashboard/login/signup links with transitionTypes=['cross-layout']
and suppress the root animation via active-view-transition-type CSS to
avoid ugly transitions when leaving the marketing route group.
Suppress all root/header transitions by default so cross-layout
navigations are instant. Re-enable cross-fade only for marketing↔marketing
links via transitionTypes=['same-layout'] and active-view-transition-type CSS.
…ment

- Added a new Starter subscription plan with specific pricing and credits.
- Updated subscription management logic to handle the new plan, including UI adjustments in the billing page and subscription card components.
- Refactored user subscription retrieval to improve clarity and maintainability.
- Enhanced webhook handling for subscription events to accommodate the new plan.
- Introduced tests for the new plan and related functionalities to ensure reliability.
…proxy support

Use x-forwarded-host/proto in the callback route to build the correct
redirect origin, fixing localhost redirects when accessed via ngrok.
Also update getAuthRedirectBaseUrl to prefer incoming headers over
NEXT_PUBLIC_APP_URL in non-production environments.
…ent with getUser

- Replaced instances of createClient and its user retrieval logic with a new getUser function for improved clarity and maintainability.
- Updated various components and actions to utilize the new getUser method, enhancing the overall authentication flow.
- Removed unnecessary console logs in layout.tsx for cleaner code.
…ages

- Introduced a new UrlToast component to display error messages based on URL parameters, enhancing user feedback during authentication and billing processes.
- Updated LoginPage, BillingPage, and PricingPage to utilize UrlToast for displaying relevant error messages.
- Added error handling logic in auth and billing features to improve user experience during sign-up and subscription management.
- Refactored existing toast notifications to remove position settings for consistency.
… statuses; refactor proxy middleware for session management
…ing page layout; refactor subscription card and credits balance display
…d subscription details; refactor chart and section cards for improved credit activity display
…ption and credits components for better user experience
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 162 out of 164 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/proxy.ts
Comment thread next.config.ts Outdated
Comment thread src/features/credits/actions/credits.ts
Comment thread src/features/billing/actions/portal.ts
Comment thread src/lib/errors.ts Outdated
…rt for allowed dev origins and improve billing portal error messages
@ubergonmx ubergonmx merged commit eb3e2b6 into main Mar 31, 2026
5 checks passed
@ubergonmx ubergonmx deleted the feat/supabase-creem-integration branch March 31, 2026 23:11
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.

2 participants