Architecture Deep Dive

A detailed walkthrough of the runtime, data model, request flows, Stripe integration, privacy model, and operational considerations for AdSwap.

Runtime & Hosting

  • Next.js (Pages Router) on Vercel — unified front-end and API routes.
  • API routes of note: /api/create-checkout-session, /api/stripe-webhook.
  • Supabase (Postgres, Auth) for users, profiles and future pass/accounting tables.
  • Stripe Checkout for funding/passes; Stripe webhooks for authoritative state.
  • Payment toggle: ENABLE_PAYMENTS and NEXT_PUBLIC_ENABLE_PAYMENTS switch between demo and real mode.

Data Model

Current tables and proposed extensions:

profiles (
  id uuid primary key references auth.users(id),
  is_premium boolean not null default false,
  stripe_customer_id text unique
)

-- Proposed for passes & accounting (not required for demo)
passes (
  id uuid primary key default gen_random_uuid(),
  user_id uuid not null references auth.users(id) on delete cascade,
  domain text not null,
  expires_at timestamptz not null,
  amount_cents int not null default 5,
  status text not null default 'active'
)

transactions (
  id uuid primary key default gen_random_uuid(),
  user_id uuid not null references auth.users(id) on delete cascade,
  stripe_event_id text,
  amount_cents int not null,
  kind text not null -- 'charge' | 'refund' | 'adjustment'
)

Domains should be stored hashed+salted; only prefixes for matching if needed.

Primary Flows

  1. Top‑up: User funds balance via Stripe Checkout; webhook writes transactions.
  2. Chip → Pass: Clicking chip requests a pass for domain. In demo, the API grants immediately; in prod it verifies balance/creates a charge.
  3. Decision: Server returns a signed decision (domain, expiry). Client hides ad slots; extension stores expiry locally.
  4. Payouts: Stripe events accumulate per publisher; periodic job aggregates into payout reports.
POST /api/create-checkout-session { userId, domain? }
  if ENABLE_PAYMENTS=false => grant pass directly
  else => stripe.checkout.sessions.create(...)

Webhook /api/stripe-webhook
  checkout.session.completed => record transaction, optionally grant initial pass
  invoice.payment_succeeded  => renew subscriptions/credits
  customer.subscription.*    => sync entitlement state

Payments & Webhooks

  • Stripe Checkout for one‑off funding or subscriptions (credits).
  • Webhooks: checkout.session.completed, invoice.payment_succeeded, customer.subscription.deleted.
  • All updates occur server‑side via the Supabase admin client using the service role key.
  • Demo mode short‑circuits Stripe and marks profiles/passes directly.

Privacy & Security

  • No URL storage; only domains (normalized), optionally hashed with per‑env salts.
  • No cross‑site tracking; decisions are domain‑scoped and time‑boxed.
  • Signed decisions (JWT or HMAC) to prevent tampering; extension validates before hiding slots.
  • Publishers receive only aggregated accruals; no reader identity is shared.
  • Rate limiting on API routes (IP + user) to prevent abuse; idempotent webhook handlers.

Operations: Environments & Observability

  • Environments via .env(.local) and Vercel envs; secrets never exposed client‑side.
  • Structured logs in API routes; webhook logs include event IDs to ensure exactly‑once behavior.
  • Health: simple /api/hello plus Stripe webhook signature checks.
  • Backfill scripts: SQL in supabase/ for schema, future migrations for passes/transactions.

Publisher Integration

A minimal script to render the chip and receive decisions.

<script>
  (async function(){
    const domain = location.hostname.replace(/^www./,'');
    // render chip (or use the extension if installed)
    // call your backend to request a decision; respect expiry
  })();
</script>

The extension supersedes the script when present to avoid duplication.

Extension Behavior

  • Renders a chip only on partner domains (or optional advanced mode).
  • On confirm: requests a signed pass; stores expiry; triggers a gentle reload to avoid flicker.
  • Observes the DOM for ad leaks; extends pass time or triggers small refunds when detected.
  • Popup shows active passes with countdown, top‑up shortcuts, and receipts.