Tiago Fortunato
ProjectsOdysPayment

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:

  1. Separation of Concerns: Professional subscriptions are a direct business relationship between the professional and the odys platform, 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.
  2. Flexibility for Professionals: By allowing professionals to specify their paymentType (e.g., PIX) and pixKey in the professionals table, odys empowers 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.
  3. 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. The src/lib/stripe/plans.ts file centralizes all plan-related logic, making it easy to manage features and pricing.
  4. Direct PIX Integration: The inclusion of src/lib/pix.ts for 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. The field and crc16 functions are low-level implementations to adhere to the Banco Central do Brasil's EMV BR Code specification, ensuring compatibility and reliability.
  5. Data Model Clarity: The professionals table clearly delineates subscription-related fields (plan, stripeCustomerId, stripeSubscriptionId) from client payment preferences (paymentType, pixKey). Similarly, the appointments table 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

  1. Dynamic PIX QR Code Generation and Reconciliation: The current buildPixPayload function in src/lib/pix.ts generates 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 specific appointmentId, 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 the appointments.paymentStatus column.
  2. Stripe Connect for Client Payments: Although appointments.stripePaymentIntentId exists, the system primarily supports direct PIX for client payments. To offer a more integrated payment experience and potentially facilitate platform-level financial reporting, odys could integrate Stripe Connect. This would allow the platform to process client payments via Stripe, automatically deduct any paymentPercentage (as defined in professionals.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.
  3. Automated Plan Feature Enforcement: The PLANS definitions in src/lib/stripe/plans.ts specify limits for clients and appointmentsPerMonth. While these limits are defined, the current code doesn't explicitly show how these limits are enforced at the application level (e.g., preventing a free plan user from adding more than 10 clients). Implementing robust checks before creating new clients or appointments would ensure that professionals adhere to their subscribed plan's limitations, providing a consistent user experience and preventing abuse.

References

  • src/lib/pix.ts
  • src/lib/stripe/plans.ts
  • /api/stripe/checkout
  • /api/stripe/webhook

On this page