Payment System Overview
Understanding the two distinct payment flows in Odys: professional subscriptions and client appointment payments, and the rationale behind their design.
Payment System Overview
In Odys, the payment system is a critical component, facilitating both the business model of the platform itself and the core service offering of professionals to their clients. This document aims to provide Tiago with a deep understanding of the architectural choices made in designing these payment flows, explaining the why behind the separation and the specific technologies employed.
Overview
Odys handles two fundamentally different types of payment interactions, each with its own set of requirements and considerations. First, there's the platform subscription payment, where professionals pay Odys for access to its features and services. Second, there's the client appointment payment, where clients pay the professionals directly for their booked sessions. This clear distinction in purpose and payer/payee relationship has led to a deliberate separation in how these payments are processed and managed within the system.
The database schema, with its 10 tables, reflects these payment concerns primarily through the professionals and appointments tables. The professionals table holds crucial information about a professional's subscription status to Odys, including their plan, stripeCustomerId, and stripeSubscriptionId. It also stores their preferred method for receiving payments from clients, such as paymentType, pixKeyType, and pixKey. The appointments table, on the other hand, tracks the payment status for individual client bookings via its paymentStatus and stripePaymentIntentId columns.
The system leverages a total of 21 API routes, with dedicated endpoints for handling Stripe-related operations like /api/stripe/checkout for initiating subscriptions and /api/stripe/webhook for processing events from Stripe. While PIX payments for appointments are handled directly within the application logic, the src/lib/pix.ts utility is central to generating the necessary payment payloads.
Subscription Management for Professionals
For professionals using Odys, the platform offers a tiered subscription model, detailed in src/lib/stripe/plans.ts. This file defines the PLANS object, outlining free, basic, pro, and premium options, each with its own price and associated priceId (linked to environment variables like STRIPE_BASIC_PRICE_ID). The PLAN_FEATURES array in the same file provides a comprehensive list of features, indicating which are included in each plan.
When a professional subscribes, the process typically begins by interacting with the /api/stripe/checkout API route. This route is responsible for initiating a Stripe checkout session, guiding the professional through the payment process. Upon successful completion of a subscription, Stripe communicates back to Odys via the /api/stripe/webhook endpoint. This webhook is configured to listen for specific events, notably checkout.session.completed, customer.subscription.updated, and customer.subscription.deleted. These events are crucial for keeping the professionals table up-to-date, populating fields like stripeCustomerId and stripeSubscriptionId, and managing the professional's plan and trialEndsAt status. This integration ensures that a professional's access to Odys features is accurately reflected by their subscription status.
Appointment Payments from Clients
The second major payment flow involves clients paying professionals for their services. Odys supports two primary methods for these payments: PIX and Stripe. The choice of payment method is largely determined by the professional's configuration, stored in the professionals table under the paymentType column.
For professionals who prefer PIX, the system utilizes the src/lib/pix.ts utility. This file contains the buildPixPayload function, which is responsible for generating a PIX static QR code payload according to the Banco Central do Brasil's EMV BR Code specification. This function takes the professional's pixKey, name, city, and the amount of the appointment (in cents) to construct the necessary string. The professional's pixKeyType and pixKey are stored in their professionals record, allowing the system to dynamically generate PIX payment instructions for clients. The crc16 function within src/lib/pix.ts ensures the integrity of the generated payload.
For professionals who opt for Stripe for client payments, the system integrates with Stripe's payment intent capabilities. When an appointment is booked, a stripePaymentIntentId might be generated and stored in the appointments table. The paymentStatus column in the appointments table tracks the state of this payment, moving through stages like 'pending', 'paid', or 'failed'. This allows for a clear audit trail of client payments and their reconciliation with booked appointments.
Design Decisions
The decision to implement two distinct payment flows stems from the fundamental difference in the nature of the transactions.
- Separation of Concerns: Odys's subscription model is a B2B transaction (professional to Odys), while client appointment payments are B2C (client to professional). Mixing these concerns into a single payment gateway or data model would introduce unnecessary complexity and potential security risks. By separating them, each flow can be optimized for its specific requirements.
- Stripe for Subscriptions: Stripe was chosen for professional subscriptions due to its robust support for recurring billing, subscription management, and handling various payment methods. Its webhook system (
/api/stripe/webhook) provides a reliable mechanism for asynchronous updates, which is crucial for managing subscription lifecycles (e.g., renewals, cancellations, failed payments) without blocking user interactions. ThePLANSstructure insrc/lib/stripe/plans.tsis a clean way to define and manage the different subscription tiers and their associated features. - PIX for Appointment Payments: The inclusion of PIX for client payments is a strategic choice, likely driven by the prevalence and popularity of PIX in the Brazilian market. PIX offers instant payments, which can be highly beneficial for appointment-based services, reducing friction and improving cash flow for professionals. The
src/lib/pix.tsmodule encapsulates the specific logic for generating PIX QR code payloads, keeping this domain-specific implementation isolated and manageable. - Direct Data Association: Storing
stripeCustomerId,stripeSubscriptionId,pixKey, andpaymentTypedirectly within theprofessionalstable, andstripePaymentIntentIdandpaymentStatusin theappointmentstable, simplifies data retrieval and ensures a direct, clear relationship between the entities and their payment information. This avoids complex joins for common queries and keeps related data together.
Potential Improvements
- Abstract Payment Gateway for Client Payments: Currently, the logic for PIX is in
src/lib/pix.ts, and Stripe payment intent handling is likely spread across various API routes or services. If Odys plans to support more client payment methods (e.g., other credit card processors, local payment methods), it would be beneficial to introduce a payment gateway abstraction layer. This would centralize payment processing logic, making it easier to add new payment methods without modifying existing business logic. For example, aPaymentServicecould expose methods likecreatePaymentIntent(amount, method)that internally dispatch tobuildPixPayloador Stripe APIs. - Enhanced Payment Method Configuration for Professionals: The
professionals.paymentTypecolumn is a singletextfield, implying a professional can only choose one primary payment method for clients. In reality, professionals might want to accept multiple methods (e.g., both PIX and credit card). Consider evolving this to a more flexible structure, perhaps a separateprofessionalPaymentMethodstable or a JSONB column, allowing professionals to configure multiple active payment options, each with its own details (e.g., different PIX keys for different services, or specific Stripe account settings). - Implement Refund and Dispute Handling Workflows: The current schema and API routes primarily focus on successful payment capture. There's no explicit indication of how refunds are initiated or disputes are managed. For a complete payment system, especially for client payments, robust refund capabilities are essential. This would involve adding API routes (e.g.,
/api/payments/refund), updatingpaymentStatusinappointmentsto include refund states, and potentially integrating with Stripe's refund APIs or managing PIX refunds. - PIX Key Validation: The
buildPixPayloadfunction insrc/lib/pix.tstakes akeyas input but does not perform any validation on its format (e.g., if it's a valid CPF, CNPJ, email, or random key). Implementing validation at the point of professional input and within thebuildPixPayloadfunction would prevent the generation of invalid QR codes, improving the reliability of PIX payments.
References
src/lib/pix.tssrc/lib/stripe/plans.ts/api/stripe/checkout/api/stripe/webhookschema.ts(forprofessionalsandappointmentstables)
RLS Gap: No Row-Level Security Policies
Analysis of Odys' current lack of RLS policies and reliance on app-layer authorization, including trade-offs and improvement opportunities.
Stripe Subscription Flow Internals
A deep dive into how Odys manages professional subscriptions using Stripe, covering plan definitions, checkout session creation, and webhook interactions.