The first version of the platform handled payments exactly as many reservation systems do.
When a customer booked a table or scheduled a private event, the application created a Stripe PaymentIntent, confirmed the payment, and considered the transaction complete.
For the initial business requirements, this architecture was perfectly reasonable.
Money moved from the customer to the platform.
The reservation was confirmed.
The payment provider maintained the transaction history.
As the platform evolved, however, payments stopped being simple one-time events.
Funds now needed to move through multiple business workflows before reaching their final destination.
That change required rethinking what a payment actually represented.
The Problem
New features gradually introduced requirements that extended well beyond charging a customer's card.
Guests needed to top up balances before making reservations.
Reservations required delayed settlement.
Managers needed to withdraw earnings.
Payments could enter dispute windows before funds became available.
Wallet top-ups, reservations, events, manager withdrawals, and dispute handling introduced financial workflows that extended well beyond a single payment request.
Each new workflow added more business rules around the payment itself.
Keeping those rules attached directly to Stripe quickly became difficult.
The application no longer needed a way to process payments.
It needed a way to model money.
The platform introduced an internal financial layer responsible for balances, ownership, and transaction history. Stripe remained responsible for moving funds between the platform and the outside world rather than acting as the source of truth for business state.
Recognizing the Real Domain
Initially it was natural to think about individual payment requests.
A reservation created a PaymentIntent.
An event created another.
Each feature implemented its own payment flow.
Over time a different pattern emerged.
Every feature was performing some variation of the same operation.
Move money.
Reserve money.
Release money.
Refund money.
Withdraw money.
The business domain was no longer centered around payments.
It was centered around financial state.
Once viewed from that perspective, building isolated payment flows for each feature became increasingly repetitive.
Instead of asking how a reservation should charge a customer, the more useful question became:
How should money move through the platform?
Introducing an Internal Wallet
The platform introduced wallets as the primary representation of available funds.
Rather than charging a customer's card every time a financial action occurred, money first entered an internal wallet.
Every subsequent operation referenced that balance instead of interacting directly with Stripe.
Conceptually, the architecture evolved from this.
To something considerably more flexible.
Once funds existed inside the platform, every feature could operate using the same financial primitives.
Reservations.
Private events.
Campaigns.
Subscriptions.
Withdrawals.
Each became another consumer of the same internal financial model.
Stripe Became Infrastructure
One important architectural change followed naturally.
Stripe stopped being part of business logic.
Instead, it became infrastructure.
Its responsibility narrowed considerably.
Incoming money entered the platform through Stripe.
Outgoing money left the platform through Stripe Connect.
Everything in between became the responsibility of the application itself.
This reduced coupling between the domain model and the payment provider.
Business rules no longer depended on the behavior of external APIs.
Replacing or extending payment providers in the future became significantly easier because the rest of the application no longer relied on provider-specific concepts.
Payment providers should move money, not define business state. Ownership, balances, and financial workflows belong to the application because they represent business concepts rather than payment-provider concepts.
A Single Source of Financial Truth
Introducing wallets also simplified how financial state was represented throughout the application.
Every balance change now followed the same lifecycle.
Funds entered the platform.
Funds became available.
Funds were reserved.
Funds were released.
Funds were withdrawn.
Rather than maintaining separate financial logic for reservations, campaigns, subscriptions, and payouts, each operation modified the same underlying financial model.
That consistency reduced duplicate implementations while making financial history significantly easier to reason about.
Every monetary event became part of the same timeline.
Not Every Payment Belonged in the Wallet
One notable exception was platform subscriptions.
Rather than routing recurring billing through the internal financial platform, subscriptions remained managed directly by Stripe Billing.
Recurring subscriptions introduce concerns such as renewals, payment recovery, proration, invoice generation, and dunning. These workflows are already mature capabilities of Stripe Billing and were independent of the platform's marketplace settlement process.
Since subscription revenue never participated in wallets, escrow, or manager payouts, routing it through the internal financial platform would have added unnecessary complexity without providing meaningful business value.
Instead, the platform intentionally used two complementary financial models.
This separation kept the internal financial platform focused on marketplace transactions while allowing Stripe Billing to handle the operational complexity of recurring subscriptions.
Preparing for Future Financial Workflows
One unexpected benefit of the wallet architecture was that new product features rarely required new payment infrastructure.
Instead, they reused existing financial building blocks.
Escrow no longer needed to understand Stripe.
Withdrawals simply transferred available wallet balances.
Campaign purchases followed the same accounting model as reservations.
Adding new financial workflows became a matter of composing existing primitives rather than introducing entirely new payment flows.
The architecture evolved by extending the domain rather than integrating additional payment APIs.
Reducing Feature Coupling
Without an internal financial layer, every feature requiring money becomes responsible for implementing payment logic.
Reservations.
Events.
Campaigns.
Each would eventually need to understand charging customers, validating balances, handling refunds, and recording financial history.
That duplication increases over time.
By introducing wallets as the common abstraction, those concerns moved into one dedicated financial subsystem.
Features simply requested financial operations.
The financial platform determined how those operations affected balances.
This separation allowed product features to evolve independently from payment infrastructure.
Lessons Learned
The initial implementation was not incorrect.
Direct Stripe payments were the simplest solution for the requirements that existed at the time.
The architecture changed because the business evolved.
As additional financial workflows appeared, the payment provider gradually became responsible for business decisions it was never intended to model.
Introducing an internal wallet shifted financial ownership back into the application.
Stripe remained responsible for securely transferring money.
The platform became responsible for modeling how that money behaved.
That distinction made subsequent features considerably easier to design while providing a consistent financial foundation for the entire system.
Future Improvements
The current implementation establishes a reusable financial platform shared across multiple product features.
Future enhancements could include:
- Multi-currency wallet support
- Promotional and credit balances
- Scheduled transfers between wallets
- Automated reconciliation reporting
- Additional payment providers behind the same financial abstraction
Because the application owns the financial model, these capabilities can be introduced without fundamentally changing how product features interact with money.
One of the most valuable lessons from this evolution was recognizing that payment providers process transactions, but applications own financial state.
Once that distinction became clear, wallets became more than a convenient balance store—they became the foundation that unified every monetary workflow across the platform.