Tiago Fortunato
ProjectsOdysPayment

PIX Payment Flow

Deep dive into Odys's PIX EMV BR Code generation, CRC16 calculation, and QR rendering for seamless payment processing.

PIX Payment Flow

Tiago, let's walk through the PIX payment flow in Odys. This part of the system is crucial for enabling professionals to receive payments directly from their clients using Brazil's instant payment system. It covers everything from generating the standardized PIX EMV BR Code payload to rendering a scannable QR code for clients, ensuring a smooth and reliable transaction experience. Understanding this flow is key to appreciating how Odys integrates with local payment methods and provides flexibility for its users.

Overview

The PIX payment functionality in Odys is primarily handled by two distinct but interconnected parts: a utility library responsible for generating the PIX payload and a React component for rendering the QR code.

At its core, the src/lib/pix.ts file contains the logic for constructing the EMV BR Code payload, which is the standardized string of data that a PIX QR code encodes. This payload includes essential information like the professional's PIX key, their name, and the transaction amount, all formatted according to the Banco Central do Brasil's specifications. A critical aspect of this payload generation is the inclusion of a CRC16 checksum, ensuring the data's integrity when scanned.

Once the payload is generated, the src/components/pix-qr.tsx component takes over. This client-side React component is responsible for visually presenting the PIX QR code to the user. It leverages the payload generation utility and a third-party library to render the QR code image, along with displaying the PIX key details and providing a convenient "copy" function for the raw PIX code. This separation of concerns—payload generation in a utility and rendering in a component—allows for modularity and reusability across Odys.

The data driving this flow originates from the professionals table, specifically the pixKeyType, pixKey, name, and paymentType columns. This ensures that each professional's unique PIX details are correctly used for generating payment requests. While the appointments table tracks paymentStatus, the actual PIX transaction details are handled externally by the PIX system itself, with Odys facilitating the initiation of the payment.

PIX Payload Generation: src/lib/pix.ts

The src/lib/pix.ts file is the engine behind creating the PIX EMV BR Code. It's designed to be a pure utility, focusing solely on the data formatting required by the Brazilian Central Bank's standards.

Structuring the EMV BR Code with field

The field function is a small but mighty helper that encapsulates the fundamental structure of an EMV BR Code data element. Each piece of information in a PIX payload follows a Tag-Length-Value (TLV) format. The field function takes an id (the Tag) and a value, then automatically calculates the len (Length) and pads it to two digits. This ensures that every data element, such as the PIX key or merchant name, is correctly formatted before being concatenated into the final payload. For example, field("00", "br.gov.bcb.pix") correctly formats the PIX indicator.

Ensuring Data Integrity with crc16

The crc16 function implements the CRC-16-CCITT algorithm, which is a standard checksum used to detect accidental changes to data. In the context of PIX, this checksum is appended to the end of the EMV BR Code payload. When a client scans the QR code, their banking app can recalculate the CRC16 and compare it to the one embedded in the payload. If they don't match, it indicates that the data might have been corrupted during transmission or display, preventing an incorrect transaction. This is a crucial step for ensuring the reliability of PIX payments.

Building the Complete Payload with buildPixPayload

The buildPixPayload function is where all the pieces come together. It accepts an object containing the key (the professional's PIX key), name (the professional's name), an optional city, and an optional amount.

Here's how it constructs the payload:

  1. Normalization: It first normalizes the name and city by removing diacritics (accents) and converting them to uppercase. This is a requirement for PIX EMV BR Code fields to ensure consistency and avoid issues with different character encodings. It also slices the strings to adhere to maximum length limits (25 characters for merchantName, 15 for merchantCity).
  2. Fixed Fields: It starts by adding mandatory fields like the Payload Format Indicator (00, "01"), the Merchant Account Information (26), the Merchant Category Code (52, "0000"), and the Transaction Currency (53, "986" for BRL).
  3. Conditional Amount: If an amount is provided and is greater than zero, it's included as field 54, formatted to two decimal places. This allows Odys to generate both static (amount-less) and dynamic (amount-specific) PIX QR codes, depending on the payment scenario.
  4. Merchant Details: The Country Code (58, "BR"), merchantName (59), and merchantCity (60) are then added.
  5. Transaction ID: A placeholder for the Transaction ID (62, field 05, "***") is included.
  6. CRC Placeholder: Finally, a 6304 tag is added as a placeholder for the CRC16 checksum.
  7. CRC Calculation: After assembling all the parts into a payload string, the crc16 function is called with this string, and the resulting checksum is appended to the end.

This meticulous construction ensures that the generated string is fully compliant with the PIX specification, making it scannable by any PIX-enabled banking application.

PIX QR Code Rendering: src/components/pix-qr.tsx

The src/components/pix-qr.tsx component is a client-side React component responsible for displaying the PIX QR code and related information to the user. It acts as the visual interface for the payment process.

Component Props and State

The PixQR component receives several props: pixKey, pixKeyType, professionalName, amount, and paymentType. These props provide all the necessary data to generate and display the PIX information. It also manages a local copied state to provide feedback to the user when the PIX code has been copied to their clipboard.

