Payments & Financials - Domain Specification
First Introduced: V1.0 Status: Specification Complete Last Updated: 2025-10-24
Overview
Payments & Financials governs all financial transactions associated with Bookings — including payments from guests, platform fees, owner payouts, refunds, and reconciliation. This domain tracks money movement from guest payment through to owner/partner disbursement, providing immutable, auditable journal entries for compliance and reporting.
It sits downstream of Bookings and RevenueRules, transforming pricing intent into actual financial transactions. All financial records are immutable and reference the underlying Booking and RatePlan lineage, ensuring complete traceability for accounting, tax compliance, and dispute resolution.
Responsibilities
This domain IS responsible for:
- Recording guest payment transactions (inflows)
- Tracking payout obligations to owners, managers, and platform (outflows)
- Processing refunds (partial and full)
- Maintaining immutable transaction ledger for audit and reconciliation
- Integrating with payment processors (Stripe, Adyen)
- Managing payment account configurations per Org/Account
- Computing payout amounts based on RevenueRules
- Generating financial reports and balance summaries
This domain is NOT responsible for:
- Pricing calculation (→ Pricing domain)
- Revenue split rules definition (→ Pricing domain, RevenueRules)
- Booking lifecycle management (→ Bookings domain)
- Tax calculation (→ Future Tax Engine)
- Full accounting/ERP integration (→ V1.1+)
- Fraud detection (→ V1.2+)
Relationships
Depends On:
- Identity & Tenancy - Org/Account for financial ownership
- Bookings - Source of payment obligations
- Pricing - RevenueRules define payout splits
Depended On By:
Related Domains:
- Authorization - Access control for financial data
- Audit - Immutable audit trail for compliance
Core Concepts
Entity: Payment Account
Purpose: Represents the financial account configuration for an Org/Account to receive payments and process payouts (e.g., Stripe Connect account).
Key Attributes:
- id(UUID, primary key)
- org_id(UUID, foreign key → organizations.id)
- account_id(UUID, foreign key → accounts.id)
- provider(ENUM) - stripe | adyen | manual | internal
- provider_account_id(VARCHAR) - External account ID (e.g., Stripe Connect account ID)
- type(ENUM) - payment_gateway | payout_destination | both
- status(ENUM) - pending | active | suspended | closed
- capabilities(JSONB) - Provider-specific capabilities (card_payments, transfers, etc.)
- default_currency(CHAR(3)) - ISO 4217 code (USD, EUR, etc.)
- settings(JSONB) - Provider-specific configuration
- verified_at(TIMESTAMP) - KYC/AML verification timestamp
- created_at,- updated_at(timestamps)
Relationships:
- PaymentAccount → Org (*, many-to-one)
- PaymentAccount → Account (*, many-to-one)
- PaymentAccount → Payment (1:*) - Payments processed via this account
- PaymentAccount → Payout (1:*) - Payouts sent to this account
Lifecycle:
- Created: When Org/Account connects payment provider
- Verified: After KYC/AML checks complete
- Activated: When ready to process transactions
- Suspended: If verification fails or compliance issues
- Closed: When Account deactivated or provider disconnected
Business Rules:
- Each Account should have one active PaymentAccount per provider
- Cannot process payments until status='active'andverified_at IS NOT NULL
- Stripe Connect accounts auto-created on Org onboarding (V1.0)
Entity: Payment
Purpose: Represents an inbound payment from a guest to the platform for a Booking.
Key Attributes:
- id(UUID, primary key)
- org_id(UUID, foreign key → organizations.id)
- account_id(UUID, foreign key → accounts.id)
- booking_id(UUID, foreign key → bookings.id)
- payment_account_id(UUID, foreign key → payment_accounts.id)
- amount(BIGINT) - Amount in minor units (cents)
- currency(CHAR(3)) - ISO 4217 code
- status(ENUM) - pending | processing | succeeded | failed | refunded | partially_refunded
- method(ENUM) - card | bank_transfer | cash | channel_collect | manual
- provider(VARCHAR) - stripe | adyen | manual
- provider_transaction_id(VARCHAR) - External payment ID
- provider_reference(VARCHAR) - Idempotency key for provider
- metadata(JSONB) - Provider-specific details (card last4, etc.)
- attempted_at(TIMESTAMP)
- captured_at(TIMESTAMP)
- refunded_at(TIMESTAMP)
- failure_reason(TEXT)
- created_at,- updated_at(timestamps)
Relationships:
- Payment → Booking (*, many-to-one)
- Payment → PaymentAccount (*, many-to-one)
- Payment → Transaction (1:*) - Generates ledger entries
- Payment → Refund (1:*) - Can be partially or fully refunded
Lifecycle:
- Created: When checkout initiated or manual payment recorded
- Processing: Payment authorization in progress
- Succeeded: Payment captured successfully
- Failed: Payment declined or error occurred
- Refunded: Full refund processed
- Partially Refunded: One or more partial refunds applied
Business Rules:
- Payment amount must match or exceed Booking total (can include tips, extras)
- Cannot create Payment without valid payment_account_id
- provider_referencemust be unique per provider for idempotency
- Succeeded payments immutable (refunds via compensating Refund records)
- Status transitions: pending → processing → succeeded|failed
- Refund states: succeeded → partially_refunded → refunded
Entity: Payout
Purpose: Represents an outbound disbursement from platform to an owner, manager, or partner Account based on RevenueRules.
Key Attributes:
- id(UUID, primary key)
- org_id(UUID, foreign key → organizations.id)
- recipient_account_id(UUID, foreign key → accounts.id) - Who receives the payout
- booking_id(UUID, foreign key → bookings.id)
- payment_account_id(UUID, nullable, foreign key → payment_accounts.id)
- amount(BIGINT) - Amount in minor units (cents)
- currency(CHAR(3)) - ISO 4217 code
- status(ENUM) - pending | processing | paid | failed | canceled
- method(ENUM) - bank_transfer | check | manual | platform_balance | automatic
- provider(VARCHAR) - stripe | adyen | manual
- provider_payout_id(VARCHAR) - External payout ID
- scheduled_at(TIMESTAMP) - When payout should be processed
- paid_at(TIMESTAMP) - When payout completed
- failure_reason(TEXT)
- metadata(JSONB) - Notes, invoice references, etc.
- created_at,- updated_at(timestamps)
Relationships:
- Payout → Booking (*, many-to-one)
- Payout → Account (*, many-to-one) - Recipient
- Payout → PaymentAccount (*, many-to-one, optional)
- Payout → Transaction (1:*) - Generates ledger entries
Lifecycle:
- Created: When Booking reaches completedstatus or manual trigger
- Scheduled: Batched for next payout run (e.g., weekly, monthly)
- Processing: Transfer initiated with provider
- Paid: Successfully disbursed
- Failed: Transfer rejected or error occurred
- Canceled: Manually canceled before processing
Business Rules:
- Payout amount computed from Booking total and RevenueRules
- Cannot exceed available balance for recipient Account
- Payouts auto-created when Booking status → completed (V1.0)
- MVP: Manual payouts tracked but not auto-disbursed
- V1.0: Automated via Stripe Connect transfers
- Failed payouts retain pending status with error reason for retry
Entity: Transaction
Purpose: Immutable journal entry representing a debit or credit within a Ledger. Forms the foundation for double-entry bookkeeping and financial reconciliation.
Key Attributes:
- id(UUID, primary key)
- org_id(UUID, foreign key → organizations.id)
- ledger_id(UUID, foreign key → ledgers.id)
- account_id(UUID, nullable, foreign key → accounts.id) - Which Account this affects
- booking_id(UUID, nullable, foreign key → bookings.id)
- payment_id(UUID, nullable, foreign key → payments.id)
- payout_id(UUID, nullable, foreign key → payouts.id)
- refund_id(UUID, nullable, foreign key → refunds.id)
- direction(ENUM) - debit | credit
- amount(BIGINT) - Amount in minor units (cents)
- currency(CHAR(3)) - ISO 4217 code
- category(ENUM) - booking_payment | payout | refund | fee | commission | tax | adjustment
- description(TEXT) - Human-readable description
- metadata(JSONB) - Additional context
- occurred_at(TIMESTAMP) - When transaction occurred
- posted_at(TIMESTAMP) - When transaction posted to ledger
- created_at(TIMESTAMP)
Relationships:
- Transaction → Ledger (*, many-to-one)
- Transaction → Booking (*, many-to-one, optional)
- Transaction → Payment (*, many-to-one, optional)
- Transaction → Payout (*, many-to-one, optional)
- Transaction → Refund (*, many-to-one, optional)
Lifecycle:
- Created: When financial event occurs (Payment succeeded, Payout processed, etc.)
- Immutable: Never updated or deleted (compensating entries only)
- Posted: Timestamp when included in ledger balance
Business Rules:
- Transactions are append-only (never UPDATE or DELETE)
- Every Payment/Payout generates at least one Transaction
- Refunds create negative (compensating) Transactions
- Double-entry ready: Each transaction can have offsetting pair
- Ledger balance = SUM(credits) - SUM(debits)
- All amounts stored in minor units (cents) to avoid rounding errors
Entity: Refund
Purpose: Represents a full or partial reversal of a Payment, creating compensating Transaction entries.
Key Attributes:
- id(UUID, primary key)
- org_id(UUID, foreign key → organizations.id)
- account_id(UUID, foreign key → accounts.id)
- payment_id(UUID, foreign key → payments.id)
- booking_id(UUID, foreign key → bookings.id)
- amount(BIGINT) - Amount refunded in minor units (cents)
- currency(CHAR(3)) - ISO 4217 code
- status(ENUM) - pending | processing | succeeded | failed | canceled
- reason(ENUM) - customer_request | cancellation_policy | dispute | fraud | other
- reason_notes(TEXT) - Detailed explanation
- provider(VARCHAR) - stripe | adyen | manual
- provider_refund_id(VARCHAR) - External refund ID
- initiated_by(UUID, foreign key → users.id)
- approved_by(UUID, nullable, foreign key → users.id)
- processed_at(TIMESTAMP)
- metadata(JSONB)
- created_at,- updated_at(timestamps)
Relationships:
- Refund → Payment (*, many-to-one)
- Refund → Booking (*, many-to-one)
- Refund → Transaction (1:*) - Generates compensating entries
Lifecycle:
- Created: When refund requested (manual or policy-based)
- Pending: Awaiting approval (if required)
- Processing: Refund submitted to provider
- Succeeded: Funds returned to guest
- Failed: Refund rejected by provider
- Canceled: Request withdrawn before processing
Business Rules:
- Total refunds for a Payment cannot exceed original Payment amount
- Refund must reference valid succeeded Payment
- Partial refunds allowed (multiple Refund records per Payment)
- Refunds generate negative Transaction entries
- Cancellation policy determines eligible refund amount (stored in Booking metadata)
- V1.0: Automated refunds based on cancellation policy
- MVP: Manual refund recording only
Entity: Ledger
Purpose: Logical financial book for an Org, grouping all Transaction entries for reconciliation and reporting.
Key Attributes:
- id(UUID, primary key)
- org_id(UUID, foreign key → organizations.id, unique)
- base_currency(CHAR(3)) - Primary currency for reporting
- balance(BIGINT) - Current balance in minor units (computed)
- last_reconciled_at(TIMESTAMP)
- created_at,- updated_at(timestamps)
Relationships:
- Ledger → Org (1:1) - One ledger per Org
- Ledger → Transaction (1:*) - All financial entries
Lifecycle:
- Created: Automatically on Org creation
- Updated: Balance recomputed on each Transaction
- Reconciled: Periodic verification against provider statements
Business Rules:
- Exactly one Ledger per Org
- Balance = SUM(credit transactions) - SUM(debit transactions)
- Ledger immutable except for Transaction appends
- Reconciliation process validates ledger against external sources
Entity: Balance Summary (View)
Purpose: Computed view of pending, available, and paid balances per Account for reporting and payout dashboards.
Computed Attributes:
- org_id,- account_id
- pending_balance- Payouts scheduled but not paid
- available_balance- Payments received but not yet paid out
- paid_balance- Total historically paid out
- currency
- last_updated_at
Relationships:
- Derived from Transactions, Payments, Payouts
Business Rules:
- Refreshed on each financial event or via scheduled job
- Used for payout eligibility checks
- Displayed in owner/manager dashboards
Workflows
Workflow: Guest Payment Processing (V1.0)
- Guest submits payment during checkout
- Create Payment record with status='pending',provider_reference=<idempotency_key>
- Call Stripe API to create PaymentIntent
- Update Payment to status='processing'
- If payment succeeds:
- Update Payment to status='succeeded', setcaptured_at
- Create Transaction entries:
- Credit to Org Ledger (full amount)
- Credit to platform fee account (per RevenueRules)
 
- Update Booking status to confirmed
- Emit event: payment.succeeded
 
- Update Payment to 
- If payment fails:
- Update Payment to status='failed', setfailure_reason
- Emit event: payment.failed
- Notify user to retry
 
- Update Payment to 
Postconditions:
- Payment immutably recorded
- Booking confirmed if payment succeeded
- Transaction entries created for reconciliation
- Payout obligations computed (but not yet disbursed)
Workflow: Automated Payout Processing (V1.0)
- Booking reaches status='completed'(after checkout date)
- Compute payout amounts from RevenueRules (e.g., Owner 80%, Platform 20%)
- Create Payout record for owner Account with status='pending'
- Schedule payout for next batch run (weekly/monthly)
- On payout run:
- Filter Payouts with status='pending'andscheduled_at <= NOW()
- Update Payout to status='processing'
- Call Stripe Connect API to create Transfer
- If successful:
- Update Payout to status='paid', setpaid_at
- Create Transaction entries:
- Debit from Org Ledger
- Credit to recipient Account balance
 
- Emit event: payout.succeeded
 
- Update Payout to 
- If failed:
- Update Payout with failure_reason, keepstatus='pending'
- Retry on next run or alert finance team
 
- Update Payout with 
 
- Filter Payouts with 
Postconditions:
- Owner receives funds to connected bank account
- Ledger updated with payout entries
- Balance Summary reflects paid amount
- Audit trail complete from Booking → Payment → Payout → Transaction
Workflow: Refund Processing (V1.0)
- User requests refund or cancellation policy triggers automatic refund
- Validate refund eligibility:
- Check Booking cancellation policy (stored in booking.metadata.cancellation_policy)
- Compute eligible refund amount
- Verify Payment has sufficient unrefunded balance
 
