{"id":35523555,"url":"https://github.com/gowelle/flutterwave-php","last_synced_at":"2026-04-10T03:01:58.299Z","repository":{"id":325820269,"uuid":"1102445066","full_name":"gowelle/flutterwave-php","owner":"gowelle","description":"Modern Laravel package for Flutterwave v4 API integration","archived":false,"fork":false,"pushed_at":"2026-03-29T18:43:17.000Z","size":598,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-29T20:47:12.334Z","etag":null,"topics":["africa","flutterwave","laravel","mobile-money","payment","payments"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gowelle.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-23T13:31:00.000Z","updated_at":"2026-03-29T18:43:19.000Z","dependencies_parsed_at":"2026-01-17T08:02:40.660Z","dependency_job_id":null,"html_url":"https://github.com/gowelle/flutterwave-php","commit_stats":null,"previous_names":["gowelle/flutterwave-php"],"tags_count":26,"template":false,"template_full_name":null,"purl":"pkg:github/gowelle/flutterwave-php","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gowelle%2Fflutterwave-php","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gowelle%2Fflutterwave-php/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gowelle%2Fflutterwave-php/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gowelle%2Fflutterwave-php/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gowelle","download_url":"https://codeload.github.com/gowelle/flutterwave-php/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gowelle%2Fflutterwave-php/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31312744,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["africa","flutterwave","laravel","mobile-money","payment","payments"],"created_at":"2026-01-04T00:22:20.138Z","updated_at":"2026-04-02T18:09:46.937Z","avatar_url":"https://github.com/gowelle.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Flutterwave - Laravel Wrapper\n\n[![Tests](https://github.com/gowelle/flutterwave-php/actions/workflows/tests.yml/badge.svg)](https://github.com/gowelle/flutterwave-php/actions/workflows/tests.yml)\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/gowelle/flutterwave-php.svg)](https://packagist.org/packages/gowelle/flutterwave-php)\n[![Total Downloads](https://img.shields.io/packagist/dt/gowelle/flutterwave-php.svg)](https://packagist.org/packages/gowelle/flutterwave-php)\n\nA comprehensive Laravel wrapper for Flutterwave Services API v4. This package provides a type-safe, feature-rich integration for Flutterwave payment processing with automatic retry logic, rate limiting, webhook verification, and comprehensive error handling.\n\n## Table of Contents\n\n- [Features](#features)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Configuration](#configuration)\n- [Usage](#usage)\n  - [Direct Charges](#direct-charges)\n  - [Payments](#payments)\n  - [Payment Methods](#payment-methods)\n  - [Customers](#customers)\n  - [Orders](#orders)\n  - [Refunds](#refunds)\n  - [Transfers/Payouts](#transferspayouts)\n  - [Settlements](#settlements)\n  - [Banks](#banks)\n  - [Mobile Networks](#mobile-networks)\n  - [Virtual Accounts](#virtual-accounts)\n  - [Wallets](#wallets)\n- [UI Components](#ui-components)\n  - [Livewire Components](#livewire-components)\n  - [Vue Components](#vue-components)\n- [Localization](#localization)\n- [Charge Sessions](#charge-sessions)\n- [Events \u0026 Listeners](#events--listeners)\n- [Webhooks](#webhooks)\n- [Error Handling](#error-handling)\n- [Card Encryption](#card-encryption)\n- [Advanced Usage](#advanced-usage)\n- [Retry Logic](#retry-logic)\n- [Rate Limiting](#rate-limiting)\n- [Testing](#testing)\n- [Troubleshooting](#troubleshooting)\n- [Static Analysis](#static-analysis)\n- [Code Style](#code-style)\n- [Contributing](#contributing)\n- [License](#license)\n- [Support](#support)\n- [Changelog](#changelog)\n\n## Features\n\n- **Complete Flutterwave v4 API Support** - Full coverage of Flutterwave's v4 API including payments, refunds, transfers, settlements, virtual accounts, wallets, and more\n- **Direct Charge Orchestrator** - Simplified payment flow that combines customer, payment method, and charge creation in a single request\n- **Payment Methods Management** - Create, list, and manage payment methods for customers\n- **Orders API** - Complete order management with create, read, update, and list operations\n- **Bank Operations** - Get banks by country, resolve bank accounts, and retrieve bank branches\n- **Mobile Networks Support** - List mobile money networks by country for mobile payments\n- **Virtual Accounts** - Create and manage virtual bank accounts for receiving payments with multi-currency support\n- **Wallets API** - Resolve wallet accounts, retrieve transaction statements with pagination, and query balances for single or multiple currencies\n- **Charge Session Tracking** - Database-backed tracking of charge sessions with automatic status updates via webhooks\n- **Event System** - Laravel events for direct charge lifecycle and webhook processing\n- **Automatic Retry Logic** - Exponential backoff for transient failures (5xx errors, rate limits, timeouts)\n- **Rate Limiting** - Configurable per-request rate limiting to prevent API quota exhaustion\n- **Webhook Verification** - Secure webhook signature validation with automatic event dispatching\n- **Type-Safe DTOs** - Full TypeScript-like typing with PHP 8.3+ for better IDE support and fewer runtime errors\n- **Comprehensive Error Handling** - Detailed error messages with categorization (validation, authentication, API errors)\n- **Database Migrations** - Built-in migrations for charge session tracking\n- **Testing Ready** - Full test suite with Pest framework and HTTP faking support\n- **Laravel Integration** - Service provider, facade, and comprehensive configuration system\n- **UI Components** - Pre-built Livewire and Vue/Inertia components for payment forms, PIN/OTP input, status display, and saved payment methods\n\n## Requirements\n\n- PHP 8.3 or higher\n- Laravel 11.0, 12.0, or 13.0\n- Composer\n- Flutterwave account with API credentials\n\n## Installation\n\nInstall the package via Composer:\n\n```bash\ncomposer require gowelle/flutterwave-php\n```\n\nThe package will automatically register its service provider and facade.\n\n## Quick Start\n\n1. **Publish the configuration file:**\n\n```bash\nphp artisan vendor:publish --tag=\"flutterwave-config\"\n```\n\nOr publish all package assets:\n\n```bash\nphp artisan vendor:publish --tag=\"flutterwave-config\"\nphp artisan vendor:publish --tag=\"flutterwave-migrations\"\n```\n\n2. **Configure your Flutterwave credentials in `.env`:**\n\n```env\nFLUTTERWAVE_CLIENT_ID=your_client_id\nFLUTTERWAVE_CLIENT_SECRET=your_client_secret\nFLUTTERWAVE_SECRET_HASH=your_secret_hash\nFLUTTERWAVE_ENCRYPTION_KEY=your_encryption_key\nFLUTTERWAVE_ENVIRONMENT=staging  # or production\n```\n\n3. **Verify your credentials:**\n\n```bash\nphp artisan flutterwave:verify\n```\n\n4. **Retrieve your encryption key:**\n\nGet your encryption key from your Flutterwave dashboard under **API Settings**. You'll need this to encrypt card data before sending requests.\n\n5. **Run migrations (if using charge sessions):**\n\n```bash\nphp artisan migrate\n```\n\n6. **Start using the package:**\n\n\u003e **Important:** When making card charge requests, card data must be encrypted using AES-256-GCM encryption. See the [Flutterwave Encryption Documentation](https://developer.flutterwave.com/docs/encryption) for encryption requirements and PHP examples.\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\n\n// Create a direct charge\n// NOTE: Card data shown below must be encrypted before sending\n// See: https://developer.flutterwave.com/docs/encryption\n$charge = Flutterwave::directCharge()-\u003ecreate([\n    'amount' =\u003e 1000,\n    'currency' =\u003e 'TZS',\n    'reference' =\u003e 'ORDER-123',\n    'customer' =\u003e [\n        'email' =\u003e 'customer@example.com',      // Required\n        'name' =\u003e [\n            'first' =\u003e 'John',                   // Required\n            'last' =\u003e 'Doe',                     // Required\n        ],\n        'phone_number' =\u003e '+255123456789',        // Required\n    ],\n    'payment_method' =\u003e [\n        'type' =\u003e 'card',\n        'card' =\u003e [\n            'nonce' =\u003e 'RANDOMLY_GENERATED_12_CHAR_NONCE',\n            'encrypted_card_number' =\u003e 'BASE64_ENCRYPTED_CARD_NUMBER',\n            'encrypted_cvv' =\u003e 'BASE64_ENCRYPTED_CVV',\n            'encrypted_expiry_month' =\u003e 'BASE64_ENCRYPTED_EXPIRY_MONTH',\n            'encrypted_expiry_year' =\u003e 'BASE64_ENCRYPTED_EXPIRY_YEAR',\n        ],\n    ],\n    'redirect_url' =\u003e 'https://example.com/callback',\n]);\n```\n\n## Configuration\n\nThe package is configured via `config/flutterwave.php`. After publishing, you can customize all settings:\n\n### API Credentials\n\n```php\n'client_id' =\u003e env('FLUTTERWAVE_CLIENT_ID'),\n'client_secret' =\u003e env('FLUTTERWAVE_CLIENT_SECRET'),\n'secret_hash' =\u003e env('FLUTTERWAVE_SECRET_HASH'),\n```\n\nYour Flutterwave API credentials can be found in your Flutterwave dashboard under Settings \u003e API.\n\n### Environment\n\n```php\n'environment' =\u003e env('FLUTTERWAVE_ENVIRONMENT', 'staging'),\n```\n\nSet to `'staging'` for testing or `'production'` for live transactions.\n\n### API Settings\n\n```php\n'timeout' =\u003e env('FLUTTERWAVE_TIMEOUT', 30),           // Request timeout in seconds\n'max_retries' =\u003e env('FLUTTERWAVE_MAX_RETRIES', 3),    // Maximum retry attempts\n'retry_delay' =\u003e env('FLUTTERWAVE_RETRY_DELAY', 1000), // Retry delay in milliseconds\n```\n\n### Rate Limiting\n\n```php\n'rate_limit' =\u003e [\n    'enabled' =\u003e env('FLUTTERWAVE_RATE_LIMIT_ENABLED', true),\n    'max_requests' =\u003e env('FLUTTERWAVE_RATE_LIMIT_MAX', 100),\n    'per_seconds' =\u003e env('FLUTTERWAVE_RATE_LIMIT_WINDOW', 60),\n],\n```\n\nConfigure rate limiting to prevent hitting Flutterwave API limits. The default allows 100 requests per 60 seconds.\n\n### Logging\n\n```php\n'logging' =\u003e [\n    'enabled' =\u003e env('FLUTTERWAVE_LOGGING_ENABLED', true),\n    'channel' =\u003e env('FLUTTERWAVE_LOG_CHANNEL', 'stack'),\n    'level' =\u003e env('FLUTTERWAVE_LOG_LEVEL', 'info'),\n    'log_requests' =\u003e env('FLUTTERWAVE_LOG_REQUESTS', false),\n    'log_responses' =\u003e env('FLUTTERWAVE_LOG_RESPONSES', false),\n],\n```\n\nControl logging behavior. Enable `log_requests` and `log_responses` for debugging API interactions.\n\n### Webhook Settings\n\n```php\n'webhook' =\u003e [\n    'verify_signature' =\u003e env('FLUTTERWAVE_WEBHOOK_VERIFY', true),\n    'route_path' =\u003e env('FLUTTERWAVE_WEBHOOK_PATH', 'webhooks/flutterwave'),\n    'route_name' =\u003e 'flutterwave.webhook',\n    'middleware' =\u003e ['api'],\n],\n```\n\nConfigure webhook handling. The package automatically registers a webhook route that verifies signatures and dispatches events.\n\n### Default Currency\n\n```php\n'default_currency' =\u003e env('FLUTTERWAVE_DEFAULT_CURRENCY', 'TZS'),\n```\n\nSet the default currency for transactions if not specified in the request.\n\n### Charge Sessions\n\n```php\n'charge_sessions' =\u003e [\n    'enabled' =\u003e true,\n    'table_name' =\u003e 'flutterwave_charge_sessions',\n    'cleanup_after_days' =\u003e env('FLUTTERWAVE_SESSION_CLEANUP_DAYS', 30),\n    'auto_create' =\u003e env('FLUTTERWAVE_SESSION_AUTO_CREATE', false),\n    'max_polls' =\u003e env('FLUTTERWAVE_SESSION_MAX_POLLS', 60),\n],\n```\n\nConfigure charge session tracking:\n\n- `enabled`: Enable/disable charge session tracking\n- `auto_create`: Automatically create sessions when direct charges are created\n- `cleanup_after_days`: Days before old sessions are cleaned up\n- `max_polls`: Maximum polling attempts for charge status\n\n### Cache Settings\n\n```php\n'cache' =\u003e [\n    'enabled' =\u003e env('FLUTTERWAVE_CACHE_ENABLED', true),\n    'prefix' =\u003e 'flutterwave',\n    'ttl' =\u003e [\n        'access_token' =\u003e 3600,      // 1 hour (managed by auth service)\n        'banks' =\u003e 86400,            // 24 hours\n        'mobile_networks' =\u003e 86400,  // 24 hours\n    ],\n],\n```\n\nConfigure caching for frequently accessed data like access tokens, bank lists, and mobile networks.\n\n### Model Classes\n\n```php\n'models' =\u003e [\n    'user' =\u003e env('FLUTTERWAVE_USER_MODEL', 'App\\Models\\User'),\n    'payment' =\u003e env('FLUTTERWAVE_PAYMENT_MODEL', 'App\\Domain\\Payment\\Models\\Payment'),\n],\n```\n\nConfigure the model classes used by the ChargeSession model for relationships. These should be the fully qualified class names of your application's User and Payment models.\n\n### Environment Variables Reference\n\n| Variable                           | Description                            | Default                             |\n| ---------------------------------- | -------------------------------------- | ----------------------------------- |\n| `FLUTTERWAVE_CLIENT_ID`            | Your Flutterwave client ID             | -                                   |\n| `FLUTTERWAVE_CLIENT_SECRET`        | Your Flutterwave client secret         | -                                   |\n| `FLUTTERWAVE_SECRET_HASH`          | Your webhook secret hash               | -                                   |\n| `FLUTTERWAVE_ENCRYPTION_KEY`       | Encryption key for card data           | -                                   |\n| `FLUTTERWAVE_ENVIRONMENT`          | Environment: `staging` or `production` | `staging`                           |\n| `FLUTTERWAVE_DEBUG`                | Enable debug logging (dev only)        | `false`                             |\n| `FLUTTERWAVE_TIMEOUT`              | Request timeout in seconds             | `30`                                |\n| `FLUTTERWAVE_MAX_RETRIES`          | Maximum retry attempts                 | `3`                                 |\n| `FLUTTERWAVE_RETRY_DELAY`          | Retry delay in milliseconds            | `1000`                              |\n| `FLUTTERWAVE_RATE_LIMIT_ENABLED`   | Enable rate limiting                   | `true`                              |\n| `FLUTTERWAVE_RATE_LIMIT_MAX`       | Max requests per window                | `100`                               |\n| `FLUTTERWAVE_RATE_LIMIT_WINDOW`    | Time window in seconds                 | `60`                                |\n| `FLUTTERWAVE_LOGGING_ENABLED`      | Enable logging                         | `true`                              |\n| `FLUTTERWAVE_LOG_CHANNEL`          | Log channel                            | `stack`                             |\n| `FLUTTERWAVE_LOG_LEVEL`            | Log level                              | `info`                              |\n| `FLUTTERWAVE_LOG_REQUESTS`         | Log API requests                       | `false`                             |\n| `FLUTTERWAVE_LOG_RESPONSES`        | Log API responses                      | `false`                             |\n| `FLUTTERWAVE_WEBHOOK_VERIFY`       | Verify webhook signatures              | `true`                              |\n| `FLUTTERWAVE_WEBHOOK_PATH`         | Webhook route path                     | `webhooks/flutterwave`              |\n| `FLUTTERWAVE_DEFAULT_CURRENCY`     | Default currency code                  | `TZS`                               |\n| `FLUTTERWAVE_SESSION_CLEANUP_DAYS` | Days before session cleanup            | `30`                                |\n| `FLUTTERWAVE_SESSION_AUTO_CREATE`  | Auto-create charge sessions            | `false`                             |\n| `FLUTTERWAVE_SESSION_MAX_POLLS`    | Max polling attempts                   | `60`                                |\n| `FLUTTERWAVE_CACHE_ENABLED`        | Enable caching                         | `true`                              |\n| `FLUTTERWAVE_USER_MODEL`           | User model class                       | `App\\Models\\User`                   |\n| `FLUTTERWAVE_PAYMENT_MODEL`        | Payment model class                    | `App\\Domain\\Payment\\Models\\Payment` |\n\n## Usage\n\n### Direct Charges\n\nThe Direct Charge service uses Flutterwave's orchestrator endpoint to simplify the payment flow by combining customer, payment method, and charge creation in a single request.\n\n\u003e **Important:** When making card charge requests, you **must encrypt** the card information before sending the request. Card data (card number, CVV, expiry month, expiry year) must be encrypted using AES-256-GCM encryption. See the [Flutterwave Encryption Documentation](https://developer.flutterwave.com/docs/encryption) for detailed encryption requirements and examples.\n\n#### Creating a Direct Charge\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\nuse Gowelle\\Flutterwave\\Exceptions\\FlutterwaveException;\n\ntry {\n    // IMPORTANT: Card data must be encrypted before sending\n    // Retrieve your encryption key from Flutterwave dashboard \u003e API Settings\n    // Use AES-256-GCM encryption with a 12-character nonce\n    // See: https://developer.flutterwave.com/docs/encryption\n\n    $charge = Flutterwave::directCharge()-\u003ecreate([\n        'amount' =\u003e 10000,        // Amount in smallest currency unit (e.g., cents)\n        'currency' =\u003e 'TZS',       // Currency code\n        'reference' =\u003e 'ORDER-123', // Your unique reference\n        'customer' =\u003e [\n            'email' =\u003e 'customer@example.com',\n            'name' =\u003e [\n                'first' =\u003e 'John',\n                'last' =\u003e 'Doe',\n            ],\n            'phone_number' =\u003e '+255123456789',\n        ],\n        'payment_method' =\u003e [\n            'type' =\u003e 'card',\n            'card' =\u003e [\n                'nonce' =\u003e 'RANDOMLY_GENERATED_12_CHAR_NONCE',\n                'encrypted_card_number' =\u003e 'BASE64_ENCRYPTED_CARD_NUMBER',\n                'encrypted_cvv' =\u003e 'BASE64_ENCRYPTED_CVV',\n                'encrypted_expiry_month' =\u003e 'BASE64_ENCRYPTED_EXPIRY_MONTH',\n                'encrypted_expiry_year' =\u003e 'BASE64_ENCRYPTED_EXPIRY_YEAR',\n            ],\n        ],\n        'redirect_url' =\u003e 'https://example.com/callback',\n        'meta' =\u003e [\n            'order_id' =\u003e '12345',\n            'user_id' =\u003e '67890',\n        ],\n    ]);\n\n    // Check charge status\n    if ($charge-\u003estatus-\u003eisSuccessful()) {\n        // Payment succeeded\n    } elseif ($charge-\u003estatus-\u003erequiresAction()) {\n        // Handle next action (PIN, OTP, redirect, etc.)\n        $nextAction = $charge-\u003enextAction;\n\n        if ($nextAction-\u003etype-\u003erequiresCustomerInput()) {\n            // Show PIN or OTP input form\n        } elseif ($nextAction-\u003etype-\u003erequiresRedirect()) {\n            // Redirect to authorization URL\n            return redirect($nextAction-\u003edata['redirect_url']);\n        }\n    }\n} catch (FlutterwaveException $e) {\n    // Handle error\n    logger()-\u003eerror('Charge failed', [\n        'error' =\u003e $e-\u003egetUserFriendlyMessage(),\n        'details' =\u003e $e-\u003egetErrorData(),\n    ]);\n}\n```\n\n**Using DTO (type-safe):**\n\n```php\nuse Gowelle\\Flutterwave\\Data\\DirectCharge\\CreateDirectChargeRequest;\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\n\n$request = CreateDirectChargeRequest::make(\n    amount: 10000,\n    currency: 'NGN',\n    reference: 'ORDER-' . uniqid(),\n    customer: [\n        'email' =\u003e 'customer@example.com',\n        'name' =\u003e [\n            'first' =\u003e 'John',\n            'last' =\u003e 'Doe',\n        ],\n        'phone_number' =\u003e '+2341234567890',\n    ],\n    paymentMethod: [\n        'type' =\u003e 'card',\n        'card' =\u003e [\n            'nonce' =\u003e 'RANDOMLY_GENERATED_12_CHAR_NONCE',\n            'encrypted_card_number' =\u003e 'BASE64_ENCRYPTED_CARD_NUMBER',\n            'encrypted_cvv' =\u003e 'BASE64_ENCRYPTED_CVV',\n            'encrypted_expiry_month' =\u003e 'BASE64_ENCRYPTED_EXPIRY_MONTH',\n            'encrypted_expiry_year' =\u003e 'BASE64_ENCRYPTED_EXPIRY_YEAR',\n        ],\n    ],\n    redirectUrl: 'https://example.com/callback',\n    meta: ['order_id' =\u003e '12345'],\n);\n\n$charge = Flutterwave::directCharge()-\u003ecreateFromDto($request);\n\n// Access charge details including new fields\necho $charge-\u003efees;          // Transaction fees\necho $charge-\u003esettlementId;  // Settlement ID\n$charge-\u003eisSettled();        // Check if settled\n$charge-\u003eisDisputed();       // Check if disputed\n```\n\n#### Updating Charge Authorization\n\nWhen a charge requires additional authorization (PIN, OTP, AVS), submit the authorization data:\n\n```php\nuse Gowelle\\Flutterwave\\Data\\AuthorizationData;\nuse Gowelle\\Flutterwave\\Enums\\NextActionType;\n\n// For PIN authorization\n$authorization = AuthorizationData::createPin(\n    nonce: $nonce,              // Nonce from Flutterwave\n    encryptedPin: $encryptedPin // Encrypted PIN\n);\n\n// For OTP authorization\n$authorization = AuthorizationData::createOtp(\n    code: $otpCode // OTP code from customer\n);\n\n// For AVS (Address Verification System)\n$authorization = AuthorizationData::createAvs([\n    'line1' =\u003e '123 Main St',\n    'city' =\u003e 'Dar es Salaam',\n    'state' =\u003e 'Dar es Salaam',\n    'country' =\u003e 'TZ',\n    'postal_code' =\u003e '11101',\n]);\n\n// Submit authorization\n$updatedCharge = Flutterwave::directCharge()-\u003eupdateChargeAuthorization(\n    chargeId: $charge-\u003eid,\n    authorizationData: $authorization\n);\n\n// Check if charge is now complete\nif ($updatedCharge-\u003estatus-\u003eisSuccessful()) {\n    // Payment completed successfully\n} elseif ($updatedCharge-\u003estatus-\u003erequiresAction()) {\n    // May require additional authorization steps\n}\n```\n\n#### Checking Charge Status\n\n```php\nuse Gowelle\\Flutterwave\\Enums\\DirectChargeStatus;\n\n$status = Flutterwave::directCharge()-\u003estatus('charge-id');\n\nif ($status-\u003eisSuccessful()) {\n    // Payment succeeded\n} elseif ($status-\u003eisTerminal()) {\n    // Payment failed, cancelled, or timed out\n} else {\n    // Payment is pending or requires action\n}\n```\n\n### Payments\n\nThe Payments service handles the traditional charge flow where you create customers and payment methods separately.\n\n#### Processing a Payment\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\n\n// Process a payment with callback for trace ID\n$payment = Flutterwave::payments()-\u003eprocess([\n    'amount' =\u003e 1000,\n    'currency' =\u003e 'TZS',\n    'reference' =\u003e 'ORDER-123',\n    'customer_id' =\u003e 'CUST-456',\n    'payment_method_id' =\u003e 'PM-789',\n    'payment_method_type' =\u003e 'card',\n    'redirect_url' =\u003e 'https://example.com/callback',\n], function ($traceId) {\n    // Callback executed when charge is successfully created\n    logger()-\u003einfo('Charge created', ['trace_id' =\u003e $traceId]);\n});\n\n// Get payment status\n$status = Flutterwave::payments()-\u003estatus('charge-id');\n```\n\n### Payment Methods\n\nManage payment methods for customers.\n\n#### List Payment Methods\n\n```php\n$methods = Flutterwave::payments()-\u003emethods([\n    'customer_id' =\u003e 'CUST-456',\n    'currency' =\u003e 'TZS',\n]);\n```\n\n#### Create Payment Method\n\n\u003e **Important:** Card data must be encrypted using AES-256-GCM encryption before sending. See the [Flutterwave Encryption Documentation](https://developer.flutterwave.com/docs/encryption) for encryption requirements.\n\n```php\n// IMPORTANT: Card data must be encrypted before sending\n// Retrieve your encryption key from Flutterwave dashboard \u003e API Settings\n// Use AES-256-GCM encryption with a 12-character nonce\n// See: https://developer.flutterwave.com/docs/encryption\n\n$paymentMethod = Flutterwave::payments()-\u003ecreateMethod([\n    'customer_id' =\u003e 'CUST-456',\n    'type' =\u003e 'card',\n    'card' =\u003e [\n        'nonce' =\u003e 'RANDOMLY_GENERATED_12_CHAR_NONCE',\n        'encrypted_card_number' =\u003e 'BASE64_ENCRYPTED_CARD_NUMBER',\n        'encrypted_cvv' =\u003e 'BASE64_ENCRYPTED_CVV',\n        'encrypted_expiry_month' =\u003e 'BASE64_ENCRYPTED_EXPIRY_MONTH',\n        'encrypted_expiry_year' =\u003e 'BASE64_ENCRYPTED_EXPIRY_YEAR',\n    ],\n]);\n```\n\n#### Get Payment Method\n\n```php\n$paymentMethod = Flutterwave::payments()-\u003egetMethod('payment-method-id');\n```\n\n### Customers\n\nManage customer records.\n\n#### Create Customer\n\n\u003e **Per v4:** Only `email` is required. `name`, `phone`, and `address` are optional. `phone` must be an object with `country_code` (ISO 3166 alpha-3) and `number` (7–10 digits without country code).\n\n```php\n$customer = Flutterwave::customers()-\u003ecreate([\n    'email' =\u003e 'john@example.com',        // Required\n    'name' =\u003e [\n        'first' =\u003e 'John',\n        'middle' =\u003e 'Michael',             // Optional\n        'last' =\u003e 'Doe',\n    ],\n    'phone' =\u003e [\n        'country_code' =\u003e 'TZA',\n        'number' =\u003e '712345678',\n    ],\n]);\n```\n\n**Using DTO (type-safe, v4-aligned):**\n\nPer [Flutterwave v4](https://developer.flutterwave.com/reference/customers_create), only `email` is required; `name`, `phone`, and `address` are optional. `phone` must be an object with `country_code` (ISO 3166 alpha-3) and `number` (7–10 digits without country code).\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Customer\\CreateCustomerRequest;\n\n// Minimal (email only)\n$request = new CreateCustomerRequest(email: 'john@example.com');\n\n// With name, phone object, and optional address\n$request = new CreateCustomerRequest(\n    email: 'john@example.com',\n    firstName: 'John',\n    lastName: 'Doe',\n    phone: ['country_code' =\u003e 'TZA', 'number' =\u003e '712345678'],\n    middleName: 'Michael',  // optional\n    address: [              // optional: line1, line2?, city, state, postal_code, country\n        'line1' =\u003e '221B Baker Street',\n        'city' =\u003e 'London',\n        'state' =\u003e 'England',\n        'postal_code' =\u003e 'NW1 6XE',\n        'country' =\u003e 'GB',\n    ],\n);\n\n$customer = Flutterwave::customers()-\u003ecreateFromDto($request);\n```\n\n#### Update Customer\n\nPer [Flutterwave v4](https://developer.flutterwave.com/reference/customers_put), only `email` is required.\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Customer\\UpdateCustomerRequest;\n\n$request = new UpdateCustomerRequest(\n    email: 'john.updated@example.com',\n    firstName: 'John',\n    lastName: 'Doe',\n    phone: ['country_code' =\u003e 'TZA', 'number' =\u003e '987654321'],\n    address: [ /* optional */ ],\n);\n\n$customer = Flutterwave::customers()-\u003eupdateFromDto('customer-id', $request);\n```\n\n#### Search Customer\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Customer\\SearchCustomerRequest;\n\n$request = new SearchCustomerRequest(email: 'john@example.com');\n$customer = Flutterwave::customers()-\u003esearchFromDto($request);\n```\n\n#### Get Customer\n\n```php\n$customer = Flutterwave::customers()-\u003eget('customer-id');\n```\n\n#### List Customers\n\n```php\n$customers = Flutterwave::customers()-\u003elist([\n    'page' =\u003e 1,\n    'limit' =\u003e 20,\n]);\n```\n\n### Orders\n\nManage orders for tracking purchases and payments. The Order API supports both a simple creation method (using existing customer and payment method IDs) and an orchestrator method (with inline customer and payment details).\n\n#### Create Order (Simple)\n\nCreate an order using existing customer and payment method IDs:\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\n\n$order = Flutterwave::orders()-\u003ecreate([\n    'amount' =\u003e 10000,\n    'currency' =\u003e 'NGN',\n    'reference' =\u003e 'ORDER-' . uniqid(),  // 6-42 chars, unique\n    'customer_id' =\u003e 'cust_abc123',\n    'payment_method_id' =\u003e 'pm_xyz789',\n    'meta' =\u003e ['order_type' =\u003e 'subscription'],  // optional\n    'redirect_url' =\u003e 'https://example.com/callback',  // optional\n]);\n```\n\n**Using DTO (type-safe):**\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Order\\CreateOrderRequest;\n\n$request = CreateOrderRequest::make(\n    amount: 10000.00,\n    currency: 'NGN',\n    reference: 'ORDER-' . uniqid(),\n    customerId: 'cust_abc123',\n    paymentMethodId: 'pm_xyz789',\n    meta: ['source' =\u003e 'api'],      // optional\n    redirectUrl: 'https://example.com/callback',  // optional\n);\n\n$order = Flutterwave::orders()-\u003ecreateFromDto($request);\n```\n\n#### Create Order with Orchestrator\n\nCreate an order with inline customer and payment method details:\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Order\\CreateOrchestratorOrderRequest;\n\n$request = CreateOrchestratorOrderRequest::make(\n    amount: 10000.00,\n    currency: 'NGN',\n    reference: 'ORDER-' . uniqid(),\n    customer: [\n        'email' =\u003e 'customer@example.com',\n        'name' =\u003e [\n            'first' =\u003e 'John',\n            'last' =\u003e 'Doe',\n        ],\n        'phone' =\u003e '+2341234567890',\n    ],\n    paymentMethod: [\n        'type' =\u003e 'card',\n        'card' =\u003e [\n            'nonce' =\u003e 'RANDOM_12_CHAR',\n            'encrypted_card_number' =\u003e 'ENCRYPTED_DATA',\n            'encrypted_cvv' =\u003e 'ENCRYPTED_DATA',\n            'encrypted_expiry_month' =\u003e 'ENCRYPTED_DATA',\n            'encrypted_expiry_year' =\u003e 'ENCRYPTED_DATA',\n        ],\n    ],\n    meta: ['order_type' =\u003e 'subscription'],  // optional\n    redirectUrl: 'https://example.com/callback',  // optional\n);\n\n$order = Flutterwave::orders()-\u003ecreateWithOrchestrator($request);\n```\n\n#### Get Order\n\n```php\n$order = Flutterwave::orders()-\u003eretrieve('order-id');\n```\n\n#### List Orders\n\nList orders with optional filtering and pagination:\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Order\\ListOrdersRequest;\nuse Gowelle\\Flutterwave\\Data\\Order\\OrderStatus;\n\n// List all orders (default pagination)\n$orders = Flutterwave::orders()-\u003elist();\n\n// List with filters\n$request = new ListOrdersRequest(\n    status: OrderStatus::Completed,      // Filter by status\n    from: new DateTime('2024-01-01'),   // Start date\n    to: new DateTime('2024-12-31'),     // End date\n    customerId: 'cust_abc123',          // Filter by customer\n    paymentMethodId: 'pm_xyz789',       // Filter by payment method\n    page: 1,                            // Page number (\u003e=1)\n    size: 20,                           // Results per page (10-50)\n);\n\n$orders = Flutterwave::orders()-\u003elistWithFilters($request);\n```\n\n**Available Order Statuses:**\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Order\\OrderStatus;\n\nOrderStatus::Completed          // Order completed successfully\nOrderStatus::Pending            // Order is pending\nOrderStatus::Authorized         // Order is authorized, awaiting capture\nOrderStatus::PartiallyCompleted // Partially completed\nOrderStatus::Voided             // Order was voided\nOrderStatus::Failed             // Order failed\n```\n\n#### Update Order\n\nUpdate order metadata or perform actions (void/capture):\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Order\\UpdateOrderRequest;\nuse Gowelle\\Flutterwave\\Data\\Order\\OrderAction;\n\n// Update with metadata only\n$request = UpdateOrderRequest::withMeta(['updated' =\u003e true]);\n$order = Flutterwave::orders()-\u003eupdateFromDto('order-id', $request);\n\n// Void an order\n$request = UpdateOrderRequest::void(['reason' =\u003e 'Customer cancelled']);\n$order = Flutterwave::orders()-\u003eupdateFromDto('order-id', $request);\n\n// Capture an authorized order\n$request = UpdateOrderRequest::capture();\n$order = Flutterwave::orders()-\u003eupdateFromDto('order-id', $request);\n```\n\n**Convenience methods:**\n\n```php\n// Void an order directly\n$order = Flutterwave::orders()-\u003evoid('order-id', ['reason' =\u003e 'Cancelled']);\n\n// Capture an authorized order directly\n$order = Flutterwave::orders()-\u003ecapture('order-id');\n```\n\n\n\n### Refunds\n\nProcess refunds for completed charges with type-safe DTOs and filtering.\n\n#### Create Refund\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Refund\\CreateRefundRequest;\nuse Gowelle\\Flutterwave\\Enums\\RefundReason;\n\n// Create a refund with DTO (type-safe)\n$refund = Flutterwave::refunds()-\u003ecreate(\n    new CreateRefundRequest(\n        amount: 500.00,\n        chargeId: 'charge-123',\n        reason: RefundReason::REQUESTED_BY_CUSTOMER,\n        meta: ['note' =\u003e 'Customer requested refund'],  // optional\n    )\n);\n\n// Check refund status\nif ($refund-\u003eisSuccessful()) {\n    // Refund succeeded\n} elseif ($refund-\u003eisPending()) {\n    // Refund is processing\n}\n```\n\n#### Get Refund\n\n```php\n$refund = Flutterwave::refunds()-\u003eget('refund-id');\n\n// Access refund properties with type safety\necho $refund-\u003eamountRefunded;  // Float\necho $refund-\u003estatus-\u003evalue;   // String via enum\necho $refund-\u003estatus-\u003eisSuccessful();  // Boolean\n```\n\n#### List Refunds\n\nList all refunds with optional pagination and date filtering:\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Refund\\ListRefundsRequest;\n\n// List all refunds (default page=1, size=10)\n$refunds = Flutterwave::refunds()-\u003elist();\n\n// List with custom pagination\n$refunds = Flutterwave::refunds()-\u003elist(\n    new ListRefundsRequest(page: 2, size: 20)\n);\n\n// List refunds within date range\n$refunds = Flutterwave::refunds()-\u003elist(\n    new ListRefundsRequest(\n        page: 1,\n        size: 50,\n        from: now()-\u003esubDays(30),\n        to: now(),\n    )\n);\n\n// Access refund data\nforeach ($refunds as $refund) {\n    echo $refund-\u003eid;\n    echo $refund-\u003echargeId;\n    echo $refund-\u003eamountRefunded;\n    echo $refund-\u003estatus-\u003evalue;\n    echo $refund-\u003ereason;\n}\n```\n\n#### Refund Reasons\n\nThe `RefundReason` enum provides type-safe reason values:\n\n```php\nuse Gowelle\\Flutterwave\\Enums\\RefundReason;\n\nRefundReason::DUPLICATE                  // Duplicate charge\nRefundReason::FRAUDULENT                 // Fraudulent transaction\nRefundReason::REQUESTED_BY_CUSTOMER      // Customer requested\nRefundReason::EXPIRED_UNCAPTURED_CHARGE  // Expired uncaptured charge\n```\n\n#### Refund Status\n\nThe `RefundStatus` enum tracks refund state:\n\n```php\nuse Gowelle\\Flutterwave\\Enums\\RefundStatus;\n\nRefundStatus::NEW       // Refund created, not yet processed\nRefundStatus::PENDING   // Refund is being processed\nRefundStatus::SUCCEEDED // Refund completed successfully\nRefundStatus::FAILED    // Refund failed\n\n// Use helper methods for type-safe checks\n$refund-\u003estatus-\u003eisSuccessful();  // true if SUCCEEDED\n$refund-\u003estatus-\u003eisPending();     // true if NEW or PENDING\n$refund-\u003estatus-\u003eisTerminal();    // true if SUCCEEDED or FAILED\n```\n\n### Transfers/Payouts\n\nSend money to bank accounts, mobile money wallets, or Flutterwave wallets.\n\n#### Bank Transfer (Orchestrator)\n\nThe recommended approach - creates the recipient inline:\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Transfer\\BankTransferRequest;\n\n$transfer = Flutterwave::transfers()-\u003ebankTransfer(\n    new BankTransferRequest(\n        amount: 50000,\n        sourceCurrency: 'NGN',\n        destinationCurrency: 'NGN',\n        accountNumber: '0123456789',\n        bankCode: '044',\n        reference: 'PAYOUT-' . uniqid(),\n        narration: 'Monthly payout',     // optional\n    )\n);\n```\n\n#### Mobile Money Transfer (Orchestrator)\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Transfer\\MobileMoneyTransferRequest;\n\n$transfer = Flutterwave::transfers()-\u003emobileMoneyTransfer(\n    new MobileMoneyTransferRequest(\n        amount: 1000,\n        sourceCurrency: 'NGN',\n        destinationCurrency: 'GHS',\n        network: 'MTN',\n        phoneNumber: '2339012345678',\n        firstName: 'John',\n        lastName: 'Doe',\n        reference: 'MOMO-' . uniqid(),\n    )\n);\n```\n\n#### Get Transfer\n\n```php\n$transfer = Flutterwave::transfers()-\u003eget('transfer-id');\n```\n\n#### List Transfers\n\n```php\n$transfers = Flutterwave::transfers()-\u003elist();\n```\n\n#### Retry Failed Transfer\n\n```php\n$transfer = Flutterwave::transfers()-\u003eretry('transfer-id');\n```\n\n#### Create Recipient\n\nFor the general flow, pre-create recipients. The SDK provides factory methods for all Flutterwave recipient types.\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Transfer\\CreateRecipientRequest;\n```\n\n**Simple Bank Recipients** (account number + bank code only):\n\n```php\n// Nigerian (NGN) - simplest form\n$recipient = Flutterwave::transfers()-\u003ecreateRecipient(\n    CreateRecipientRequest::bankNgn(\n        accountNumber: '0123456789',\n        bankCode: '044',\n    )\n);\n```\n\n**African Bank Recipients** (with name):\n\n```php\n// Ethiopian (ETB)\nCreateRecipientRequest::bankEtb($accountNumber, $bankCode, $firstName, $lastName);\n\n// Kenyan (KES)\nCreateRecipientRequest::bankKes($accountNumber, $bankCode, $firstName, $lastName);\n\n// Malawian (MWK)\nCreateRecipientRequest::bankMwk($accountNumber, $bankCode, $firstName, $lastName);\n\n// Rwandan (RWF)\nCreateRecipientRequest::bankRwf($accountNumber, $bankCode, $firstName, $lastName);\n\n// Sierra Leonean (SLL)\nCreateRecipientRequest::bankSll($accountNumber, $bankCode, $firstName, $lastName);\n\n// Ugandan (UGX)\nCreateRecipientRequest::bankUgx($accountNumber, $bankCode, $firstName, $lastName);\n```\n\n**African Bank Recipients** (with name + branch):\n\n```php\n// Ghanaian (GHS)\nCreateRecipientRequest::bankGhs($accountNumber, $bankCode, $branch, $firstName, $lastName);\n\n// Central African (XAF)\nCreateRecipientRequest::bankXaf($accountNumber, $bankCode, $branch, $firstName, $lastName);\n\n// West African (XOF)\nCreateRecipientRequest::bankXof($accountNumber, $bankCode, $branch, $firstName, $lastName);\n```\n\n**International Bank Recipients** (full KYC required):\n\n```php\n// US (USD) bank recipient\n$recipient = Flutterwave::transfers()-\u003ecreateRecipient(\n    CreateRecipientRequest::bankUsd(\n        accountNumber: '1234567890',\n        bankCode: '021000021',\n        accountType: 'checking', // or 'savings'\n        routingNumber: '021000021',\n        swiftCode: 'CHASUS33',\n        firstName: 'John',\n        lastName: 'Doe',\n        phone: ['country_code' =\u003e '1', 'number' =\u003e '2025551234'],\n        email: 'john@example.com',\n        address: [\n            'city' =\u003e 'New York',\n            'country' =\u003e 'US',\n            'line1' =\u003e '123 Main St',\n            'postal_code' =\u003e '10001',\n            'state' =\u003e 'NY',\n        ],\n    )\n);\n\n// UK (GBP) bank recipient\nCreateRecipientRequest::bankGbp(\n    accountNumber: 'GB82WEST12345698765432',\n    accountType: 'individual', // or 'corporate'\n    bankName: 'HSBC',\n    sortCode: '401276',\n    firstName: 'John',\n    lastName: 'Doe',\n    phone: ['country_code' =\u003e '44', 'number' =\u003e '7911123456'],\n    email: 'john@example.com',\n    address: ['city' =\u003e 'London', 'country' =\u003e 'GB', 'line1' =\u003e '123 High St', 'postal_code' =\u003e 'EC1A 1BB'],\n);\n\n// European (EUR) bank recipient\nCreateRecipientRequest::bankEur(\n    accountNumber: 'DE89370400440532013000',\n    bankName: 'Deutsche Bank',\n    swiftCode: 'DEUTDEFF',\n    firstName: 'Hans',\n    lastName: 'Mueller',\n    phone: ['country_code' =\u003e '49', 'number' =\u003e '1701234567'],\n    email: 'hans@example.com',\n    address: ['city' =\u003e 'Berlin', 'country' =\u003e 'DE', 'line1' =\u003e 'Alexanderplatz 1', 'postal_code' =\u003e '10178'],\n);\n\n// South African (ZAR) bank recipient\nCreateRecipientRequest::bankZar(\n    accountNumber: '1234567890',\n    bankCode: 'ABSAZAJJ',\n    firstName: 'John',\n    lastName: 'Doe',\n    phone: ['country_code' =\u003e '27', 'number' =\u003e '823456789'],\n    email: 'john@example.com',\n    address: ['city' =\u003e 'Cape Town', 'country' =\u003e 'ZA', 'line1' =\u003e '123 Long St', 'postal_code' =\u003e '8001'],\n);\n```\n\n**Mobile Money Recipients** (ETB, GHS, KES, RWF, TZS, UGX, XAF, XOF, ZMW):\n\n```php\n$recipient = Flutterwave::transfers()-\u003ecreateRecipient(\n    CreateRecipientRequest::mobileMoney(\n        currency: 'TZS',\n        network: 'VODACOM',\n        phoneNumber: '255123456789',\n        firstName: 'John',\n        lastName: 'Doe',\n    )\n);\n```\n\n**Custom/New Types** (use constructor directly):\n\n```php\n// For any type not covered by factory methods\n$recipient = Flutterwave::transfers()-\u003ecreateRecipient(\n    new CreateRecipientRequest(\n        type: 'bank_custom',\n        bank: ['account_number' =\u003e '...', 'code' =\u003e '...'],\n        name: ['first' =\u003e 'John', 'last' =\u003e 'Doe'],\n    )\n);\n```\n\n#### Create Sender\n\nThe SDK supports all Flutterwave sender types:\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Transfer\\CreateSenderRequest;\n```\n\n**Generic Sender** (for most transfers):\n\n```php\n// Basic generic sender\n$sender = Flutterwave::transfers()-\u003ecreateSender(\n    CreateSenderRequest::generic(\n        firstName: 'John',\n        lastName: 'Doe',\n    )\n);\n\n// Generic sender with full details\n$sender = Flutterwave::transfers()-\u003ecreateSender(\n    CreateSenderRequest::generic(\n        firstName: 'John',\n        lastName: 'Doe',\n        middleName: 'Michael',\n        phone: ['country_code' =\u003e '234', 'number' =\u003e '8012345678'],\n        email: 'john@example.com',\n        address: [\n            'city' =\u003e 'Lagos',\n            'country' =\u003e 'NG',\n            'line1' =\u003e '123 Main Street',\n            'postal_code' =\u003e '100001',\n            'state' =\u003e 'Lagos',\n        ],\n    )\n);\n```\n\n**GBP/EUR Bank Sender** (requires full KYC for international transfers):\n\n```php\n// GBP sender for UK bank transfers\n$sender = Flutterwave::transfers()-\u003ecreateSender(\n    CreateSenderRequest::bankGbp(\n        firstName: 'John',\n        lastName: 'Doe',\n        phone: ['country_code' =\u003e '44', 'number' =\u003e '7911123456'],\n        email: 'john@example.com',\n        address: [\n            'city' =\u003e 'London',\n            'country' =\u003e 'GB',\n            'line1' =\u003e '123 High Street',\n            'postal_code' =\u003e 'EC1A 1BB',\n            'state' =\u003e 'Greater London',\n        ],\n    )\n);\n\n// EUR sender for European bank transfers\n$sender = Flutterwave::transfers()-\u003ecreateSender(\n    CreateSenderRequest::bankEur(\n        firstName: 'Hans',\n        lastName: 'Mueller',\n        phone: ['country_code' =\u003e '49', 'number' =\u003e '1701234567'],\n        email: 'hans@example.com',\n        address: [\n            'city' =\u003e 'Berlin',\n            'country' =\u003e 'DE',\n            'line1' =\u003e 'Alexanderplatz 1',\n            'postal_code' =\u003e '10178',\n            'state' =\u003e 'Berlin',\n        ],\n    )\n);\n```\n\n#### Get Transfer Rate\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Transfer\\GetRateRequest;\n\n$rate = Flutterwave::transfers()-\u003egetRate(\n    new GetRateRequest(\n        sourceCurrency: 'NGN',\n        destinationCurrency: 'GHS',\n        amount: 10000,\n    )\n);\n```\n\n#### General Flow Transfer\n\nFor the general flow, use pre-created recipient and sender IDs:\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Transfer\\CreateTransferRequest;\n\n// First, create recipient and sender (see above)\n$recipient = Flutterwave::transfers()-\u003ecreateRecipient(...);\n$sender = Flutterwave::transfers()-\u003ecreateSender(...);\n\n// Then create the transfer\n$transfer = Flutterwave::transfers()-\u003ecreate(\n    new CreateTransferRequest(\n        amount: 50000,\n        sourceCurrency: 'NGN',\n        destinationCurrency: 'NGN',\n        recipientId: $recipient-\u003eid,\n        senderId: $sender-\u003eid,\n        reference: 'PAYOUT-' . uniqid(),\n    )\n);\n```\n\n### Settlements\n\nRetrieve settlement information (read-only).\n\n#### Get Settlement\n\n```php\n$settlement = Flutterwave::settlements()-\u003eget('settlement-id');\n```\n\n#### List Settlements\n\n```php\n$settlements = Flutterwave::settlements()-\u003elist([\n    'page' =\u003e 1,\n    'limit' =\u003e 20,\n]);\n```\n\n### Banks\n\nGet bank information and resolve account details.\n\n#### Get Banks by Country\n\n```php\n$banks = Flutterwave::banks()-\u003eget('NG'); // Country code (e.g., NG, TZ, KE)\n```\n\n#### Get Bank Branches\n\n```php\n$branches = Flutterwave::banks()-\u003ebranches('bank-id');\n```\n\n#### Resolve Bank Account\n\n```php\n$account = Flutterwave::banks()-\u003eresolveAccount(\n    bankCode: '044',\n    accountNumber: '0123456789',\n    currency: 'NGN'\n);\n\n// Access resolved account details\necho $account-\u003eaccountName;\necho $account-\u003eaccountNumber;\n```\n\n**Using DTO (type-safe):**\n\n```php\nuse Gowelle\\Flutterwave\\Data\\Banks\\BankAccountResolveRequest;\n\n$request = new BankAccountResolveRequest(\n    bankCode: '044',\n    accountNumber: '0123456789',\n    currency: 'NGN',  // defaults to NGN if omitted\n);\n\n$account = Flutterwave::banks()-\u003eresolveFromDto($request);\n```\n\n### Mobile Networks\n\nGet mobile money networks by country.\n\n#### List Mobile Networks\n\n```php\n$networks = Flutterwave::mobileNetworks()-\u003elist('TZ'); // Country code\n\nforeach ($networks as $network) {\n    echo $network-\u003ename;\n    echo $network-\u003ecode;\n}\n```\n\n### Wallets\n\nManage Flutterwave wallet operations including account lookup, statement retrieval, and balance queries.\n\n#### Resolve Wallet Account\n\nVerify wallet account information for a customer:\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\n\n$account = Flutterwave::wallets()-\u003eresolveAccount(\n    provider: 'flutterwave',\n    identifier: 'wallet_123'\n);\n\n// Access resolved account details\necho $account-\u003eprovider;      // 'flutterwave'\necho $account-\u003eidentifier;    // 'wallet_123'\necho $account-\u003ename;          // Account holder name\n```\n\n#### Get Wallet Statement\n\nRetrieve wallet transaction statement with pagination and filtering:\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\n\n$statement = Flutterwave::wallets()-\u003egetStatement([\n    'currency' =\u003e 'NGN',           // Required: 3-letter currency code\n    'size' =\u003e 20,                  // Optional: Page size (10-50, default 10)\n    'from' =\u003e '2024-01-01T00:00:00Z', // Optional: Start date (ISO 8601)\n    'to' =\u003e '2024-12-31T23:59:59Z',   // Optional: End date (ISO 8601)\n    'next' =\u003e 'next_cursor',       // Optional: Next page cursor\n    'previous' =\u003e 'prev_cursor',   // Optional: Previous page cursor\n]);\n\n// Access statement data\necho $statement-\u003ecursor-\u003etotal;        // Total transactions\necho $statement-\u003ecursor-\u003elimit;       // Page limit\necho $statement-\u003ecursor-\u003ehasMoreItems; // Whether more items exist\necho $statement-\u003ecursor-\u003enext;         // Next page cursor\necho $statement-\u003ecursor-\u003eprevious;      // Previous page cursor\n\n// Access transactions\nforeach ($statement-\u003etransactions as $transaction) {\n    echo $transaction['transaction_direction']; // 'credit' or 'debit'\n    echo $transaction['amount']['value'];\n    echo $transaction['amount']['currency'];\n    echo $transaction['balance']['before'];\n    echo $transaction['balance']['after'];\n}\n```\n\n#### Get Wallet Balance (Single Currency)\n\nFetch the available balance for a specific currency:\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\n\n$balance = Flutterwave::wallets()-\u003egetBalance('NGN');\n\necho $balance-\u003ecurrency;           // 'NGN'\necho $balance-\u003eavailableBalance;   // 1200.09\n```\n\n#### Get Wallet Balances (Multiple Currencies)\n\nFetch available balances for all currencies:\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\n\n$balances = Flutterwave::wallets()-\u003egetBalances();\n\nforeach ($balances as $balance) {\n    echo $balance-\u003ecurrency;           // 'NGN', 'USD', etc.\n    echo $balance-\u003eavailableBalance;   // Available balance\n}\n```\n\n### Virtual Accounts\n\nCreate and manage virtual bank accounts for receiving payments. Virtual account operations are now integrated into the Banks service for a unified banking experience.\n\n#### Create Virtual Account\n\nCreate a virtual account for a specific customer using type-safe DTOs:\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\nuse Gowelle\\Flutterwave\\Data\\VirtualAccount\\CreateVirtualAccountRequestDTO;\nuse Gowelle\\Flutterwave\\Enums\\VirtualAccountCurrency;\nuse Gowelle\\Flutterwave\\Enums\\VirtualAccountType;\n\n$request = new CreateVirtualAccountRequestDTO(\n    reference: 'unique-ref-' . time(),      // 6-42 chars, unique\n    customerId: 'cus_123',                  // Existing customer ID\n    amount: 0,                              // 0 for static accounts\n    currency: VirtualAccountCurrency::NGN,  // NGN, GHS, EGP, or KES\n    accountType: VirtualAccountType::STATIC, // STATIC or DYNAMIC\n    narration: 'Payment for Order #123',    // Optional\n    meta: ['order_id' =\u003e '123'],            // Optional metadata\n);\n\n$account = Flutterwave::banks()-\u003ecreateVirtualAccount($request);\n\n// Access account details\necho $account-\u003eaccountNumber;      // Virtual account number\necho $account-\u003eaccountBankName;    // Bank name (e.g., \"WEMA BANK\")\necho $account-\u003ereference;          // Your reference\necho $account-\u003estatus-\u003evalue;      // 'active' or 'inactive'\n```\n\n#### Retrieve Virtual Account\n\nGet details of a specific virtual account:\n\n```php\n$account = Flutterwave::banks()-\u003eretrieveVirtualAccount('va_123');\n\necho $account-\u003eaccountNumber;\necho $account-\u003eaccountBankName;\necho $account-\u003estatus-\u003evalue;\necho $account-\u003ecurrency-\u003evalue;\n```\n\n#### List Virtual Accounts\n\nList all virtual accounts:\n\n```php\n$accounts = Flutterwave::banks()-\u003elistVirtualAccounts();\n\nforeach ($accounts as $account) {\n    echo $account-\u003eaccountNumber;\n    echo $account-\u003estatus-\u003evalue;\n    echo $account-\u003ecurrency-\u003evalue;\n}\n```\n\n#### List Virtual Accounts with Filters\n\nList virtual accounts with pagination and filtering using DTOs:\n\n```php\nuse Gowelle\\Flutterwave\\Data\\VirtualAccount\\ListVirtualAccountsParamsDTO;\n\n$params = new ListVirtualAccountsParamsDTO(\n    page: 1,                                // Page number (min: 1)\n    size: 20,                               // Page size (10-50)\n    from: '2024-01-01T00:00:00Z',          // Start date (ISO 8601)\n    to: '2024-12-31T23:59:59Z',            // End date (ISO 8601)\n    reference: 'unique-ref-123',            // Filter by reference\n);\n\n$accounts = Flutterwave::banks()-\u003elistVirtualAccountsWithParams($params);\n\nforeach ($accounts as $account) {\n    echo $account-\u003eaccountNumber;\n    echo $account-\u003ereference;\n}\n```\n\n#### Update Virtual Account\n\nUpdate account status or BVN using DTOs:\n\n```php\nuse Gowelle\\Flutterwave\\Data\\VirtualAccount\\UpdateVirtualAccountRequestDTO;\nuse Gowelle\\Flutterwave\\Enums\\VirtualAccountStatus;\n\n// Deactivate an account\n$request = UpdateVirtualAccountRequestDTO::forStatusUpdate(\n    VirtualAccountStatus::INACTIVE\n);\n\n$updated = Flutterwave::banks()-\u003eupdateVirtualAccount('va_123', $request);\n\n// Update BVN\n$request = UpdateVirtualAccountRequestDTO::forBvnUpdate(\n    bvn: '12345678901',\n    meta: ['updated_by' =\u003e 'admin']  // Optional\n);\n\n$updated = Flutterwave::banks()-\u003eupdateVirtualAccount('va_123', $request);\n```\n\n#### Supported Currencies\n\nVirtual accounts support the following currencies:\n\n- **NGN** - Nigerian Naira\n- **GHS** - Ghanaian Cedi\n- **EGP** - Egyptian Pound (requires `customer_account_number`)\n- **KES** - Kenyan Shilling (requires `customer_account_number`)\n\n#### Account Types\n\n- **STATIC** - Permanent account that can be reused (amount should be 0)\n- **DYNAMIC** - Temporary account for specific transaction (expires after configured duration)\n\n#### Example: Complete Virtual Account Flow\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\nuse Gowelle\\Flutterwave\\Data\\VirtualAccount\\CreateVirtualAccountRequestDTO;\nuse Gowelle\\Flutterwave\\Enums\\VirtualAccountCurrency;\nuse Gowelle\\Flutterwave\\Enums\\VirtualAccountType;\n\n// Create a static account for a customer\n$request = new CreateVirtualAccountRequestDTO(\n    reference: 'order-' . $order-\u003eid,\n    customerId: 'cus_' . $customer-\u003eid,\n    amount: 0,\n    currency: VirtualAccountCurrency::NGN,\n    accountType: VirtualAccountType::STATIC,\n    narration: \"Payment for Order #{$order-\u003eid}\",\n    meta: [\n        'order_id' =\u003e $order-\u003eid,\n        'customer_id' =\u003e $customer-\u003eid,\n    ],\n);\n\n$account = Flutterwave::banks()-\u003ecreateVirtualAccount($request);\n\n// Store the account number for the customer to pay into\n$bankAccount = $account-\u003eaccountNumber;\n$bankName = $account-\u003eaccountBankName;\n\n// Later, retrieve to check status\n$current = Flutterwave::banks()-\u003eretrieveVirtualAccount($account-\u003eid);\nif ($current-\u003eisActive()) {\n    // Account is still active\n}\n\n// When no longer needed, deactivate\n$api-\u003eupdate($account['data']['id'], [\n    'action_type' =\u003e 'update_status',\n    'status' =\u003e 'inactive',\n]);\n```\n\n## UI Components\n\nThis package includes pre-built UI components for both Laravel Livewire and Vue/Inertia applications, enabling rapid payment form integration with client-side encryption.\n\n### Publishing Assets\n\n```bash\n# Publish Livewire Blade views (for customization)\nphp artisan vendor:publish --tag=flutterwave-views\n\n# Publish Vue components (for Vue/Inertia apps)\nphp artisan vendor:publish --tag=flutterwave-vue\n```\n\n### Livewire Components\n\nThe package includes 5 Livewire components that are automatically registered when Livewire is installed.\n\n#### Payment Form\n\nA complete card payment form with client-side encryption:\n\n```blade\n\u003clivewire:flutterwave-payment-form\n    :amount=\"10000\"\n    currency=\"TZS\"\n    :customer=\"['email' =\u003e 'user@example.com', 'firstName' =\u003e 'John', 'lastName' =\u003e 'Doe']\"\n    redirect-url=\"/payment/callback\"\n    @payment-success=\"handleSuccess\"\n    @payment-error=\"handleError\"\n    @authorization-required=\"handleAuth\"\n/\u003e\n```\n\n**Props:**\n- `amount` (int) - Payment amount in minor units\n- `currency` (string) - 3-letter currency code (e.g., \"TZS\", \"NGN\", \"KES\")\n- `customer` (array) - Customer details with `email`, `firstName`, `lastName`, `phone`\n- `redirectUrl` (string) - Callback URL after payment\n\n**Events:**\n- `payment-success` - Payment completed successfully\n- `payment-error` - Payment failed\n- `authorization-required` - PIN/OTP/redirect needed\n\n#### PIN Input\n\nSecure PIN authorization input with masked fields:\n\n```blade\n\u003clivewire:flutterwave-pin-input\n    :charge-id=\"$chargeId\"\n    :pin-length=\"4\"\n    @pin-submitted=\"handlePinSubmit\"\n    @cancelled=\"handleCancel\"\n/\u003e\n```\n\n**Props:**\n- `chargeId` (string) - Direct charge ID requiring authorization\n- `pinLength` (int) - Number of PIN digits (default: 4)\n\n#### OTP Input\n\nOTP verification with resend countdown:\n\n```blade\n\u003clivewire:flutterwave-otp-input\n    :charge-id=\"$chargeId\"\n    :length=\"6\"\n    :resend-countdown=\"60\"\n    @otp-submitted=\"handleOtpSubmit\"\n    @otp-resent=\"handleResend\"\n    @cancelled=\"handleCancel\"\n/\u003e\n```\n\n**Props:**\n- `chargeId` (string) - Direct charge ID requiring OTP\n- `length` (int) - OTP length (default: 6)\n- `resendCountdown` (int) - Seconds before resend allowed (default: 60)\n\n#### Payment Status\n\nReal-time payment status display with polling:\n\n```blade\n\u003clivewire:flutterwave-payment-status\n    :charge-id=\"$chargeId\"\n    :poll-interval=\"3000\"\n    :auto-poll=\"true\"\n    @status-updated=\"handleStatusUpdate\"\n    @payment-complete=\"handleComplete\"\n/\u003e\n```\n\n**Props:**\n- `chargeId` (string) - Direct charge ID to monitor\n- `pollInterval` (int) - Polling interval in milliseconds (default: 3000)\n- `autoPoll` (bool) - Start polling automatically (default: true)\n\n#### Payment Methods\n\nList and manage saved payment methods:\n\n```blade\n\u003clivewire:flutterwave-payment-methods\n    :customer-id=\"$customerId\"\n    currency=\"TZS\"\n    @method-selected=\"handleMethodSelect\"\n    @add-new-clicked=\"handleAddNew\"\n/\u003e\n```\n\n**Props:**\n- `customerId` (string) - Customer ID to fetch methods for\n- `currency` (string) - Currency to filter methods\n\n---\n\n#### Complete Livewire Payment Flow Example\n\nHere's how to implement a complete payment flow in a Livewire component:\n\n```php\n// app/Livewire/CheckoutPage.php\nnamespace App\\Livewire;\n\nuse Livewire\\Component;\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\nuse Gowelle\\Flutterwave\\Data\\AuthorizationData;\n\nclass CheckoutPage extends Component\n{\n    public int $amount = 10000;\n    public string $currency = 'TZS';\n    \n    // Payment flow state\n    public string $step = 'form'; // 'form', 'pin', 'otp', 'status'\n    public ?string $chargeId = null;\n    public ?string $error = null;\n    \n    protected $listeners = [\n        'payment-success' =\u003e 'handlePaymentSuccess',\n        'payment-error' =\u003e 'handlePaymentError',\n        'authorization-required' =\u003e 'handleAuthorizationRequired',\n        'pin-submitted' =\u003e 'handlePinSubmitted',\n        'otp-submitted' =\u003e 'handleOtpSubmitted',\n        'cancelled' =\u003e 'handleCancelled',\n    ];\n    \n    public function handlePaymentSuccess($charge)\n    {\n        // Payment completed - redirect to success page\n        return redirect()-\u003eroute('payment.success', ['reference' =\u003e $charge['reference']]);\n    }\n    \n    public function handlePaymentError($message)\n    {\n        $this-\u003eerror = $message;\n        $this-\u003estep = 'form';\n    }\n    \n    public function handleAuthorizationRequired($data)\n    {\n        $this-\u003echargeId = $data['chargeId'];\n        \n        // Switch to appropriate input based on authorization type\n        match ($data['type']) {\n            'requires_pin' =\u003e $this-\u003estep = 'pin',\n            'requires_otp' =\u003e $this-\u003estep = 'otp',\n            'redirect_url' =\u003e redirect($data['redirectUrl']),\n            default =\u003e null,\n        };\n    }\n    \n    public function handlePinSubmitted($data)\n    {\n        try {\n            $authorization = AuthorizationData::createPin(\n                nonce: $data['nonce'],\n                encryptedPin: $data['encrypted_pin']\n            );\n            \n            $charge = Flutterwave::directCharge()-\u003eupdateChargeAuthorization(\n                chargeId: $this-\u003echargeId,\n                authorizationData: $authorization\n            );\n            \n            $this-\u003eprocessChargeResult($charge);\n        } catch (\\Exception $e) {\n            $this-\u003eerror = $e-\u003egetMessage();\n            $this-\u003estep = 'form';\n        }\n    }\n    \n    public function handleOtpSubmitted($otp)\n    {\n        try {\n            $authorization = AuthorizationData::createOtp(code: $otp);\n            \n            $charge = Flutterwave::directCharge()-\u003eupdateChargeAuthorization(\n                chargeId: $this-\u003echargeId,\n                authorizationData: $authorization\n            );\n            \n            $this-\u003eprocessChargeResult($charge);\n        } catch (\\Exception $e) {\n            $this-\u003eerror = $e-\u003egetMessage();\n            $this-\u003estep = 'form';\n        }\n    }\n    \n    public function handleCancelled()\n    {\n        $this-\u003estep = 'form';\n        $this-\u003echargeId = null;\n    }\n    \n    protected function processChargeResult($charge)\n    {\n        if ($charge-\u003estatus-\u003eisSuccessful()) {\n            return redirect()-\u003eroute('payment.success');\n        }\n        \n        if ($charge-\u003estatus-\u003erequiresAction()) {\n            $this-\u003ehandleAuthorizationRequired([\n                'chargeId' =\u003e $charge-\u003eid,\n                'type' =\u003e $charge-\u003enextAction-\u003etype-\u003evalue,\n                'redirectUrl' =\u003e $charge-\u003enextAction-\u003edata['redirect_url'] ?? null,\n            ]);\n        }\n    }\n    \n    public function render()\n    {\n        return view('livewire.checkout-page');\n    }\n}\n```\n\n**Blade Template:**\n\n```blade\n{{-- resources/views/livewire/checkout-page.blade.php --}}\n\u003cdiv class=\"checkout-container\"\u003e\n    @if ($error)\n        \u003cdiv class=\"alert alert-danger\"\u003e{{ $error }}\u003c/div\u003e\n    @endif\n\n    @if ($step === 'form')\n        \u003clivewire:flutterwave-payment-form\n            :amount=\"$amount\"\n            :currency=\"$currency\"\n            :customer=\"['email' =\u003e auth()-\u003euser()-\u003eemail, 'firstName' =\u003e auth()-\u003euser()-\u003efirst_name, 'lastName' =\u003e auth()-\u003euser()-\u003elast_name]\"\n        /\u003e\n    @elseif ($step === 'pin')\n        \u003clivewire:flutterwave-pin-input :charge-id=\"$chargeId\" /\u003e\n    @elseif ($step === 'otp')\n        \u003clivewire:flutterwave-otp-input :charge-id=\"$chargeId\" /\u003e\n    @elseif ($step === 'status')\n        \u003clivewire:flutterwave-payment-status :charge-id=\"$chargeId\" :auto-poll=\"true\" /\u003e\n    @endif\n\u003c/div\u003e\n```\n\n---\n\n#### Livewire Events Reference\n\nAll Livewire components dispatch events that you can listen to in parent components:\n\n| Component | Event | Payload |\n|-----------|-------|---------|\n| `flutterwave-payment-form` | `payment-success` | `['id' =\u003e string, 'reference' =\u003e string, 'status' =\u003e string]` |\n| `flutterwave-payment-form` | `payment-error` | `string` (error message) |\n| `flutterwave-payment-form` | `authorization-required` | `['chargeId' =\u003e string, 'type' =\u003e string, 'redirectUrl' =\u003e ?string]` |\n| `flutterwave-pin-input` | `pin-submitted` | `['nonce' =\u003e string, 'encrypted_pin' =\u003e string]` |\n| `flutterwave-pin-input` | `cancelled` | - |\n| `flutterwave-otp-input` | `otp-submitted` | `string` (OTP code) |\n| `flutterwave-otp-input` | `otp-resent` | - |\n| `flutterwave-otp-input` | `cancelled` | - |\n| `flutterwave-payment-status` | `status-updated` | `['status' =\u003e string, 'charge' =\u003e array]` |\n| `flutterwave-payment-status` | `payment-complete` | `array` (charge data) |\n| `flutterwave-payment-methods` | `method-selected` | `string` (method ID) |\n| `flutterwave-payment-methods` | `add-new-clicked` | - |\n\n### Vue Components\n\nFor Vue/Inertia applications, publish the components and import them:\n\n```bash\nphp artisan vendor:publish --tag=flutterwave-vue\n```\n\nComponents will be published to `resources/js/vendor/flutterwave/`.\n\n#### Payment Form (Vue)\n\n```vue\n\u003cscript setup lang=\"ts\"\u003e\nimport { ref } from 'vue';\nimport PaymentForm from '@/vendor/flutterwave/components/PaymentForm.vue';\n\nconst encryptionKey = ref('your-encryption-key');\n\nfunction handleSuccess(charge) {\n  console.log('Payment successful:', charge);\n}\n\nfunction handleError(error) {\n  console.error('Payment failed:', error);\n}\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cPaymentForm\n    :amount=\"10000\"\n    currency=\"TZS\"\n    :encryption-key=\"encryptionKey\"\n    :customer=\"{ email: 'user@example.com', firstName: 'John', lastName: 'Doe' }\"\n    @success=\"handleSuccess\"\n    @error=\"handleError\"\n  /\u003e\n\u003c/template\u003e\n```\n\n#### PIN Input (Vue)\n\n```vue\n\u003cscript setup lang=\"ts\"\u003e\nimport PinInput from '@/vendor/flutterwave/components/PinInput.vue';\n\nfunction handleSubmit({ nonce, encrypted_pin }) {\n  // Submit encrypted PIN to API\n}\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cPinInput\n    charge-id=\"dc_123\"\n    :encryption-key=\"encryptionKey\"\n    :pin-length=\"4\"\n    @submit=\"handleSubmit\"\n    @cancel=\"handleCancel\"\n  /\u003e\n\u003c/template\u003e\n```\n\n#### OTP Input (Vue)\n\n```vue\n\u003cscript setup lang=\"ts\"\u003e\nimport OtpInput from '@/vendor/flutterwave/components/OtpInput.vue';\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cOtpInput\n    charge-id=\"dc_123\"\n    :length=\"6\"\n    @submit=\"handleOtpSubmit\"\n    @resend=\"handleResend\"\n    @cancel=\"handleCancel\"\n  /\u003e\n\u003c/template\u003e\n```\n\n#### Payment Status (Vue)\n\n```vue\n\u003cscript setup lang=\"ts\"\u003e\nimport PaymentStatus from '@/vendor/flutterwave/components/PaymentStatus.vue';\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cPaymentStatus\n    charge-id=\"dc_123\"\n    :start-polling=\"true\"\n    :poll-interval=\"3000\"\n    @success=\"handleSuccess\"\n    @failed=\"handleFailed\"\n    @timeout=\"handleTimeout\"\n  /\u003e\n\u003c/template\u003e\n```\n\n#### Payment Methods (Vue)\n\n```vue\n\u003cscript setup lang=\"ts\"\u003e\nimport PaymentMethods from '@/vendor/flutterwave/components/PaymentMethods.vue';\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cPaymentMethods\n    customer-id=\"cust_123\"\n    :selected-method-id=\"selectedId\"\n    @select=\"handleSelect\"\n    @add-new=\"handleAddNew\"\n  /\u003e\n\u003c/template\u003e\n```\n\n### Client-Side Encryption\n\nThe Vue components include encryption utilities for secure card data handling:\n\n```typescript\nimport { encryptCardData, encryptPin, generateNonce } from '@/vendor/flutterwave/utils/encryption';\n\n// Encrypt card data\nconst nonce = generateNonce();\nconst encrypted = await encryptCardData(encryptionKey, {\n  cardNumber: '4111111111111111',\n  expiryMonth: '12',\n  expiryYear: '25',\n  cvv: '123',\n}, nonce);\n\n// Encrypt PIN\nconst pinEncrypted = await encryptPin(encryptionKey, '1234');\n```\n\n---\n\n### Backend API Setup\n\nThe Vue components communicate with your backend via API endpoints. Add these routes to handle payment processing:\n\n```php\n// routes/api.php\nuse App\\Http\\Controllers\\FlutterwaveController;\n\nRoute::prefix('flutterwave')-\u003egroup(function () {\n    Route::post('/charges', [FlutterwaveController::class, 'createCharge']);\n    Route::post('/charges/{chargeId}/authorize', [FlutterwaveController::class, 'authorize']);\n    Route::get('/charges/{chargeId}/status', [FlutterwaveController::class, 'status']);\n});\n```\n\n**Example Controller:**\n\n```php\n// app/Http/Controllers/FlutterwaveController.php\nnamespace App\\Http\\Controllers;\n\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\nuse Gowelle\\Flutterwave\\Data\\AuthorizationData;\nuse Illuminate\\Http\\Request;\n\nclass FlutterwaveController extends Controller\n{\n    public function createCharge(Request $request)\n    {\n        $validated = $request-\u003evalidate([\n            'amount' =\u003e 'required|numeric|min:1',\n            'currency' =\u003e 'required|string|size:3',\n            'reference' =\u003e 'required|string',\n            'customer' =\u003e 'required|array',\n            'customer.email' =\u003e 'required|email',\n            'customer.name.first' =\u003e 'required|string',\n            'customer.name.last' =\u003e 'required|string',\n            'customer.phone_number' =\u003e 'required|string',\n            'payment_method' =\u003e 'required|array',\n            'redirect_url' =\u003e 'nullable|url',\n            'meta' =\u003e 'nullable|array',\n        ]);\n\n        $charge = Flutterwave::directCharge()-\u003ecreate($validated);\n\n        return response()-\u003ejson(['charge' =\u003e $charge]);\n    }\n\n    public function authorize(Request $request, string $chargeId)\n    {\n        $type = $request-\u003einput('type');\n\n        $authorization = match ($type) {\n            'pin' =\u003e AuthorizationData::createPin(\n                nonce: $request-\u003einput('nonce'),\n                encryptedPin: $request-\u003einput('encrypted_pin')\n            ),\n            'otp' =\u003e AuthorizationData::createOtp(\n                code: $request-\u003einput('code')\n            ),\n            default =\u003e throw new \\InvalidArgumentException('Invalid authorization type'),\n        };\n\n        $charge = Flutterwave::directCharge()-\u003eupdateChargeAuthorization(\n            chargeId: $chargeId,\n            authorizationData: $authorization\n        );\n\n        return response()-\u003ejson(['charge' =\u003e $charge]);\n    }\n\n    public function status(string $chargeId)\n    {\n        $status = Flutterwave::directCharge()-\u003estatus($chargeId);\n\n        return response()-\u003ejson(['charge' =\u003e $status]);\n    }\n}\n```\n\n---\n\n### Complete Payment Flow Example\n\nHere's how to implement a complete payment flow using the Vue components together:\n\n```vue\n\u003cscript setup lang=\"ts\"\u003e\nimport { ref } from 'vue';\nimport PaymentForm from '@/vendor/flutterwave/components/PaymentForm.vue';\nimport PinInput from '@/vendor/flutterwave/components/PinInput.vue';\nimport OtpInput from '@/vendor/flutterwave/components/OtpInput.vue';\nimport PaymentStatus from '@/vendor/flutterwave/components/PaymentStatus.vue';\n\nconst props = defineProps\u003c{\n  encryptionKey: string;\n  amount: number;\n  currency: string;\n}\u003e();\n\n// Payment flow state\ntype Step = 'form' | 'pin' | 'otp' | 'status' | 'redirect';\nconst currentStep = ref\u003cStep\u003e('form');\nconst chargeId = ref\u003cstring\u003e('');\nconst redirectUrl = ref\u003cstring\u003e('');\n\n// Handle events from PaymentForm\nfunction onRequiresPin(id: string) {\n  chargeId.value = id;\n  currentStep.value = 'pin';\n}\n\nfunction onRequiresOtp(id: string) {\n  chargeId.value = id;\n  currentStep.value = 'otp';\n}\n\nfunction onRequiresRedirect(url: string, id: string) {\n  chargeId.value = id;\n  redirectUrl.value = url;\n  currentStep.value = 'redirect';\n}\n\n// Handle PIN/OTP submission\nasync function onPinSubmit(data: { nonce: string; encrypted_pin: string }) {\n  const response = await fetch(`/api/flutterwave/charges/${chargeId.value}/authorize`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ type: 'pin', ...data }),\n  });\n  const result = await response.json();\n  handleChargeResult(result.charge);\n}\n\nasync function onOtpSubmit(otp: string) {\n  const response = await fetch(`/api/flutterwave/charges/${chargeId.value}/authorize`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({ type: 'otp', code: otp }),\n  });\n  const result = await response.json();\n  handleChargeResult(result.charge);\n}\n\nfunction handleChargeResult(charge: any) {\n  if (charge.status === 'succeeded') {\n    currentStep.value = 'status';\n  } else if (charge.status === 'requires_action') {\n    const action = charge.next_action?.type;\n    if (action === 'requires_otp') currentStep.value = 'otp';\n    else if (action === 'redirect_url') {\n      redirectUrl.value = charge.next_action.redirect_url;\n      currentStep.value = 'redirect';\n    }\n  }\n}\n\nfunction onSuccess() {\n  window.location.href = '/payment/success';\n}\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cdiv class=\"payment-container\"\u003e\n    \u003c!-- Step 1: Payment Form --\u003e\n    \u003cPaymentForm\n      v-if=\"currentStep === 'form'\"\n      :amount=\"amount\"\n      :currency=\"currency\"\n      :encryption-key=\"encryptionKey\"\n      @success=\"onSuccess\"\n      @requires-pin=\"onRequiresPin\"\n      @requires-otp=\"onRequiresOtp\"\n      @requires-redirect=\"onRequiresRedirect\"\n    /\u003e\n\n    \u003c!-- Step 2a: PIN Input --\u003e\n    \u003cPinInput\n      v-else-if=\"currentStep === 'pin'\"\n      :charge-id=\"chargeId\"\n      :encryption-key=\"encryptionKey\"\n      @submit=\"onPinSubmit\"\n      @cancel=\"currentStep = 'form'\"\n    /\u003e\n\n    \u003c!-- Step 2b: OTP Input --\u003e\n    \u003cOtpInput\n      v-else-if=\"currentStep === 'otp'\"\n      :charge-id=\"chargeId\"\n      @submit=\"onOtpSubmit\"\n      @cancel=\"currentStep = 'form'\"\n    /\u003e\n\n    \u003c!-- Step 2c: Redirect Required --\u003e\n    \u003cdiv v-else-if=\"currentStep === 'redirect'\" class=\"redirect-notice\"\u003e\n      \u003cp\u003eYou need to complete authorization on your bank's page.\u003c/p\u003e\n      \u003cbutton @click=\"window.location.href = redirectUrl\"\u003eContinue to Authorization\u003c/button\u003e\n    \u003c/div\u003e\n\n    \u003c!-- Step 3: Payment Status --\u003e\n    \u003cPaymentStatus\n      v-else-if=\"currentStep === 'status'\"\n      :charge-id=\"chargeId\"\n      :start-polling=\"true\"\n      @success=\"onSuccess\"\n    /\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\n---\n\n### Detailed Props \u0026 Events Reference\n\n#### PaymentForm Props\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `amount` | `number` | *required* | Payment amount in minor units |\n| `currency` | `string` | `'TZS'` | 3-letter ISO currency code |\n| `reference` | `string` | auto-generated | Unique payment reference |\n| `redirectUrl` | `string` | `''` | Callback URL after 3DS authorization |\n| `customer` | `object` | `{}` | Pre-filled customer data |\n| `meta` | `object` | `{}` | Custom metadata to attach |\n| `encryptionKey` | `string` | *required* | Flutterwave encryption key |\n| `labels` | `object` | `{}` | Custom label overrides for i18n |\n\n#### PaymentForm Events\n\n| Event | Payload | Description |\n|-------|---------|-------------|\n| `success` | `DirectChargeResponse` | Payment completed successfully |\n| `failed` | `DirectChargeResponse` | Payment failed/cancelled/timeout |\n| `error` | `string` | Error message for display |\n| `requires-pin` | `chargeId: string` | Card requires PIN |\n| `requires-otp` | `chargeId: string` | Card requires OTP |\n| `requires-redirect` | `url, chargeId` | Redirect needed for 3DS |\n\n#### PinInput Props\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `chargeId` | `string` | *required* | Charge ID |\n| `pinLength` | `number` | `4` | PIN digit count |\n| `encryptionKey` | `string` | *required* | Encryption key |\n| `labels` | `object` | `{}` | Label overrides |\n\n#### OtpInput Props\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `chargeId` | `string` | *required* | Charge ID |\n| `otpLength` | `number` | `6` | OTP digit count |\n| `maskedPhone` | `string` | `''` | Phone to display |\n| `labels` | `object` | `{}` | Label overrides |\n\n#### PaymentStatus Props\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `chargeId` | `string` | *required* | Charge ID to monitor |\n| `startPolling` | `boolean` | `false` | Start polling on mount |\n| `pollInterval` | `number` | `3000` | Poll interval (ms) |\n| `maxPolls` | `number` | `60` | Max attempts before timeout |\n\n---\n\n### useFlutterwave Composable\n\nFor custom payment implementations, use the `useFlutterwave` composable:\n\n```typescript\nimport { useFlutterwave } from '@/vendor/flutterwave/composables/useFlutterwave';\n\nconst {\n  // Reactive state\n  processing,        // Ref\u003cboolean\u003e - true during API calls\n  error,             // Ref\u003cstring | null\u003e - error message\n  charge,            // Ref\u003cDirectChargeResponse | null\u003e\n  \n  // Card form state (use with v-model)\n  cardNumber,        // Ref\u003cstring\u003e\n  expiryMonth,       // Ref\u003cstring\u003e\n  expiryYear,        // Ref\u003cstring\u003e\n  cvv,               // Ref\u003cstring\u003e\n  \n  // Computed\n  cardBrand,         // 'visa', 'mastercard', etc.\n  isFormValid,       // All card fields valid\n  isSuccessful,      // Charge succeeded\n  isFailed,          // Charge failed\n  \n  // Methods\n  createCharge,      // (payload) =\u003e Promise\u003cDirectChargeResponse\u003e\n  submitPin,         // (chargeId, pin) =\u003e Promise\u003cDirectChargeResponse\u003e\n  submitOtp,         // (chargeId, otp) =\u003e Promise\u003cDirectChargeResponse\u003e\n  checkStatus,       // (chargeId) =\u003e Promise\u003cDirectChargeResponse\u003e\n  resetForm,         // () =\u003e void\n} = useFlutterwave({\n  encryptionKey: 'your-key',\n  apiEndpoint: '/api/flutterwave', // optional\n});\n```\n\n---\n\n### Styling \u0026 Customization\n\nAll components use scoped CSS with `.flw-` prefixed class names.\n\n**Key CSS Classes:**\n\n| Class | Element |\n|-------|---------|\n| `.flw-payment-form` | Form container |\n| `.flw-input` | Input fields |\n| `.flw-btn-primary` | Primary buttons |\n| `.flw-alert-error` | Error messages |\n| `.flw-pin-box` | PIN digit inputs |\n| `.flw-otp-box` | OTP digit inputs |\n\n**Override Example:**\n\n```css\n.flw-btn-primary {\n  background: linear-gradient(135deg, #your-color, #your-secondary) !important;\n}\n```\n\n\n## Localization\n\nThe package includes built-in support for multiple languages. Currently supported languages:\n\n- English (`en`) - Default\n- Swahili (`sw`)\n- French (`fr`)\n\n### Publishing Translations\n\nTo customize the translation strings, publish the language files:\n\n```bash\nphp artisan vendor:publish --tag=\"flutterwave-translations\"\n```\n\nThis will publish the files to `resources/lang/vendor/flutterwave`.\n\n### Using in Vue Components\n\nAll Vue components accept a `labels` prop to override specific text:\n\n```vue\n\u003cscript setup\u003e\nconst customLabels = {\n    pay: 'Lipa Sasa',\n    processing: 'Inachakata...'\n};\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n    \u003cPaymentForm :labels=\"customLabels\" /\u003e\n\u003c/template\u003e\n```\n\n## Charge Sessions\n\n\nCharge Sessions provide database-backed tracking of direct charge transactions. This feature is particularly useful for tracking charges that require multiple authorization steps (PIN, OTP, redirects).\n\n### Features\n\n- Automatic status updates via webhooks\n- Event-driven session creation and updates\n- Relationship tracking with User and Payment models\n- Metadata storage for custom data\n- Automatic cleanup of old sessions\n\n### Enabling Charge Sessions\n\n1. **Publish and run the migration:**\n\n```bash\nphp artisan vendor:publish --tag=\"flutterwave-migrations\"\nphp artisan migrate\n```\n\n2. **Configure in `config/flutterwave.php`:**\n\n```php\n'charge_sessions' =\u003e [\n    'enabled' =\u003e true,\n    'auto_create' =\u003e true, // Automatically create sessions on charge creation\n],\n```\n\n### Using Charge Sessions\n\n#### Automatic Creation\n\nWhen `auto_create` is enabled, sessions are automatically created when direct charges are created:\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\nuse Gowelle\\Flutterwave\\Models\\ChargeSession;\n\n$charge = Flutterwave::directCharge()-\u003ecreate([\n    'amount' =\u003e 1000,\n    'currency' =\u003e 'TZS',\n    'reference' =\u003e 'ORDER-123',\n    'customer' =\u003e [...],\n    'payment_method' =\u003e [...],\n    'user_id' =\u003e auth()-\u003eid(),        // Required for auto-create\n    'payment_id' =\u003e $payment-\u003eid,     // Required for auto-create\n]);\n\n// Session is automatically created and linked\n$session = ChargeSession::byRemoteChargeId($charge-\u003eid)-\u003efirst();\n```\n\n#### Manual Creation\n\nYou can also create sessions manually:\n\n```php\nuse Gowelle\\Flutterwave\\Models\\ChargeSession;\n\n$session = ChargeSession::create([\n    'user_id' =\u003e auth()-\u003eid(),\n    'payment_id' =\u003e $payment-\u003eid,\n    'remote_charge_id' =\u003e $charge-\u003eid,\n    'status' =\u003e $charge-\u003estatus-\u003evalue,\n    'next_action_type' =\u003e $charge-\u003enextAction-\u003etype-\u003evalue ?? null,\n    'next_action_data' =\u003e $charge-\u003enextAction-\u003edata ?? null,\n    'payment_method_type' =\u003e 'card',\n    'meta' =\u003e [\n        'order_id' =\u003e '12345',\n    ],\n]);\n```\n\n#### Querying Sessions\n\n```php\nuse Gowelle\\Flutterwave\\Models\\ChargeSession;\nuse Gowelle\\Flutterwave\\Enums\\DirectChargeStatus;\n\n// Find by remote charge ID\n$session = ChargeSession::byRemoteChargeId('charge-id')-\u003efirst();\n\n// Find pending sessions\n$pendingSessions = ChargeSession::pending()-\u003eget();\n\n// Find completed sessions\n$completedSessions = ChargeSession::completed()-\u003eget();\n\n// Find by status\n$succeededSessions = ChargeSession::withStatus(DirectChargeStatus::SUCCEEDED)-\u003eget();\n\n// Access relationships\n$user = $session-\u003euser;\n$payment = $session-\u003epayment;\n```\n\n#### Updating Sessions\n\nSessions are automatically updated via webhooks when `enabled` is true. You can also update them manually:\n\n```php\n$session-\u003eupdateStatus(DirectChargeStatus::SUCCEEDED);\n$session-\u003eupdateNextAction($nextActionData);\n$session-\u003esetMeta('custom_key', 'custom_value');\n$session-\u003esave();\n```\n\n#### Session Cleanup\n\nRun the cleanup command to remove old sessions:\n\n```bash\nphp artisan flutterwave:cleanup-sessions\n```\n\nOr schedule it in your `app/Console/Kernel.php`:\n\n```php\nprotected function schedule(Schedule $schedule)\n{\n    $schedule-\u003ecommand('flutterwave:cleanup-sessions')-\u003edaily();\n}\n```\n\n## Events \u0026 Listeners\n\nThe package dispatches Laravel events for important actions, allowing you to hook into the payment flow.\n\n### Available Events\n\n#### FlutterwaveChargeCreated\n\nDispatched when a direct charge is created:\n\n```php\nuse Gowelle\\Flutterwave\\Events\\FlutterwaveChargeCreated;\n\nEvent::listen(FlutterwaveChargeCreated::class, function (FlutterwaveChargeCreated $event) {\n    $chargeData = $event-\u003echargeData;\n    $requestData = $event-\u003erequestData;\n\n    // Create charge session, send notification, etc.\n});\n```\n\n#### FlutterwaveChargeUpdated\n\nDispatched when charge authorization is submitted:\n\n```php\nuse Gowelle\\Flutterwave\\Events\\FlutterwaveChargeUpdated;\n\nEvent::listen(FlutterwaveChargeUpdated::class, function (FlutterwaveChargeUpdated $event) {\n    $chargeData = $event-\u003echargeData;\n    $authorizationData = $event-\u003eauthorizationData;\n\n    // Update charge session, process completion, etc.\n});\n```\n\n#### FlutterwaveTransferCreated\n\nDispatched when a transfer is created (bank, mobile money, or wallet):\n\n```php\nuse Gowelle\\Flutterwave\\Events\\FlutterwaveTransferCreated;\n\nEvent::listen(FlutterwaveTransferCreated::class, function (FlutterwaveTransferCreated $event) {\n    $transferData = $event-\u003etransferData;\n\n    // Log transfer, update records, send notification, etc.\n    logger()-\u003einfo('Transfer created', [\n        'id' =\u003e $transferData-\u003eid,\n        'status' =\u003e $transferData-\u003estatus-\u003evalue,\n        'amount' =\u003e $transferData-\u003eamount,\n    ]);\n});\n```\n\n#### FlutterwaveWebhookReceived\n\nDispatched when a webhook is received and verified:\n\n```php\nuse Gowelle\\Flutterwave\\Events\\FlutterwaveWebhookReceived;\n\nEvent::listen(FlutterwaveWebhookReceived::class, function (FlutterwaveWebhookReceived $event) {\n    $eventType = $event-\u003egetEventType(); // String (backward compatible)\n    $eventTypeEnum = $event-\u003egetEventTypeEnum(); // WebhookEventType enum (recommended)\n    $transactionData = $event-\u003egetTransactionData();\n\n    // Using helper methods on the event\n    if ($event-\u003eisPaymentEvent()) {\n        // Handle payment-related webhook\n    } elseif ($event-\u003eisTransferEvent()) {\n        // Handle transfer-related webhook\n    }\n\n    // Or using the enum directly\n    if ($eventTypeEnum?-\u003eisPaymentEvent()) {\n        // Handle payment-related webhook\n    } elseif ($eventTypeEnum?-\u003eisTransferEvent()) {\n        // Handle transfer-related webhook\n    }\n\n    if ($event-\u003eisSuccessful()) {\n        // Transaction was successful\n    }\n});\n```\n\n### Built-in Listeners\n\nThe package includes built-in listeners that are automatically registered:\n\n- **CreateChargeSession** - Creates charge sessions when `auto_create` is enabled\n- **UpdateChargeSession** - Updates charge sessions when authorization is submitted\n- **UpdateChargeSessionFromWebhook** - Updates charge sessions from webhook events\n\nYou can disable these by setting the appropriate configuration options.\n\n### Custom Event Listeners\n\nCreate your own event listeners:\n\n```php\n// app/Listeners/ProcessSuccessfulPayment.php\nnamespace App\\Listeners;\n\nuse Gowelle\\Flutterwave\\Events\\FlutterwaveWebhookReceived;\n\nclass ProcessSuccessfulPayment\n{\n    public function handle(FlutterwaveWebhookReceived $event): void\n    {\n        if (!$event-\u003eisPaymentEvent() || !$event-\u003eisSuccessful()) {\n            return;\n        }\n\n        $transactionData = $event-\u003egetTransactionData();\n        $chargeId = $transactionData['id'] ?? null;\n\n        // Update your payment record, send confirmation email, etc.\n    }\n}\n```\n\nRegister in `app/Providers/EventServiceProvider.php`:\n\n```php\nuse App\\Listeners\\ProcessSuccessfulPayment;\nuse Gowelle\\Flutterwave\\Events\\FlutterwaveWebhookReceived;\n\nprotected $listen = [\n    FlutterwaveWebhookReceived::class =\u003e [\n        ProcessSuccessfulPayment::class,\n    ],\n];\n```\n\n## Webhooks\n\nThe package includes automatic webhook handling with signature verification and event dispatching.\n\n### Using the Built-in Webhook Route\n\nThe package automatically registers a webhook route at `/webhooks/flutterwave` (configurable via `FLUTTERWAVE_WEBHOOK_PATH`). This route:\n\n1. Verifies the webhook signature\n2. Dispatches the `FlutterwaveWebhookReceived` event\n3. Returns a 200 response\n\nConfigure the webhook URL in your Flutterwave dashboard to point to:\n\n```\nhttps://yourdomain.com/webhooks/flutterwave\n```\n\n### Listening to Webhook Events\n\nYou can listen to webhook events and process them based on the event type. The package provides both string-based and enum-based methods for type safety.\n\n#### Using String Event Types (Backward Compatible)\n\n```php\nuse Gowelle\\Flutterwave\\Events\\FlutterwaveWebhookReceived;\nuse Illuminate\\Support\\Facades\\Event;\n\nEvent::listen(FlutterwaveWebhookReceived::class, function (FlutterwaveWebhookReceived $event) {\n    $payload = $event-\u003epayload;\n    $eventType = $event-\u003egetEventType(); // Returns string\n    $data = $event-\u003egetTransactionData();\n\n    // Process webhook event based on type\n    match ($eventType) {\n        'charge.completed' =\u003e $this-\u003ehandleChargeCompleted($data),\n        'charge.failed' =\u003e $this-\u003ehandleChargeFailed($data),\n        'transfer.completed' =\u003e $this-\u003ehandleTransferCompleted($data),\n        default =\u003e logger()-\u003einfo('Unhandled webhook event', ['type' =\u003e $eventType]),\n    };\n});\n```\n\n#### Using WebhookEventType Enum (Recommended)\n\nFor better type safety, use the `WebhookEventType` enum:\n\n```php\nuse Gowelle\\Flutterwave\\Enums\\WebhookEventType;\nuse Gowelle\\Flutterwave\\Events\\FlutterwaveWebhookReceived;\nuse Illuminate\\Support\\Facades\\Event;\n\nEvent::listen(FlutterwaveWebhookReceived::class, function (FlutterwaveWebhookReceived $event) {\n    $eventTypeEnum = $event-\u003egetEventTypeEnum(); // Returns WebhookEventType enum\n    $data = $event-\u003egetTransactionData();\n\n    if ($eventTypeEnum === null) {\n        logger()-\u003ewarning('Unknown webhook event type', ['payload' =\u003e $event-\u003epayload]);\n        return;\n    }\n\n    // Use enum helper methods\n    if ($eventTypeEnum-\u003eisPaymentEvent()) {\n        // Handle payment-related webhook\n        if ($eventTypeEnum-\u003eisSuccessful()) {\n            $this-\u003ehandleSuccessfulPayment($data);\n        } else {\n            $this-\u003ehandleFailedPayment($data);\n        }\n    } elseif ($eventTypeEnum-\u003eisTransferEvent()) {\n        // Handle transfer-related webhook\n        $this-\u003ehandleTransfer($data);\n    }\n\n    // Or use match with enum cases\n    match ($eventTypeEnum) {\n        WebhookEventType::CHARGE_COMPLETED =\u003e $this-\u003ehandleChargeCompleted($data),\n        WebhookEventType::CHARGE_FAILED =\u003e $this-\u003ehandleChargeFailed($data),\n        WebhookEventType::CHARGE_SUCCESSFUL =\u003e $this-\u003ehandleChargeSuccessful($data),\n        WebhookEventType::PAYMENT_COMPLETED =\u003e $this-\u003ehandlePaymentCompleted($data),\n        WebhookEventType::PAYMENT_FAILED =\u003e $this-\u003ehandlePaymentFailed($data),\n        WebhookEventType::PAYMENT_SUCCESSFUL =\u003e $this-\u003ehandlePaymentSuccessful($data),\n        WebhookEventType::TRANSFER_COMPLETED =\u003e $this-\u003ehandleTransferCompleted($data),\n    };\n});\n```\n\n#### WebhookEventType Enum\n\nThe `WebhookEventType` enum provides type-safe webhook event handling with helper methods:\n\n**Available Event Types:**\n\n- `CHARGE_COMPLETED` - Charge completed event\n- `CHARGE_FAILED` - Charge failed event\n- `CHARGE_SUCCESSFUL` - Charge successful event\n- `PAYMENT_COMPLETED` - Payment completed event\n- `PAYMENT_FAILED` - Payment failed event\n- `PAYMENT_SUCCESSFUL` - Payment successful event\n- `TRANSFER_COMPLETED` - Transfer completed event\n\n**Helper Methods:**\n\n- `fromString(?string $event): ?self` - Convert string to enum (returns null for unknown types)\n- `isPaymentEvent(): bool` - Check if event is payment-related (charge._ or payment._)\n- `isTransferEvent(): bool` - Check if event is transfer-related (transfer.\\*)\n- `isChargeEvent(): bool` - Check if event is charge-related (charge.\\*)\n- `isSuccessful(): bool` - Check if event indicates success\n\n**Example Usage:**\n\n```php\nuse Gowelle\\Flutterwave\\Enums\\WebhookEventType;\n\n// Convert string to enum\n$enum = WebhookEventType::fromString('charge.completed');\nif ($enum !== null) {\n    // Type-safe event handling\n    if ($enum-\u003eisPaymentEvent()) {\n        // Handle payment event\n    }\n}\n\n// Check event type\nif ($enum?-\u003eisSuccessful()) {\n    // Event indicates success\n}\n```\n\n### Manual Webhook Verification\n\nIf you need to verify webhooks manually (e.g., in a custom route):\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\nuse Gowelle\\Flutterwave\\Exceptions\\WebhookVerificationException;\nuse Illuminate\\Http\\Request;\n\nRoute::post('/custom-webhook', function (Request $request) {\n    try {\n        // Verify webhook signature\n        Flutterwave::webhook()-\u003everifyRequest($request);\n\n        // Get event details (string)\n        $eventType = Flutterwave::webhook()-\u003egetEventType($request);\n\n        // Get event details (enum - recommended)\n        $eventTypeEnum = Flutterwave::webhook()-\u003egetEventTypeEnum($request);\n        $data = Flutterwave::webhook()-\u003egetEventData($request);\n\n        // Process webhook\n        // ...\n\n        return response()-\u003ejson(['status' =\u003e 'success']);\n    } catch (WebhookVerificationException $e) {\n        // Invalid webhook signature\n        return response()-\u003ejson(['error' =\u003e 'Invalid signature'], 401);\n    }\n});\n```\n\n**Note:** Flutterwave sends the signature in the `flutterwave-signature` header, which is automatically handled by the `verifyRequest` method.\n\n## Error Handling\n\nAll API calls throw `FlutterwaveException` on error. The exception provides detailed information about the error:\n\n```php\nuse Gowelle\\Flutterwave\\Exceptions\\FlutterwaveException;\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\n\ntry {\n    $payment = Flutterwave::payments()-\u003eprocess($data);\n} catch (FlutterwaveException $e) {\n    // Get user-friendly message\n    $userMessage = $e-\u003egetUserFriendlyMessage();\n\n    // Check error type\n    if ($e-\u003eisValidationError()) {\n        // Handle validation error (400)\n        logger()-\u003ewarning('Validation error', ['message' =\u003e $userMessage]);\n    } elseif ($e-\u003eisAuthenticationError()) {\n        // Handle authentication error (401)\n        logger()-\u003eerror('Authentication failed', ['message' =\u003e $userMessage]);\n    } elseif ($e-\u003eisRateLimitError()) {\n        // Handle rate limit error (429)\n        logger()-\u003ewarning('Rate limit exceeded', ['message' =\u003e $userMessage]);\n    } else {\n        // Handle other API errors\n        logger()-\u003eerror('API error', ['message' =\u003e $userMessage]);\n    }\n\n    // Get technical details\n    $errorData = $e-\u003egetErrorData();\n    logger()-\u003eerror('Error details', [\n        'message' =\u003e $errorData-\u003egetMessage(),\n        'code' =\u003e $errorData-\u003egetCode(),\n        'type' =\u003e $errorData-\u003egetType(),\n    ]);\n}\n```\n\n### Error Types\n\n- **ValidationError** - Invalid request data (400)\n- **AuthenticationError** - Invalid credentials (401)\n- **RateLimitError** - Too many requests (429)\n- **ApiError** - Other API errors (500, 502, 503, etc.)\n\n## Card Encryption\n\n**Critical:** When making card charge requests, you **must encrypt** all card data using AES-256-GCM encryption before sending to Flutterwave.\n\n### Encryption Implementation Guide\n\nFor a complete encryption implementation guide with examples, security best practices, and integration patterns, see [ENCRYPTION_IMPLEMENTATION.md](ENCRYPTION_IMPLEMENTATION.md).\n\n### Quick Start - Using ChargeRequestBuilder\n\nThe simplest way to handle card encryption is using the `ChargeRequestBuilder`. It returns a type-safe `DirectChargeRequestDTO`:\n\n```php\nuse Gowelle\\Flutterwave\\Builders\\ChargeRequestBuilder;\n\n$dto = ChargeRequestBuilder::for('ORDER-123')\n    -\u003eamount(150, 'NGN')\n    -\u003ecustomer('customer@example.com', 'John', 'Doe')\n    -\u003ecard(\n        cardNumber: '5531886652142950',\n        expiryMonth: '09',\n        expiryYear: '32',\n        cvv: '564'\n    )\n    -\u003eredirectUrl('https://example.com/callback')\n    -\u003ebuild();\n\n// Convert to array for API request\n$request = $dto-\u003etoArray();\n\n// All card data is automatically encrypted and removed from plaintext\n```\n\n### Using ChargeRequestBuilder with Different Payment Methods\n\nThe builder supports multiple payment methods:\n\n**Card Payments:**\n\n```php\n$dto = ChargeRequestBuilder::for('ORDER-123')\n    -\u003eamount(150, 'NGN')\n    -\u003ecustomer('customer@example.com', 'John', 'Doe')\n    -\u003ecard('5531886652142950', '09', '32', '564')\n    -\u003eredirectUrl('https://example.com/callback')\n    -\u003ebuild();\n```\n\n**Mobile Money:**\n\n```php\n$dto = ChargeRequestBuilder::for('ORDER-123')\n    -\u003eamount(1000, 'TZS')\n    -\u003ecustomer('user@example.com', 'Jane', 'Doe', '+255712345678')\n    -\u003emobileMoney('VODACOM', '255712345678')\n    -\u003eredirectUrl('https://example.com/callback')\n    -\u003ebuild();\n```\n\n**Bank Account:**\n\n```php\n$dto = ChargeRequestBuilder::for('ORDER-123')\n    -\u003eamount(50000, 'NGN')\n    -\u003ecustomer('user@example.com', 'Bank', 'User')\n    -\u003ebankAccount('0123456789', '044')\n    -\u003eredirectUrl('https://example.com/callback')\n    -\u003ebuild();\n```\n\n**With Additional Options:**\n\n```php\n$dto = ChargeRequestBuilder::for('ORDER-123')\n    -\u003eamount(150, 'NGN')\n    -\u003ecustomer('customer@example.com', 'John', 'Doe', '+234812345678')\n    -\u003ecard('5531886652142950', '09', '32', '564')\n    -\u003eredirectUrl('https://example.com/callback')\n    -\u003emeta(['order_id' =\u003e '12345'])\n    -\u003ecustomizations('My Store', 'Complete your purchase')\n    -\u003eidempotencyKey('unique-key-' . time())\n    -\u003etraceId('trace-' . uniqid())\n    -\u003euserId(auth()-\u003eid())\n    -\u003epaymentId($payment-\u003eid)\n    -\u003ebuild();\n```\n\n### Manual Encryption\n\nFor more control, use the `EncryptionService` directly:\n\n```php\nuse Gowelle\\Flutterwave\\Support\\EncryptionService;\n\n$encryptionService = new EncryptionService(config('flutterwave.encryption_key'));\n\n$encryptedCard = $encryptionService-\u003eencryptCardData([\n    'card_number' =\u003e '5531886652142950',\n    'expiry_month' =\u003e '09',\n    'expiry_year' =\u003e '32',\n    'cvv' =\u003e '564',\n]);\n\n// Returns: [\n//     'nonce' =\u003e 'random_12_char_nonce',\n//     'encrypted_card_number' =\u003e 'base64_encoded_ciphertext',\n//     'encrypted_expiry_month' =\u003e 'base64_encoded_ciphertext',\n//     'encrypted_expiry_year' =\u003e 'base64_encoded_ciphertext',\n//     'encrypted_cvv' =\u003e 'base64_encoded_ciphertext',\n// ]\n```\n\n### Type-Safe DTOs\n\nThe `ChargeRequestBuilder` returns a `DirectChargeRequestDTO` for type safety:\n\n```php\nuse Gowelle\\Flutterwave\\Builders\\ChargeRequestBuilder;\n\n$dto = ChargeRequestBuilder::for('ORDER-123')\n    -\u003eamount(150, 'NGN')\n    -\u003ecustomer('customer@example.com', 'John', 'Doe')\n    -\u003ecard('5531886652142950', '09', '32', '564')\n    -\u003eredirectUrl('https://example.com/callback')\n    -\u003ebuild();\n\n// Type-safe DTO with validation\n$request = $dto-\u003etoArray(); // Get array for API\n```\n\nAvailable DTOs:\n\n- **DirectChargeRequestDTO** - Direct charge orchestrator requests\n- **ChargeRequestDTO** - Traditional charge flow requests\n\n### Key Features\n\n- **AES-256-GCM Encryption** - Industry-standard authenticated encryption\n- **Automatic Nonce Generation** - Cryptographically secure 12-character nonces\n- **Input Validation** - Validates card data before encryption\n- **Base64 Encoding** - Safe transmission of binary data\n- **Zero Plaintext** - Card data never exposed in request payloads\n\n\u003e For detailed documentation including security best practices, error handling, and advanced usage, see [ENCRYPTION_IMPLEMENTATION.md](ENCRYPTION_IMPLEMENTATION.md).\n\n## Advanced Usage\n\n### Card Data Encryption (Advanced)\n\n**Critical:** When making card charge requests, you **must encrypt** all card data (card number, CVV, expiry month, expiry year) using AES-256-GCM encryption before sending the request to Flutterwave.\n\n#### Encryption Requirements\n\n1. **Retrieve your encryption key** from your Flutterwave dashboard under **API Settings**\n2. **Use AES-256-GCM encryption** with a 12-character nonce\n3. **Base64 encode** the encrypted data\n4. **Include the nonce** in your request\n\n#### Encryption Process\n\n1. Generate a cryptographically secure random 12-character nonce (alphanumeric)\n2. Encrypt each card field (card number, CVV, expiry month, expiry year) using:\n   - Algorithm: AES-256-GCM\n   - Key: Your encryption key from Flutterwave dashboard (base64 decoded)\n   - IV/Nonce: The 12-character nonce\n3. Base64 encode the encrypted result\n4. Include both the nonce and encrypted fields in your request\n\n#### Example Request Structure\n\n```php\n'payment_method' =\u003e [\n    'type' =\u003e 'card',\n    'card' =\u003e [\n        'nonce' =\u003e 'RANDOMLY_GENERATED_12_CHAR_NONCE',\n        'encrypted_card_number' =\u003e 'BASE64_ENCRYPTED_CARD_NUMBER',\n        'encrypted_cvv' =\u003e 'BASE64_ENCRYPTED_CVV',\n        'encrypted_expiry_month' =\u003e 'BASE64_ENCRYPTED_EXPIRY_MONTH',\n        'encrypted_expiry_year' =\u003e 'BASE64_ENCRYPTED_EXPIRY_YEAR',\n    ],\n]\n```\n\n#### PHP Encryption Example\n\nFor PHP, you can use OpenSSL or libsodium. Here's a basic example using OpenSSL:\n\n```php\n/**\n * Generate a cryptographically secure 12-character alphanumeric nonce\n */\nfunction generateSecureNonce(int $length = 12): string\n{\n    $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n    $charactersLength = strlen($characters);\n    $nonce = '';\n\n    // Generate cryptographically secure random bytes\n    $randomBytes = random_bytes($length);\n\n    // Map bytes to alphanumeric characters\n    for ($i = 0; $i \u003c $length; $i++) {\n        $nonce .= $characters[ord($randomBytes[$i]) % $charactersLength];\n    }\n\n    return $nonce;\n}\n\nfunction encryptCardData(string $plainText, string $encryptionKey, string $nonce): string\n{\n    $key = base64_decode($encryptionKey);\n    $iv = $nonce; // 12-character nonce\n\n    // Encrypt using AES-256-GCM\n    $encrypted = openssl_encrypt(\n        $plainText,\n        'aes-256-gcm',\n        $key,\n        OPENSSL_RAW_DATA,\n        $iv,\n        $tag\n    );\n\n    // Combine encrypted data with authentication tag\n    $encryptedWithTag = $encrypted . $tag;\n\n    // Base64 encode\n    return base64_encode($encryptedWithTag);\n}\n\n// Usage\n$encryptionKey = 'your_base64_encoded_encryption_key_from_dashboard';\n$nonce = generateSecureNonce(12); // Generate cryptographically secure 12-character nonce\n\n$encryptedCardNumber = encryptCardData('5531886652142950', $encryptionKey, $nonce);\n$encryptedCvv = encryptCardData('564', $encryptionKey, $nonce);\n$encryptedExpiryMonth = encryptCardData('09', $encryptionKey, $nonce);\n$encryptedExpiryYear = encryptCardData('32', $encryptionKey, $nonce);\n```\n\n\u003e **Reference:** For detailed encryption documentation, examples, and best practices, see the [Flutterwave Encryption Documentation](https://developer.flutterwave.com/docs/encryption).\n\n#### Error Handling\n\nIf you send unencrypted or improperly encrypted card details, Flutterwave will return a `422` error:\n\n```json\n{\n  \"status\": \"failed\",\n  \"error\": {\n    \"type\": \"CLIENT_ENCRYPTION_ERROR\",\n    \"code\": \"11100\",\n    \"message\": \"Unable to decrypt encrypted fields provided\",\n    \"validation_errors\": []\n  }\n}\n```\n\n### Idempotency Keys\n\nUse idempotency keys to safely retry requests:\n\n```php\n$charge = Flutterwave::directCharge()-\u003ecreate([\n    'amount' =\u003e 1000,\n    'currency' =\u003e 'TZS',\n    'reference' =\u003e 'ORDER-123',\n    'idempotency_key' =\u003e 'unique-key-' . time(),\n    // ... other data\n]);\n```\n\n### Trace IDs\n\nTrace IDs help track requests across systems:\n\n```php\n$charge = Flutterwave::directCharge()-\u003ecreate([\n    'amount' =\u003e 1000,\n    'currency' =\u003e 'TZS',\n    'reference' =\u003e 'ORDER-123',\n    'trace_id' =\u003e 'trace-' . uniqid(),\n    // ... other data\n]);\n```\n\n### Custom Retry Strategies\n\nThe package automatically retries on transient failures. You can customize retry behavior:\n\n```env\nFLUTTERWAVE_MAX_RETRIES=5\nFLUTTERWAVE_RETRY_DELAY=2000\n```\n\n### Rate Limiting Customization\n\nAdjust rate limiting based on your needs:\n\n```env\nFLUTTERWAVE_RATE_LIMIT_ENABLED=true\nFLUTTERWAVE_RATE_LIMIT_MAX=200\nFLUTTERWAVE_RATE_LIMIT_WINDOW=60\n```\n\n### Direct Service Access\n\nAccess services directly without the facade:\n\n```php\nuse Gowelle\\Flutterwave\\Services\\FlutterwaveDirectChargeService;\n\n$service = app(FlutterwaveDirectChargeService::class);\n$charge = $service-\u003ecreate($data);\n```\n\n### Dependency Injection\n\nInject services into your classes:\n\n```php\nuse Gowelle\\Flutterwave\\Services\\FlutterwaveDirectChargeService;\n\nclass PaymentController\n{\n    public function __construct(\n        private FlutterwaveDirectChargeService $chargeService\n    ) {}\n\n    public function process()\n    {\n        $charge = $this-\u003echargeService-\u003ecreate([...]);\n    }\n}\n```\n\n## Retry Logic\n\nThe package automatically retries failed requests with exponential backoff for:\n\n- 5xx server errors\n- 429 rate limit errors\n- 408 timeout errors\n- 503 service unavailable\n\n### Configuration\n\nConfigure retry behavior in `.env`:\n\n```env\nFLUTTERWAVE_MAX_RETRIES=3\nFLUTTERWAVE_RETRY_DELAY=1000  # milliseconds\n```\n\nThe retry delay doubles with each attempt (exponential backoff).\n\n## Rate Limiting\n\nRate limiting prevents hitting Flutterwave API quotas. It's enabled by default and limits requests per time window.\n\n### Configuration\n\n```env\nFLUTTERWAVE_RATE_LIMIT_ENABLED=true\nFLUTTERWAVE_RATE_LIMIT_MAX_REQUESTS=100\nFLUTTERWAVE_RATE_LIMIT_PER_SECONDS=60\n```\n\nWhen the limit is reached, requests will wait until the window resets or throw a `RateLimitException`.\n\n## Testing\n\nThe package is fully testable using Laravel's HTTP faking capabilities.\n\n### Running Tests\n\n```bash\nvendor/bin/pest\n```\n\n### Example Test\n\n```php\nuse Gowelle\\Flutterwave\\Facades\\Flutterwave;\nuse Illuminate\\Support\\Facades\\Http;\n\nit('creates a direct charge successfully', function () {\n    Http::fake([\n        'api.flutterwave.com/*' =\u003e Http::response([\n            'status' =\u003e 'success',\n            'data' =\u003e [\n                'id' =\u003e 'charge-123',\n                'status' =\u003e 'pending',\n                'amount' =\u003e 1000,\n                'currency' =\u003e 'TZS',\n            ],\n        ], 200),\n    ]);\n\n    $charge = Flutterwave::directCharge()-\u003ecreate([\n        'amount' =\u003e 1000,\n        'currency' =\u003e 'TZS',\n        'reference' =\u003e 'ORDER-123',\n        'customer' =\u003e [\n            'email' =\u003e 'test@example.com',\n            'name' =\u003e 'Test User',\n        ],\n        'payment_method' =\u003e [\n            'type' =\u003e 'card',\n            'card' =\u003e [\n                'number' =\u003e '5531886652142950',\n                'cvv' =\u003e '564',\n                'expiry_month' =\u003e '09',\n                'expiry_year' =\u003e '32',\n            ],\n        ],\n    ]);\n\n    expect($charge-\u003eid)-\u003etoBe('charge-123');\n    expect($charge-\u003estatus-\u003evalue)-\u003etoBe('pending');\n});\n```\n\n### Testing Webhooks\n\n```php\nuse Gowelle\\Flutterwave\\Events\\FlutterwaveWebhookReceived;\nuse Illuminate\\Support\\Facades\\Event;\n\nit('handles webhook events', function () {\n    Event::fake();\n\n    $payload = [\n        'event' =\u003e 'charge.completed',\n        'data' =\u003e [\n            'id' =\u003e 'charge-123',\n            'status' =\u003e 'successful',\n        ],\n    ];\n\n    event(new FlutterwaveWebhookReceived($payload));\n\n    Event::assertDispatched(FlutterwaveWebhookReceived::class);\n});\n```\n\n## Troubleshooting\n\n### Common Issues\n\n#### Authentication Errors\n\n**Problem:** `401 Unauthorized` errors\n\n**Solutions:**\n\n- Verify your `FLUTTERWAVE_CLIENT_ID` and `FLUTTERWAVE_CLIENT_SECRET` are correct\n- Check that credentials match your environment (staging vs production)\n- Ensure credentials haven't been rotated in Flutterwave dashboard\n\n#### Webhook Verification Failures\n\n**Problem:** Webhook signature verification fails\n\n**Solutions:**\n\n- Verify `FLUTTERWAVE_SECRET_HASH` matches your webhook secret in Flutterwave dashboard\n- Ensure the webhook route is accessible (not behind authentication)\n- Check that the `flutterwave-signature` header is being received\n\n#### Rate Limit Errors\n\n**Problem:** `429 Too Many Requests` errors\n\n**Solutions:**\n\n- Increase `FLUTTERWAVE_RATE_LIMIT_MAX_REQUESTS` if you have higher quotas\n- Implement request queuing for high-volume operations\n- Use caching for frequently accessed data (banks, networks)\n\n#### Charge Status Not Updating\n\n**Problem:** Charge sessions not updating from webhooks\n\n**Solutions:**\n\n- Verify `charge_sessions.enabled` is `true` in config\n- Check that webhook route is properly configured\n- Ensure webhook events are being received (check logs)\n- Verify database migrations have been run\n\n#### Timeout Errors\n\n**Problem:** Requests timing out\n\n**Solutions:**\n\n- Increase `FLUTTERWAVE_TIMEOUT` value\n- Check network connectivity to Flutterwave API\n- Verify firewall rules allow outbound connections\n\n### Debugging\n\nEnable detailed logging:\n\n```env\nFLUTTERWAVE_LOGGING_ENABLED=true\nFLUTTERWAVE_LOG_LEVEL=debug\nFLUTTERWAVE_LOG_REQUESTS=true\nFLUTTERWAVE_LOG_RESPONSES=true\n```\n\nCheck logs in `storage/logs/laravel.log` for detailed API interactions.\n\n### Testing in Different Environments\n\nAlways test in staging before moving to production:\n\n```env\n# Staging\nFLUTTERWAVE_ENVIRONMENT=staging\nFLUTTERWAVE_CLIENT_ID=your_staging_client_id\nFLUTTERWAVE_CLIENT_SECRET=your_staging_client_secret\n\n# Production\nFLUTTERWAVE_ENVIRONMENT=production\nFLUTTERWAVE_CLIENT_ID=your_production_client_id\nFLUTTERWAVE_CLIENT_SECRET=your_production_client_secret\n```\n\n## Static Analysis\n\nRun PHPStan for type checking:\n\n```bash\nvendor/bin/phpstan analyse --memory-limit=512M\n```\n\nFor lower-resource systems, adjust the memory limit:\n\n```bash\nvendor/bin/phpstan analyse --memory-limit=256M\n```\n\n## Code Style\n\nFormat code with Laravel Pint:\n\n```bash\nvendor/bin/pint\n```\n\n## Contributing\n\nContributions are welcome! Please ensure:\n\n1. Tests pass: `vendor/bin/pest`\n2. Code style: `vendor/bin/pint`\n3. Type safety: `vendor/bin/phpstan analyse`\n\n## License\n\nMIT License. See [LICENSE](LICENSE) file for details.\n\n## Support\n\nFor issues and questions, please visit [GitHub Issues](https://github.com/gowelle/flutterwave-php/issues).\n\n## Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for detailed version history.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgowelle%2Fflutterwave-php","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgowelle%2Fflutterwave-php","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgowelle%2Fflutterwave-php/lists"}