The payment system uses PostgreSQL with Drizzle ORM. All schemas follow project conventions:
nanoid() for primary keys (not auto-increment)snake_case column names in SQL, camelCase in TypeScripttimestamp with timezone for all date fieldsdeletedAt (except financial tables)account table (account-centric architecture)A payment consists of two linked records: a product record (product-specific) and a stripePayment record (generic Stripe payment details and status).
Creation Flow:
SUCCEEDEDBidirectional Relationship:
payForId/payFor fieldsstripePaymentId fieldAmount Handling:
getProductByPayForId() when neededQuerying Payments:
Offers (payFor: "OFFER"):
offers row; payForId = offers.idoffers.payment_stripe_id links to stripe_payments.idMaster transaction record tracking the full lifecycle from checkout initiation through charge completion and refunds. This table is Stripe-focused and does not store product-specific pricing breakdowns (platform fee, talent share) - those live in the product table. The payment system fetches pricing via getProductByPayForId().
File: packages/db/src/schema/stripePayments.ts
Key Fields:
id: nanoid primary keybuyerUserId: Buyer's user ID (required)buyerAccountId: Buyer's account ID (optional, only if logged in)sellerAccountId: Seller's account ID (required)payFor: Enum (MERCH, VOICE_OVER, LIKENESS, IMAGE, OFFER)payForId: Foreign key to product-specific table (for OFFER, the offers row id)amountCent: Payment amount in integer cents (negative for refunds) - fetched from product tablestripeFeeCent: Stripe transaction fees (set when charge succeeds, initially null)status: Lifecycle status enum (CREATED, PROCESSING, SUCCEEDED, FAILED, CANCELED, REFUNDED, PARTIALLY_REFUNDED)stripePaymentIntentId: Stripe PaymentIntent ID (pi_xxx)stripeChargeId: Stripe charge/refund ID (ch_xxx or re_xxx)stripeCustomerId: Stripe customer ID (cus_xxx)canceledByStripePaymentId: Self-referential FK to refund recordStatus Lifecycle:
CREATED → PROCESSING → SUCCEEDED
↓
FAILED / CANCELED
↓
REFUNDED
Refund Pattern:
stripe_payments record with negative amountCentstatus = REFUNDED and canceledByStripePaymentId points to refund recordCompleted payouts to talent accounts via Stripe Connect.
File: packages/db/src/schema/payOut.ts
Key Fields:
id: nanoid primary keypayeeAccountId: Account receiving the payoutamountCent: Total payout amount in centsadvanceAmountCent: Amount that was an advance (pre-earning payment)status: Payout status (PENDING, CHARGE, CANCELED, REFUND, EXTERNAL)stripeChargeId: Stripe Connect transfer ID (tr_xxx)isLiveMode: Live vs test mode flagStatus Lifecycle:
PENDING → CHARGE (on Stripe success)
→ CANCELED (on Stripe failure)
Note: Financial tables (pay_out, stripe_payments, payment_share_tracking) intentionally omit deletedAt - financial records must never be soft-deleted. Status transitions mark records as inactive while preserving audit trail.
The heart of the revenue distribution system. Tracks who gets paid what from each incoming payment.
File: packages/db/src/schema/paymentShareTracking.ts
Key Fields:
id: nanoid primary keypayeeAccountId: Account receiving this share (always set, including system accounts)stripePaymentId: Foreign key to stripe_paymentspayOutId: Foreign key to pay_out (null until paid out)amountCent: Share amount in centstype: Share type enum (TALENT, AGENT, AMBASSADOR, PLATFORM, STRIPE_FEE, HOST_PARTNER, PRODUCER, LAWYER)status: Tracking status (OPEN, CLOSED, CANCELED, REFUNDED)canceledByTrackingId: Self-referential FK to refund tracking recordStatus Lifecycle:
OPEN → CLOSED (on payout or advance offset)
→ CANCELED (on refund)
→ REFUNDED (on refund)
Auto-Closed Types:
STRIPE_FEE: Auto-closed (Stripe deducts directly)PLATFORM: Auto-closed (stays in Stripe account)Key Invariants:
SUM(amountCent) WHERE stripePaymentId = X = stripePayment.amountCentSUM(amountCent) WHERE payOutId = X = payOut.amountCentOne record per account storing payout method configuration.
File: packages/db/src/schema/accountPayoutRoutes.ts
Key Fields:
id: nanoid primary keyaccountId: Foreign key to account (unique, one-to-one)payoutMethod: Payout method enum (STRIPE_CONNECT, BANK_TRANSFER)stripeConnectAccountId: Stripe Connect account ID (acct_xxx)kycVerified: KYC verification status (must be true for payouts)One-to-one relationship with account table for payment-related settings.
File: packages/db/src/schema/accountPaymentSettings.ts
Key Fields:
id: nanoid primary keyaccountId: Foreign key to account (unique, one-to-one)minimumPayoutAmountCent: Payout threshold (default $100)stripeCustomerId: Buyer-side Stripe customer ID (cus_xxx)lastPayoutInspectionAt: Timestamp of last payout daemon checkemailOutstandingPayout: Flag to prevent duplicate "outstanding payout" emailsStructured audit trail for all payment operations.
File: packages/db/src/schema/paymentSystemLogs.ts
Key Fields:
id: nanoid primary keycomponent: Component enum (SHARE_CALCULATION, PAYMENT_COMPLETION, PAYOUT_PROCESSING, INTEGRITY_CHECK, PAYOUT_DAEMON, REFUND, WEBHOOK)message: Human-readable log messagedata: JSONB structured log datastripePaymentId: Optional FK to paymentpayOutId: Optional FK to payoutaccountId: Optional FK to accountisError: Error flagUnique PPU (Purchase Proof URL) codes generated per completed payment (1:1 with stripe_payments).
File: packages/db/src/schema/ppuCodes.ts
Key Fields:
id: nanoid primary keycode: Unique PPU codestripePaymentId: Foreign key to stripe_payments (unique)licenseType: License type (optional)licenseOwner: License owner (optional)Tracks which accounts are agents for which talent accounts, and their revenue share configuration.
File: packages/db/src/schema/accountAgents.ts
Key Fields:
id: nanoid primary keyagentAccountId: Agent's account IDtalentAccountId: Talent's account IDcanEdit: Can edit permission flaggetsShare: Gets revenue share flagshareAmountCent: Share amount (basis points if PERCENTAGE, cents if FIXED_AMOUNT)shareUnit: Share unit enum (PERCENTAGE, FIXED_AMOUNT)deletedAt: Soft delete timestampAll enums defined in packages/db/src/schema/paymentEnums.ts:
MERCH, VOICE_OVER, LIKENESS, IMAGE, OFFER
CREATED, PROCESSING, SUCCEEDED, FAILED, CANCELED, REFUNDED, PARTIALLY_REFUNDED
PENDING, CHARGE, CANCELED, REFUND, EXTERNAL
TALENT, AGENT, AMBASSADOR, PLATFORM, STRIPE_FEE, HOST_PARTNER, PRODUCER, LAWYER
OPEN, CLOSED, CANCELED, REFUNDED
STRIPE_CONNECT, BANK_TRANSFER
SHARE_CALCULATION, PAYMENT_COMPLETION, PAYOUT_PROCESSING, INTEGRITY_CHECK, PAYOUT_DAEMON, REFUND, WEBHOOK
PERCENTAGE, FIXED_AMOUNT
Two system accounts with fixed IDs are required:
zooly_acc: Platform account (receives PLATFORM share tracking records)stripe_acc: Stripe fee account (receives STRIPE_FEE share tracking records)These are real account records with ownerUserId = null (no associated user). They must be seeded in the DB before any payment is processed.
All tables have appropriate indexes for common query patterns:
stripe_payments: buyerAccountId, sellerAccountId, status, stripePaymentIntentId, stripeChargeIdpay_out: payeeAccountId, statuspayment_share_tracking: (payeeAccountId, status), stripePaymentId, payOutId, (type, status)payment_system_logs: component, isError, createdAtaccount_payment_settings: accountId, lastPayoutInspectionAtOn This Page
Schema OverviewInvariantCore Tablesstripe_paymentspay_outpayment_share_trackingaccount_payout_routesaccount_payment_settingspayment_system_logsppu_codesaccount_agentsEnumsstripePaymentPayForEnumstripePaymentStatusEnumpayOutStatusEnumshareTrackingTypeEnumshareTrackingStatusEnumpayoutMethodEnumpaymentLogComponentEnumagentShareUnitEnumSystem AccountsIndexesRelationshipsNext Steps