- Check Booking cancellation policy (stored in 
- Create Refund record with status='pending',amount,reason
- If approval required: Wait for admin approval, then proceed
- Update Refund to status='processing'
- Call provider API (Stripe Refund) with original Payment ID
- If refund succeeds:
- Update Refund to status='succeeded', setprocessed_at
- Update Payment status to partially_refundedorrefunded
- Create compensating Transaction entries (negative amounts)
- Emit event: refund.succeeded
 
- Update Refund to 
- If refund fails:
- Update Refund to status='failed', setfailure_reason
- Emit event: refund.failed
 
- Update Refund to 
Postconditions:
- Guest receives funds back to original payment method
- Payment and Refund records immutably linked
- Ledger reflects reversed transaction
- Payout obligations reduced proportionally
Business Rules
- Immutability: Payment, Payout, Transaction, and Refund records are append-only (never updated except status transitions)
- Idempotency: All provider API calls use unique reference keys to prevent duplicate charges/payouts
- Amount Storage: All monetary amounts stored in minor units (cents) as BIGINT to avoid floating-point errors
- Currency Consistency: All transactions for a Booking must use the same currency
- Audit Trail: Every Payment/Payout/Refund links back to Booking and generates Transaction entries
- Payout Eligibility: Payouts only created after Booking status = completed (V1.0) or manual trigger
- Refund Limits: Total refunds for a Payment cannot exceed original Payment amount
- Ledger Balance: Org Ledger balance must always reconcile with sum of Transactions
- Access Control: Financial data restricted to admin and owner roles (enforced via Authorization domain)
- Provider Integration: Payment processing requires active PaymentAccount with verified status
- Double-Entry Ready: Each Transaction can be paired with offsetting entry for full accounting compliance (V1.1+)
Implementation Notes
MVP Scope (MVP.0 - OUT OF SCOPE)
Deferred to V1.0:
- All payment processing functionality
- Stripe integration
- Automated payouts
- Financial ledger
- Transaction tracking
MVP Workaround:
- Payments handled outside system (cash, direct transfer, channel collect)
- Booking confirmations manual
- Payout tracking via external spreadsheet
V1.0 Implementation (Full Payment Processing)
Included:
- Complete entity implementation (PaymentAccount, Payment, Payout, Transaction, Refund, Ledger)
- Stripe Connect integration for payment processing
- Automated payout calculation based on RevenueRules
- Manual or scheduled payout disbursement
- Full refund processing (manual approval)
- Immutable transaction ledger
- Balance summaries and financial reporting
- Idempotency for all payment operations
- PCI-DSS compliance (no raw card data stored)
Deferred to V1.1+:
- Automated refunds based on cancellation policy
- Multi-currency support with FX conversion
- Payment authorization holds (preauth + capture)
- Recurring billing and deposits
- Financial reconciliation engine
- Accounting system integration (QuickBooks, Xero)
- Full double-entry bookkeeping
Database Indexes
Critical for performance:
-- Payment lookups
CREATE INDEX idx_payments_booking_id ON payments(booking_id);
CREATE INDEX idx_payments_org_status ON payments(org_id, status);
CREATE UNIQUE INDEX idx_payments_provider_ref ON payments(provider, provider_reference);
-- Payout queries
CREATE INDEX idx_payouts_recipient_status ON payouts(recipient_account_id, status);
CREATE INDEX idx_payouts_scheduled ON payouts(scheduled_at) WHERE status = 'pending';
CREATE INDEX idx_payouts_booking_id ON payouts(booking_id);
-- Transaction ledger
CREATE INDEX idx_transactions_ledger_occurred ON transactions(ledger_id, occurred_at DESC);
CREATE INDEX idx_transactions_booking ON transactions(booking_id) WHERE booking_id IS NOT NULL;
CREATE INDEX idx_transactions_account ON transactions(account_id) WHERE account_id IS NOT NULL;
-- Refund tracking
CREATE INDEX idx_refunds_payment_id ON refunds(payment_id);
CREATE INDEX idx_refunds_booking_id ON refunds(booking_id);
Constraints
Enforce data integrity:
-- Payments
ALTER TABLE payments ADD CONSTRAINT payments_amount_positive CHECK (amount > 0);
ALTER TABLE payments ADD CONSTRAINT payments_currency_valid CHECK (currency ~ '^[A-Z]{3}$');
-- Payouts
ALTER TABLE payouts ADD CONSTRAINT payouts_amount_positive CHECK (amount > 0);
-- Transactions (immutable)
CREATE RULE transactions_no_update AS ON UPDATE TO transactions DO INSTEAD NOTHING;
CREATE RULE transactions_no_delete AS ON DELETE TO transactions DO INSTEAD NOTHING;
-- Refunds cannot exceed payment
ALTER TABLE refunds ADD CONSTRAINT refunds_amount_positive CHECK (amount > 0);
-- Ledger uniqueness
ALTER TABLE ledgers ADD CONSTRAINT ledgers_org_unique UNIQUE (org_id);
Future Enhancements
V1.1: Advanced Payment Features
- Payment authorization holds (preauth before checkout, capture after)
- Multi-currency support with real-time FX conversion
- Automated refunds triggered by cancellation policy rules
- Payment plans and deposit schedules
- Alternative payment methods (ACH, wire transfer, crypto)
V1.2: Reconciliation & Compliance
- Automated reconciliation engine (compare ledger vs. provider statements)
- Accounting system integration (QuickBooks, Xero, NetSuite)
- Full double-entry bookkeeping with chart of accounts
- Tax calculation engine integration
- Fraud detection and risk scoring
- KYC/AML verification workflows
V2.0: Enterprise Financial Management
- Split disbursements (multi-party payouts in single transaction)
- Escrow management for high-value bookings
- Invoice generation and management
- Financial reporting dashboards (P&L, balance sheet)
- Multi-entity consolidation (corporate parent + subsidiaries)
- Payment gateway failover and routing optimization
Physical Schema
See 001_initial_schema.sql for complete CREATE TABLE statements.
Summary:
- 7 tables: payment_accounts, payments, payouts, transactions, refunds, ledgers, balance_summaries (view)
- 15+ indexes for query performance
- 10+ constraints for data integrity
- Append-only rules for transaction immutability
- Stripe Connect integration fields
Related Documents
- MVP Mapping - Which MVP versions use this domain
- Bookings Domain - Source of payment obligations
- Pricing Domain - Revenue split rules
- Stripe Integration Guide - Payment processor setup
- Financial Reconciliation - Ledger validation procedures
- PCI-DSS Compliance - Security requirements
- MVP.0 Overview
- V1 Vision