Payment
Payment overview: two flows, why split
Payment
The odys application manages two distinct payment flows, each serving a different user persona and business need: professional subscriptions to the platform, and client payments for appointments with professionals. This architectural separation allows for tailored payment methods, reporting, and integration points, reflecting the different nature of these transactions.
Overview
At its core, the payment system in odys is designed to facilitate both recurring platform subscriptions for professionals and one-off or recurring payments from clients for services rendered. This dual approach is evident in the database schema and API routes. Professionals manage their subscription status, which dictates the features available to them, while also configuring how they receive payments from their clients.
The professionals table is central to managing the professional's relationship with the platform, including their chosen plan, and their Stripe subscription identifiers (stripeCustomerId, stripeSubscriptionId). It also stores their preferred method for receiving client payments, such as paymentType (e.g., PIX) and associated details like pixKeyType and pixKey.
Client payments, on the other hand, are primarily tracked within the appointments table, which includes a paymentStatus to reflect the state of the transaction and a stripePaymentIntentId if Stripe is used for that specific appointment. The system leverages Stripe for subscription management and offers direct PIX integration for client payments, catering to local payment preferences.
Professional Subscriptions
Professionals using odys subscribe to various plans to unlock different sets of features. These plans are meticulously defined in src/lib/stripe/plans.ts, where you'll find a master list of all features (including those marked as comingSoon) in PLAN_FEATURES and detailed definitions for each PLANS object: free, basic, pro, and premium. Each plan specifies its label, price, and the corresponding priceId from Stripe, along with any limits on clients or appointments per month. For instance, the free plan is limited to 10 clients and 20 appointments per month, while paid plans offer Infinity for these limits.
When a professional subscribes, their plan is recorded in the professionals table. This table also stores their stripeCustomerId and stripeSubscriptionId, which are crucial for managing their recurring billing through Stripe. The trialEndsAt column in the professionals table indicates if a professional is currently on a trial period, allowing the system to manage access to features accordingly.
The subscription process is initiated via the /api/stripe/checkout API route, which typically creates a Stripe Checkout Session. Subsequent updates and cancellations are handled by the /api/stripe/webhook route, which listens for specific Stripe events such as checkout.session.completed, customer.subscription.updated, and customer.subscription.deleted. These webhooks ensure that the professional's plan and subscription status in the professionals table are kept in sync with Stripe's records.
Client Appointment Payments
For client appointments, odys provides flexibility in how payments are handled. The appointments table tracks the paymentStatus for each booking, indicating whether the payment has been made, is pending, or has failed. While a stripePaymentIntentId column exists, suggesting the potential for Stripe-facilitated client payments, the system also supports direct payments to professionals, particularly through PIX.
Professionals can configure their preferred paymentType and paymentPercentage in their professionals table entry. If they opt for PIX, they provide their pixKeyType and pixKey. The src/lib/pix.ts utility is responsible for generating the necessary PIX static QR code payload.
The buildPixPayload function in src/lib/pix.ts takes the professional's key, name, city, and the amount (in cents) for the appointment. It processes the name and city inputs by normalizing them, removing diacritics, truncating name to a maximum of 25 characters and city to 15 characters, and converting them to uppercase to comply with EMV BR Code specifications. It constructs an EMV BR Code payload, which is a standardized format for PIX static QR codes. This function relies on two helper functions: field, which formats individual data fields with their ID, length, and value, and crc16, which calculates the Cyclic Redundancy Check for the payload to ensure data integrity. By generating this payload, the system allows clients to scan a QR code and pay the professional directly, bypassing platform-mediated payment processing for that specific transaction.
Design Decisions
The decision to implement two distinct payment flows—one for professional subscriptions and another for client appointments—stems from several key considerations:
- Separation of Concerns: Professional subscriptions are a direct business relationship between the professional and the
odysplatform, primarily for access to features. Client payments, conversely, are for services rendered by the professional to their client. Keeping these separate simplifies the financial logic and reporting for each type of transaction. - Flexibility for Professionals: By allowing professionals to specify their
paymentType(e.g., PIX) andpixKeyin theprofessionalstable,odysempowers them to choose how they receive payments for their services. This is particularly relevant in markets where local payment methods like PIX are prevalent and preferred for direct transactions. - Stripe for Platform Subscriptions: Stripe is a robust platform for managing recurring subscriptions, handling billing cycles, trials, and various payment methods. Integrating Stripe for professional plans (
free,basic,pro,premium) leverages its capabilities for a critical part of the platform's revenue model. Thesrc/lib/stripe/plans.tsfile centralizes all plan-related logic, making it easy to manage features and pricing. - Direct PIX Integration: The inclusion of
src/lib/pix.tsfor generating PIX QR codes directly addresses the need for instant, direct payments between clients and professionals. This avoids the overhead and fees associated with platform-mediated transactions for appointments, offering a more direct and potentially faster settlement for professionals. Thefieldandcrc16functions are low-level implementations to adhere to the Banco Central do Brasil's EMV BR Code specification, ensuring compatibility and reliability. - Data Model Clarity: The
professionalstable clearly delineates subscription-related fields (plan,stripeCustomerId,stripeSubscriptionId) from client payment preferences (paymentType,pixKey). Similarly, theappointmentstable focuses on the status of individual service payments (paymentStatus,stripePaymentIntentId). This clear separation in the data model enhances maintainability and understanding of the system's financial state.
Potential Improvements
- Dynamic PIX QR Code Generation and Reconciliation: The current
buildPixPayloadfunction insrc/lib/pix.tsgenerates static QR codes. While functional, static QR codes require professionals to manually confirm payments. Implementing dynamic PIX QR codes, where each QR code is unique to a transaction and linked to a specificappointmentId, would allow for automatic payment reconciliation. This would involve integrating with a PIX API that supports dynamic QR code generation and webhooks for payment status updates, automatically updating theappointments.paymentStatuscolumn. - Stripe Connect for Client Payments: Although
appointments.stripePaymentIntentIdexists, the system primarily supports direct PIX for client payments. To offer a more integrated payment experience and potentially facilitate platform-level financial reporting,odyscould integrate Stripe Connect. This would allow the platform to process client payments via Stripe, automatically deduct anypaymentPercentage(as defined inprofessionals.paymentPercentage), and then disburse the remaining funds to the professional's connected Stripe account. This would centralize payment processing and simplify financial management for both professionals and the platform. - Automated Plan Feature Enforcement: The
PLANSdefinitions insrc/lib/stripe/plans.tsspecifylimitsforclientsandappointmentsPerMonth. While these limits are defined, the current code doesn't explicitly show how these limits are enforced at the application level (e.g., preventing afreeplan user from adding more than 10 clients). Implementing robust checks before creating newclientsorappointmentswould ensure that professionals adhere to their subscribed plan's limitations, providing a consistent user experience and preventing abuse.
References
src/lib/pix.tssrc/lib/stripe/plans.ts/api/stripe/checkout/api/stripe/webhook