The payment system follows a layered architecture with clear separation of concerns:
┌─────────────────────────────────────────────────────────────┐
│ API Routes (Next.js) │
│ Thin layer: auth, params extraction, delegate to service │
└───────────────────────┬─────────────────────────────────────┘
│
┌───────────────────────▼─────────────────────────────────────┐
│ Service Layer (@zooly/srv-stripe-payment) │
│ Business logic: payment completion, payouts, refunds │
└───────────────────────┬─────────────────────────────────────┘
│
┌───────────────────────▼─────────────────────────────────────┐
│ Access Layer (@zooly/app-db) │
│ Database operations: CRUD, transactions, queries │
└───────────────────────┬─────────────────────────────────────┘
│
┌───────────────────────▼─────────────────────────────────────┐
│ Database Schema (Drizzle ORM) │
│ Tables: stripe_payments, pay_out, payment_share_tracking │
└─────────────────────────────────────────────────────────────┘
Payment completion uses a dual-path approach. The client endpoint is the primary path; the webhook is the fallback. Both call the same idempotent completePayment() function with FOR UPDATE row locking.
accountId (not userId)zooly_acc, stripe_acc) for platform/fee trackingFOR UPDATE locks where needed for concurrencyPOST /complete) is the primary completion path — queries Stripe API to verify charge statuscharge.succeeded) is the fallback — covers cases where the client never calls the endpointcompletePayment() function with FOR UPDATE row lockingmarkStripePaymentCompleted returns null if already completed — only one caller succeedsgetProductByPayForIdzooly_acc: Platform fee tracking (auto-closed)stripe_acc: Stripe fee tracking (auto-closed)payeeAccountId NOT NULL across all tracking recordsadvanceAmountCent > 0payFor enum + payForId foreign key (e.g. IP term id, merch session id, offer id for OFFER)getProductByPayForId() returns a normalized PaymentProductpackages/
├── db/src/
│ ├── schema/
│ │ ├── paymentEnums.ts # All payment pgEnums
│ │ ├── stripePayments.ts # Master transaction record
│ │ ├── payOut.ts # Completed payouts
│ │ ├── paymentShareTracking.ts # Revenue distribution
│ │ ├── paymentSystemLogs.ts # Audit trail
│ │ ├── accountPayoutRoutes.ts # Payout method config
│ │ ├── ppu.ts # Proof of purchase
│ │ └── accountAgents.ts # Agent relationships
│ └── access/
│ ├── stripePayments.ts # Payment CRUD
│ ├── payOut.ts # Payout CRUD
│ ├── paymentShareTracking.ts # Tracking CRUD
│ └── ... # Other access functions
└── srv-stripe-payment/src/
├── stripe.ts # Stripe client & operations
├── getProductByPayForId.ts # Product resolution and pricing (inc. OFFER / escrow amounts)
├── insertShareTrackingForProduct.ts # Batch tracking + advance offset (shared completion + offer release)
├── completePayment.ts # Payment completion (escrow branch for OFFER)
├── releaseOfferEscrowForOffer.ts # OFFER: create tracking after delivery accepted
├── refund.ts # Refund processing
├── advancePayout.ts # Advance payout
├── payoutDaemon.ts # Daily cron daemon
└── doPayout.ts # Per-account payout logic
apps/zooly-app/app/api/payments/
├── create-intent/route.ts # Create payment intent
├── complete/route.ts # Payment completion (primary path, verifies with Stripe)
├── webhook/stripe/route.ts # Stripe webhook handler
├── process-payouts/route.ts # Payout cron endpoint
├── refund/route.ts # Refund endpoint
└── admin/
├── do-payout/route.ts # Manual payout
└── advance-payout/route.ts # Advance payout
| Variable | Purpose | Required |
|---|---|---|
STRIPE_SECRET_KEY | Stripe API authentication (sk_xxx) | Yes |
STRIPE_PUBLISHABLE_KEY | Stripe.js frontend (pk_xxx) | Yes |
STRIPE_WEBHOOK_SECRET | Webhook signature verification (whsec_xxx) | Yes |
CRON_SECRET | Bearer token for cron endpoint auth | Yes |
DATABASE_URL | PostgreSQL connection string | Yes |
On This Page
System ArchitectureHigh-Level Flow DiagramPayment Intent Creation FlowPayment Completion FlowRevenue Distribution FlowPayout Processing FlowRefund FlowKey Design Decisions1. Account-Centric Architecture2. Atomic Operations3. Dual-Path Completion with Idempotency4. Two-Phase Payout Commit5. Product as Source of Truth for Pricing6. System Accounts for Fees7. Advance Payout Offset8. Product-Agnostic DesignFile StructureEnvironment VariablesNext Steps