Dynamic Payload Generation

Inside the component, it determines whether to include the amount in the PIX payload based on the paymentType prop and whether an amount is actually provided. If paymentType is not "after" and an amount is present and positive, the amount is passed to buildPixPayload. This allows for flexibility, enabling professionals to request upfront payments with a specific amount or simply provide their PIX key for manual entry or variable amounts.

The buildPixPayload function from src/lib/pix.ts is then called with the relevant pixKey, professionalName, and the conditionally included amount to generate the final payload string.

Rendering the QR Code

The component uses the QRCodeSVG component from the qrcode.react library to render the QR code. The value prop of QRCodeSVG is set to the payload string, which is then visually translated into the scannable QR image. The size prop is set to 160 pixels, providing a clear and easily scannable image.

Displaying PIX Key Details and Copy Functionality

To enhance user experience, the component displays the pixKey itself, along with a user-friendly label for the pixKeyType (e.g., "Telefone", "CPF"). This mapping is handled by the PIX_TYPE_LABELS object.

The handleCopy asynchronous function allows users to easily copy the entire PIX payload string to their clipboard. This is particularly useful for clients who might prefer to paste the code directly into their banking app rather than scanning the QR code. A temporary "Copiado!" message provides immediate feedback after a successful copy operation.

Payment Context and Amount Display

The component also provides contextual messages based on the paymentType (e.g., "Realize o pagamento para garantir seu horário."). It conditionally displays the amount and professionalName as the favored recipient, offering clear instructions and confirmation to the client about the payment details. The amount is displayed as R$ {(amount! / 100).toFixed(0)}, which converts cents to reais.

Design decisions

The design of the PIX payment flow in Odys reflects several deliberate choices aimed at modularity, compliance, and user experience:

  • Separation of Concerns: The decision to place the core PIX payload generation logic in src/lib/pix.ts and the UI rendering in src/components/pix-qr.tsx is a classic example of separating concerns. This makes src/lib/pix.ts a reusable, backend-agnostic utility that could potentially be used in server-side contexts (e.g., for generating PIX codes in emails or notifications) without carrying any React dependencies. It also keeps the UI component focused solely on presentation.
  • Client-Side QR Rendering: Rendering the QR code on the client side using qrcode.react avoids the need for server-side image generation. This reduces server load, simplifies deployment, and provides immediate visual feedback to the user without an additional network roundtrip. The payload string is small enough that transmitting it and rendering locally is efficient.
  • Adherence to PIX EMV BR Code Specification: The meticulous construction of the payload in buildPixPayload, including the field helper and crc16 calculation, directly reflects the necessity to comply with the Banco Central do Brasil's technical standards. This ensures interoperability with all PIX-enabled banking applications. The normalization and slicing of merchant name and city are also direct responses to these specifications.
  • Conditional Amount Inclusion: The amount parameter in buildPixPayload and its conditional usage in PixQR allows Odys to support different payment models. Professionals can require upfront payments for a fixed amount, or simply provide their PIX key for clients to pay a variable amount later, aligning with the paymentType column in the professionals table.
  • User-Friendly Key Display and Copy: Providing the raw PIX key with a descriptive label (PIX_TYPE_LABELS) and a "copy to clipboard" button acknowledges that not all users will scan the QR code. Some might prefer to manually input or paste the key, enhancing accessibility and convenience.

Potential improvements

While the current PIX flow is functional and compliant, here are a few areas where it could be enhanced:

  1. Robust Input Validation in buildPixPayload: The buildPixPayload function in src/lib/pix.ts currently assumes valid inputs for key and name. For instance, a pixKey might not conform to the expected format for its pixKeyType (e.g., an invalid CPF or email format). Adding validation logic within buildPixPayload or at the point of data entry (e.g., when a professional sets their PIX key in professionals table) would make the system more resilient to malformed data, preventing the generation of invalid QR codes.
  2. Dynamic City Information: The city parameter in buildPixPayload defaults to "Brasil" if not provided. While functional, it might not always be accurate for the professional's actual location. Odys could enhance this by fetching the professional's city from a more authoritative source, perhaps by adding a city column to the professionals table or deriving it from other profile information. This would make the generated PIX code more precise and professional.
  3. Improved Amount Display Precision: In src/components/pix-qr.tsx, the amount is displayed using (amount! / 100).toFixed(0). While toFixed(2) is used for the payload, displaying toFixed(0) for the user might be misleading if the amount has cents (e.g., R$ 80.50 would show as R$ 80). Changing this to (amount! / 100).toFixed(2).replace('.', ',') would ensure correct currency formatting for Brazilian reais, providing a more accurate and professional user experience.
  4. Explicit paymentType Handling: The logic paymentType !== "after" in src/components/pix-qr.tsx to determine if an amount should be included is functional but could be made more explicit or configurable. If Odys introduces more payment types or complex payment rules, this conditional logic might become harder to maintain. Consider defining a clear enum or a more robust payment strategy pattern that explicitly states when an amount is required for PIX.

References

  • src/lib/pix.ts
  • src/components/pix-qr.tsx

On this page