Looking to hire Laravel developers? Try LaraJobs

laravel-flutterwave maintained by opsofts

Description
A minimal Flutterwave integration for Laravel, built directly on Laravel's own HTTP client.
Author
Opsofts
Last update
2026/08/06 16:23 (dev-main)
License
Links
Downloads
0

Comments
comments powered by Disqus

opsofts/laravel-flutterwave

A minimal Flutterwave v4 integration for Laravel, built directly on Laravel's own HTTP client (Illuminate\Support\Facades\Http). No external HTTP SDK dependency — just illuminate/support/illuminate/http, which ship with every Laravel app and are maintained on the same cadence as the framework itself.

Targets the Flutterwave v4 API, currently in public beta. v4 changed a lot from v3 — OAuth 2.0 authentication, a customer/payment-method/charge flow instead of one-shot checkout, and a new webhook signature scheme. See "Coming from v3" below if you're upgrading.

What it does

Implements the operations most apps need to collect and manage payments on v4:

  • Create a customerPOST /customers
  • Create a payment methodPOST /payment-methods (mobile money, OPay, bank transfer, USSD, or a pre-encrypted card)
  • Initiate a chargePOST /charges against a customer + payment method
  • Authorize a chargePUT /charges/{id}, for charges that come back needing a PIN, OTP, or address verification
  • Verify a chargeGET /charges/{id}
  • Refund a chargePOST /refunds, full or partial
  • Verify a webhook signature — HMAC-SHA256 the raw body with your secret hash and compare against the flutterwave-signature header

OAuth token fetching and caching (10-minute tokens, refreshed automatically) is handled for you. Anything beyond the above (transfers, subaccounts, settlements, virtual accounts, etc.) isn't included — the package stays intentionally small. Feel free to extend src/Flutterwave.php if you need more of the API surface.

Requirements

  • PHP 8.2+
  • Laravel 11, 12, or 13

Install

composer require opsofts/laravel-flutterwave
php artisan vendor:publish --tag=flutterwave-config

Set your credentials in .env. These are v4's Client ID / Client Secret, not the old v3 Public Key / Secret Key — if your dashboard still shows Public Key / Private Key / Encryption Key, click "Switch to v4 live API keys" first.

FLUTTERWAVE_CLIENT_ID=...
FLUTTERWAVE_CLIENT_SECRET=...
FLUTTERWAVE_SECRET_HASH=your-chosen-webhook-secret
FLUTTERWAVE_ENVIRONMENT=sandbox

FLUTTERWAVE_SECRET_HASH is a value you choose and enter under Settings > Webhooks in your dashboard — Flutterwave doesn't issue it.

FLUTTERWAVE_ENVIRONMENT selects the base URL (developersandbox-api.flutterwave.com for sandbox, f4bexperience.flutterwave.com for production, per Flutterwave's docs). v4 is a public beta and its infrastructure has been known to move — if requests start failing against production, check your dashboard/docs for the current base URL and set FLUTTERWAVE_BASE_URL to override it directly.

Usage

use Opsofts\LaravelFlutterwave\Facades\Flutterwave;

// 1. Create a customer
$customer = Flutterwave::createCustomer([
    'email' => $user->email,
    'name' => ['first' => $user->first_name, 'last' => $user->last_name],
]);
$customerId = $customer['data']['id'];

// 2. Create a payment method (mobile money shown here -- no encryption needed).
// For cards, encrypt the card number/expiry/CVV client-side first; see
// "What this package deliberately does not do" below.
$paymentMethod = Flutterwave::createPaymentMethod([
    'type' => 'mobile_money',
    'mobile_money' => [
        'country_code' => '234',
        'network' => 'MTN',
        'phone_number' => '9012345678',
    ],
]);
$paymentMethodId = $paymentMethod['data']['id'];

// 3. Initiate the charge
$charge = Flutterwave::initiateCharge([
    'reference' => $orderReference,
    'currency' => 'NGN',
    'customer_id' => $customerId,
    'payment_method_id' => $paymentMethodId,
    'redirect_url' => route('payment.callback'),
    'amount' => $amount,
]);
$chargeId = $charge['data']['id'];

// 4. Handle data.next_action from step 3:
//    - redirect_url        -> redirect the payer to $charge['data']['next_action']['redirect_url']['url']
//    - payment_instruction -> show $charge['data']['next_action']['payment_instruction']['note']
//    - requires_pin / requires_otp / requires_additional_fields -> collect input, then:
$result = Flutterwave::authorizeCharge($chargeId, [
    'authorization' => [
        'type' => 'otp',
        'otp' => ['code' => $otpFromCustomer],
    ],
]);

// 5. Verify before giving value -- don't trust a webhook or redirect alone
$charge = Flutterwave::verifyTransaction($chargeId);
if ($charge['data']['status'] === 'succeeded') {
    // mark the order/payment as paid
}

// Refund (full or partial)
Flutterwave::refund($chargeId, amount: $partialAmount, reason: 'Customer requested refund');

// Webhook signature verification -- do this before trusting ANY webhook payload.
// Pass the RAW request body, not the parsed array.
$isValid = Flutterwave::verifyWebhookSignature(
    rawPayload: $request->getContent(),
    signatureHeader: $request->header('flutterwave-signature'),
);

if (! $isValid) {
    abort(401);
}

Coming from v3 / this package's earlier version

If you integrated against v3 (or an earlier version of this package), note what changed:

  • Auth: static Secret Key → OAuth 2.0 client_credentials grant (Client ID + Client Secret, 10-minute access tokens). This package fetches and caches the token for you; you never handle it directly.
  • Initializing a payment: v3's single POST /v3/payments call (returning a hosted-checkout authorization_url) is replaced by a customer → payment-method → charge flow. There's no v4 hosted-checkout equivalent yet — Flutterwave has said one is coming, but it wasn't available at the time of writing. initiateCharge() plus handling next_action is the closest v4 equivalent.
  • Webhook header: verif-hash (a plain value compare) → flutterwave-signature (an HMAC-SHA256 of the raw body, base64-encoded). verifyWebhookSignature() now takes the raw payload as well as the header.
  • Refund shape: the transaction reference moved from the URL into the request body as charge_id, alongside a required reason.

What this package deliberately does not do

No card encryption. v4 requires card numbers, expiry, and CVV to be AES-256-GCM encrypted before they reach your server, using a nonce and encryption key obtained per Flutterwave's encryption guide — this happens client-side (in JS), not on your backend. This package accepts already-encrypted card fields in createPaymentMethod() but does not perform the encryption itself; doing that server-side would mean plaintext card data touching your server, which is exactly what this design avoids.

No card storage, no saved payment methods beyond what Flutterwave's payment_method_id already represents. This package only ever talks to Flutterwave's API and hands back what Flutterwave returns — persisting IDs/references on your own models is the calling application's responsibility, same as with any SDK.

Testing

Http::fake() works exactly as it does for any Laravel HTTP client usage — fake the idp.flutterwave.com/* pattern for the OAuth token call and your configured base URL for API calls, rather than hitting the real API. No custom test helpers are provided; standard Laravel HTTP testing covers this package fully.

License

MIT.