Location: packages/db/src/schema/
paymentEnums.ts - All payment-related pgEnumsstripePayments.ts - Master transaction record tablepayOut.ts - Completed payouts tablepaymentShareTracking.ts - Revenue distribution tracking tablepaymentSystemLogs.ts - Audit trail tableaccountPayoutRoutes.ts - Payout method configuration tableaccountPaymentSettings.ts - Payment settings (one-to-one with account)ppu.ts - Proof of purchase codes tableaccountAgents.ts - Agent relationships tableLocation: packages/db/src/access/
stripePayments.ts - Payment CRUD operations
createStripePayment() - Create payment recordgetStripePaymentById() - Get payment by IDgetStripePaymentByStripeId() - Get by Stripe PaymentIntent IDmarkStripePaymentCompleted() - Atomic completion with FOR UPDATE lockupdateStripePaymentStatus() - Update statusupdateStripePaymentStripeId() - Set PaymentIntent IDlistStripePaymentsByAccount() - List payments for accountpayOut.ts - Payout CRUD operations
createPayOut() - Create payout recordgetPayOutById() - Get payout by IDlistPayoutsByAccount() - List payouts for accountgetPayoutsWithAdvanceBalance() - Get payouts with advance > 0reduceAdvanceAmount() - Atomic decrement advanceincrementAdvanceAmount() - Atomic increment advanceupdatePayOutStatus() - Update status and Stripe transfer IDcancelPayoutAndReopenTracking() - Compensation action for failed transferspaymentShareTracking.ts - Share tracking operations
createShareTracking() - Create single tracking recordcreateShareTrackingBatch() - Batch create tracking recordsgetOpenTrackingSumByAccount() - Sum OPEN tracking for accountgetOpenTrackingByAccount() - Get all OPEN tracking for accountcloseTrackingRecords() - Batch close tracking recordscancelTrackingRecord() - Cancel tracking (for refunds)listTrackingByStripePayment() - Get tracking for paymentlistTrackingByPayOut() - Get tracking for payoutaccountPayoutRoutes.ts - Payout route operations
getPayoutRouteByAccountId() - Get payout routeupsertPayoutRoute() - Create or update payout routeupdateKycStatus() - Update KYC verification statusaccountPaymentSettings.ts - Payment settings operations
getAccountPaymentSettings() - Get settings for accountupsertAccountPaymentSettings() - Create or update settingsupdateLastPayoutInspection() - Update inspection timestampupdateEmailOutstandingPayout() - Update email flagpaymentSystemLogs.ts - Logging operations
logPaymentEvent() - Create log entrygetRecentLogs() - Query logs with filtersppuCodes.ts - PPU code operations
createPpuCode() - Generate and create codegetPpuCodeByStripePayment() - Get code by paymentgetPpuCodeByCode() - Get code by code stringaccountAgents.ts - Agent relationship operations
getAgentsByTalentAccountId() - Get agents for talentgetAgentByIds() - Get specific agent-talent relationshipcreateAccountAgent() - Create agent relationshipupdateAccountAgent() - Update agent fieldsdeleteAccountAgent() - Soft delete agentgetShareEligibleAgents() - Get agents eligible for shareaccount.ts - Account operations (extended)
getAccountsNeedingPayoutInspection() - Find accounts for daemonupdateLastPayoutInspection() - Update inspection timestampLocation: packages/srv-stripe-payment/src/
stripe.ts - Stripe client and operations
getStripe() - Lazy-initialized Stripe client singletoncreateStripePaymentIntent() - Create PaymentIntentretrieveStripePaymentIntent() - Retrieve PaymentIntent with expanded charge (used by client completion)createOrGetStripeCustomer() - Get or create Stripe customercreateStripeTransfer() - Create Stripe Connect transfercreateStripeRefund() - Create Stripe refundverifyWebhookSignature() - Verify webhook signaturegetProductByPayForId.ts - Product resolution and pricing
getProductByPayForId() - Resolve product by payFor/payForId, returns PaymentProduct with full pricing breakdown (includes OFFER from offers row; status guards for checkout vs settlement)computePriceData() - Calculate 3-way split (Stripe fee, platform fee, talent gross)insertShareTrackingForProduct.ts - Share tracking + advance offset (used by default completion and by releaseOfferEscrowForOffer)
createPaymentIntent.ts - Payment intent creation
createPaymentIntent() - Orchestrates payment intent creation (fetches product, creates DB record, creates Stripe PaymentIntent)completePayment.ts - Payment completion engine
completePayment() - Atomic payment completion: default path uses insertShareTrackingForProduct; OFFER uses applyOfferPaymentEscrow (no tracking until release)completePaymentFromClient() - Client-side wrapper that verifies with Stripe API before calling completePayment()releaseOfferEscrowForOffer.ts - After buyer accepts delivery: insertShareTrackingForProduct + mark offer completed (idempotent)
refund.ts - Refund processing
processRefund() - Full refund with reversal of all trackingadvancePayout.ts - Advance payout mechanism
processAdvancePayout() - Create advance payoutpayoutDaemon.ts - Daily payout cron daemon
payoutDaemon() - Main daemon loopdoPayout.ts - Per-account payout logic
doPayout() - Process payout for single accountpaymentLogger.ts - Structured logging helper
logPaymentEvent() - Convenience wrapper for loggingLocation: apps/zooly-app/app/api/payments/
create-intent/route.ts - Create payment intent endpointcomplete/route.ts - Payment completion endpoint (primary path, verifies with Stripe)history/route.ts - Payment history endpointpayout-status/route.ts - Payout status endpointpayout-route/route.ts - Payout route configuration endpointrefund/route.ts - Refund endpointprocess-payouts/route.ts - Payout cron endpointwebhook/stripe/route.ts - Stripe webhook handleradmin/do-payout/route.ts - Manual payout endpointadmin/advance-payout/route.ts - Advance payout endpointAccess functions that participate in multi-step transactions accept an optional tx parameter:
type DbOrTx = typeof db | Parameters<Parameters<typeof db.transaction>[0]>[0];
export async function createStripePayment(
data: NewStripePayment,
tx?: DbOrTx
): Promise<StripePayment> {
const conn = tx ?? db;
const [result] = await conn.insert(stripePaymentsTable).values(data).returning();
return result;
}
Service layer uses transactions:
return await db.transaction(async (tx) => {
const payment = await createStripePayment(data, tx);
const tracking = await createShareTrackingBatch(records, tx);
// ... more operations
});
Critical paths use database transactions with row-level locks:
// Atomic double-spend prevention
const payment = await tx
.select()
.from(stripePaymentsTable)
.where(and(
eq(stripePaymentsTable.id, stripePaymentId),
eq(stripePaymentsTable.status, "CREATED")
))
.for("update") // Row-level lock
.limit(1);
Webhook handlers are idempotent:
const payment = await markStripePaymentCompleted(stripePaymentId);
if (!payment) {
// Already completed - return existing result
const existingCode = await getPpuCodeByStripePayment(stripePaymentId);
return { success: true, alreadyCompleted: true, ppuCode: existingCode };
}
Payout processing uses two-phase commit:
// Phase 1: DB
const payOut = await createPayOut({ status: "PENDING", ... });
await closeTrackingRecords(trackingIds, payOut.id);
// Phase 2: Stripe
const transfer = await createStripeTransfer({ ... });
// Phase 3: Confirm
if (transfer) {
await updatePayOutStatus(payOut.id, "CHARGE", transfer.id);
} else {
await cancelPayoutAndReopenTracking(payOut.id, trackingIds);
}
Location: packages/srv-stripe-payment/src/getProductByPayForId.ts and packages/util/src/calculateStripeFees.ts
const PLATFORM_FEE_STANDARD_CENT = 500; // $5.00 fixed AI creation cost
// Stripe fee constants (in @zooly/util)
const STRIPE_FEE_FIXED_CENT = 30; // $0.30
const STRIPE_FEE_PERCENTAGE_BPS = 290; // 2.9% (29 per 1000)
Note: These constants are currently hardcoded. TODO: Move to environment variables or configuration file.
Location: packages/types/src/types/
StripePayment.ts - Payment types and enumsPayOut.ts - Payout types and enumsPaymentShareTracking.ts - Share tracking types and enumsAccountAgent.ts - Agent types and enumsAccountPayoutRoute.ts - Payout route types and enumsPpuCode.ts - PPU code typesLocation: packages/db/migrations/
Migrations are generated using Drizzle:
cd packages/db
npm run db:generate # Generate migration files
npm run db:migrate # Apply migrations
Location: packages/db/src/seedSystemAccounts.ts
System accounts must be seeded before any payment processing:
npm run db:seed:system-accounts
Creates:
zooly_acc - Platform accountstripe_acc - Stripe fee accountownerUserId setzooly_acc, stripe_acc)STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_PUBLISHABLE_KEY=pk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
CRON_SECRET=your-cron-secret
DATABASE_URL=postgresql://...
# Install Stripe CLI
# Forward webhooks to local dev server
stripe listen --forward-to localhost:3004/api/payments/webhook/stripe
Add to apps/zooly-app/vercel.json:
{
"crons": [
{
"path": "/api/payments/process-payouts",
"schedule": "0 14 * * *"
}
]
}
On This Page
File StructureDatabase SchemaAccess LayerService LayerAPI RoutesKey Implementation PatternsTransaction PatternAtomic OperationsIdempotencyTwo-Phase CommitConstantsType DefinitionsDatabase MigrationsSystem Accounts SeedingTesting ConsiderationsUnit TestsIntegration TestsTest DataEnvironment SetupRequired Environment VariablesStripe Webhook Testing (Local)Vercel Cron ConfigurationNext Steps