Skip to content

Repository files navigation

Duck Cards — Virtual Card Learning MVP

Local-only learning product that mimics a Duck-style virtual card experience: create merchant-locked / single-use / everywhere cards, pause & close them, simulate charges, and see merchant amount + $0.25 platform fee on every successful charge.

No real Stripe, bank links, OAuth, or webhooks. Everything runs against SQLite + in-process mocks.

Quick start

# From repo root
pnpm install

# Build shared package (types + fee math)
pnpm --filter @duck/shared build

# Create DB + seed demo data
cd apps/web
cp ../../.env.example .env   # if .env missing
pnpm db:migrate              # applies Prisma migrations (SQLite file)
pnpm db:seed                 # demo@example.com + 5 cards + transactions
cd ../..

# Dev server
pnpm dev

Open http://localhost:3000.

You are always “logged in” as demo@example.com (mock auth — no password).

One-liner after install

pnpm --filter @duck/shared build && pnpm --filter @duck/web exec prisma migrate dev --name init && pnpm db:seed && pnpm dev

Scripts

Command Description
pnpm dev Next.js app on :3000
pnpm test Vitest (fee math + authorize rules)
pnpm smoke Acceptance flow against local DB + dev server (dev must be running)
pnpm db:migrate Prisma migrate (interactive name if new)
pnpm db:seed Reseed demo user/cards/txs
pnpm db:reset Drop DB, remigrate, reseed
pnpm build Production build

Architecture

duck-cards/
├── apps/web                 # Next.js 15 App Router + Prisma + UI
│   ├── prisma/              # schema, migrations, seed
│   └── src/
│       ├── app/             # pages: /, /cards, /cards/new, /cards/[id], /pricing
│       ├── components/      # shadcn-style UI + card actions / forms
│       └── lib/
│           ├── auth.ts      # mock: always demo user
│           ├── db.ts        # Prisma client
│           ├── actions.ts   # server actions (CRUD + simulate)
│           ├── validations.ts  # zod
│           └── issuing/mock-issuing.ts
└── packages/shared          # fee math + shared types
    └── src/fees.ts          # PLATFORM_FEE_CENTS = 25

Data model

  • User — demo account
  • VirtualCard — label, type, status, limits, fake last4
  • Transaction — merchant amount, feeCents, status

Fee logic (@duck/shared)

PLATFORM_FEE_CENTS = 25
totalChargeCents(merchant) = merchant + 25

Fee is written only on settled (approved) transactions. Declines store feeCents = 0.

Mock issuing

lib/issuing/mock-issuing.ts simulates:

Method Behavior
createCard Random last4, status active, local DB only
pauseCard / unpauseCard / closeCard Status transitions
simulateAuthorization Pure rules → approve/decline + write Transaction

Decline rules (unit-tested):

  • Card paused or closed
  • Over spend limit
  • Wrong merchant on merchant_locked
  • Second charge on single_use (also auto-closes after first success)
  • Invalid amount

Card UI always shows a clearly fake PAN: 4242 4242 4242 {last4}.

What’s mocked

Concern Implementation
Auth Hardcoded demo user; no sessions/OAuth
Card issuing Local Prisma writes
Authorizations / webhooks simulateAuthorization form on card detail
Bank funding / ACH Not modeled
Stripe / Lithic / Clerk / Supabase Not used

Acceptance walkthrough

  1. Open dashboard → see 5 seed cards (Netflix, Spotify, iCloud, ChatGPT, YouTube).
  2. Create a new merchant-locked card.
  3. Simulate charge with matching merchant → settles, fee $0.25 line item.
  4. Pause the card → simulate again → declined.
  5. Unpause → charge works again (if under limit).
  6. Close (confirm dialog) → permanent; no more approvals.
  7. Check /pricing for fee disclosure vs mock Privacy-style monthly plans.

Zero external network calls required for core flows.

Environment

See .env.example:

DATABASE_URL="file:./dev.db"
DEMO_USER_EMAIL="demo@example.com"

SQLite file lives at apps/web/prisma/dev.db (path relative to Prisma schema).

Tests

pnpm test
  • packages/shared — fee math (totalChargeCents, formatCents, …)
  • apps/webevaluateAuthorization decline/approve matrix

Phase 2 (not in this repo)

Outlined only — do not implement yet:

  1. Stripe Issuing (test mode) — real card objects, ephemeral keys for PAN/CVC display under PCI constraints.
  2. Webhooksissuing_authorization.request, transaction updates; replace simulateAuthorization.
  3. Funding — Connect / treasury / bank link (still sandbox).
  4. Chrome extension — autofill checkout with card details; share types from @duck/shared.
  5. Real auth — Clerk/Auth.js; multi-user tenancy.
  6. Production — hosted Postgres, secrets, KYC, compliance.

Stack

  • TypeScript monorepo (pnpm)
  • Next.js 15 (App Router) + React 19 + Tailwind + Radix/shadcn-style components
  • Prisma + SQLite
  • Zod validation
  • Vitest
  • Sonner toasts

License

MIT — learning project, not a financial product.

About

Just testing out grok-build CLI here. Duck-style virtual card learning MVP — fully mocked issuing, local SQLite, no real payment APIs

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages