|
| 1 | +--- |
| 2 | +description: |
| 3 | +globs: |
| 4 | +alwaysApply: true |
| 5 | +--- |
| 6 | +\# PyAzul 3D Secure (3DS) Implementation Guide |
| 7 | + |
| 8 | +This guide details the 3D Secure flow implementation within the `pyazul` library. |
| 9 | + |
| 10 | +## Core Components for 3DS |
| 11 | + |
| 12 | +1. **`SecureService` ([pyazul/pyazul/services/secure.py](mdc:pyazul/pyazul/pyazul/pyazul/services/secure.py))**: |
| 13 | + * Handles all logic specific to 3DS transactions, including: |
| 14 | + * Initiating 3DS sales, token sales, and holds (`process_sale`, `process_token_sale`, `process_hold`). |
| 15 | + * Processing 3DS method notifications (`process_3ds_method`). |
| 16 | + * Processing 3DS challenge responses (`process_challenge`). |
| 17 | + * Generating HTML forms for ACS redirection (`_create_challenge_form`). |
| 18 | + * Uses the shared `AzulAPI` client (passed during its initialization), ensuring `is_secure=True` is used for 3DS-specific authentication via `AzulAPI`. |
| 19 | + * Manages 3DS session state internally using dictionaries keyed by `secure_id` (for initial session data like `term_url` and `azul_order_id`) and `AzulOrderId` (for transaction processing states). |
| 20 | + |
| 21 | +2. **`PyAzul` Facade ([pyazul/pyazul/index.py](mdc:pyazul/pyazul/pyazul/pyazul/index.py))**: |
| 22 | + * Exposes user-friendly methods for 3DS operations: |
| 23 | + * `secure_sale`, `secure_token_sale`, `secure_hold` |
| 24 | + * `process_3ds_method` (Note: the facade method is `process_3ds_method`, not `secure_3ds_method` as previously might have been implied by some docs/tests) |
| 25 | + * `process_challenge` |
| 26 | + * `create_challenge_form` (convenience wrapper around `SecureService._create_challenge_form`). |
| 27 | + * `get_secure_session_info(secure_id)`: retrieves session data stored by `SecureService`. |
| 28 | + * These methods delegate to the `SecureService` instance, which is initialized by `PyAzul`. |
| 29 | + |
| 30 | +## Flow Overview |
| 31 | + |
| 32 | +1. **Initiation**: |
| 33 | + * User calls `azul.secure_sale()` (or `secure_token_sale`, `secure_hold`) with payment details, `cardHolderInfo`, and `threeDSAuth` (which includes `TermUrl` and `MethodNotificationUrl`). |
| 34 | + * `SecureService` generates a unique `secure_id` (UUID). |
| 35 | + * `TermUrl` and `MethodNotificationUrl` provided by the user are internally appended with `?secure_id=<generated_id>` by `SecureService` before being sent to Azul. |
| 36 | + * `SecureService` makes an initial request to Azul. It then stores initial session data (including `azul_order_id` from the Azul response and the modified `term_url`) internally, associated with the generated `secure_id`. |
| 37 | + * The response from `azul.secure_sale()` may include HTML for immediate redirection (to the ACS for a challenge or to the 3DS Method URL). This response will also contain the `id` (which is the `secure_id`). |
| 38 | + |
| 39 | +2. **Method Notification Callback (Your `MethodNotificationUrl`)**: |
| 40 | + * Your application endpoint (the `MethodNotificationUrl` you provided) is called by the ACS/PSP, with `secure_id` available as a query parameter. |
| 41 | + * Your application should first use `secure_id` to retrieve the stored session data: `session_data = await azul.get_secure_session_info(secure_id)`. |
| 42 | + * From `session_data`, retrieve the `azul_order_id`: `azul_order_id = session_data.get("azul_order_id")`. |
| 43 | + * Then, call `await azul.process_3ds_method(azul_order_id=azul_order_id, method_notification_status="RECEIVED")`. |
| 44 | + * `SecureService` uses its internal state to check if this method was already processed (to prevent duplicates) and updates the transaction state. |
| 45 | + * The response from `azul.process_3ds_method` might trigger a challenge, requiring redirection. In this case, use `azul.create_challenge_form(...)` with data from the response and the `term_url` (retrieved from `session_data.get("term_url")`) to generate the necessary HTML form. |
| 46 | + |
| 47 | +3. **Challenge Callback (Your `TermUrl`)**: |
| 48 | + * Your application endpoint (the `TermUrl` you provided) is called by the ACS, typically via POST, after the cardholder completes (or skips) the challenge. |
| 49 | + * The `secure_id` (from the `TermUrl` query parameters) and `CRes` (Challenge Response, from the POST body) are received. |
| 50 | + * Your application calls `await azul.process_challenge(session_id=secure_id, challenge_response=CRes)`. |
| 51 | + * `SecureService` uses `secure_id` to retrieve session data (like `azul_order_id`) from its internal store and makes the final API call to process the challenge result. |
| 52 | + * The final transaction status (Approved/Declined) is returned. |
| 53 | + |
| 54 | +Refer to [pyazul/pyazul/README.md](mdc:pyazul/pyazul/pyazul/pyazul/README.md) for detailed FastAPI examples of this flow. |
0 commit comments