abraham-flutterwave/laravel-payment
Laravel package for Flutterwave for Business v3 APIs, simplifying payments integration in Laravel apps. Supports card, account, mobile money, bank transfers, USSD, Barter, and NQR collections with quick API calls.
Architecture fit
This package is a thin, conventional Laravel integration: a service provider, a Flutterwave facade, a publishable config/flutterwave.php, a Blade view for the inline modal, and a set of pre-registered routes (checkout, callback, success, failed, cancel, webhook). It wraps Flutterwave for Business (F4B) v3 REST endpoints and exposes them as static facade calls plus convenience helpers (generateTransactionReference(), render('inline'|'standard', $payload), use('transactions'), use('webhooks')).
It fits a server-rendered Laravel monolith or a Blade-based checkout flow well. It fits poorly in three scenarios worth flagging up front:
flutterwave::modal), so a Vue/React/Next front end can't consume it directly. Those teams would use render('standard') to redirect to a hosted Flutterwave page, or bypass the package and call the API directly — at which point the package's value drops to just webhook signature verification.Flutterwave::use('webhooks') followed by $webhook->getHook() — i.e., state stashed on a container singleton and read back in a second call. Under a long-lived worker, request-scoped state on singletons is a classic cross-request leak vector. Needs validation before any Octane migration.A genuine positive: the package does not impose a schema. No migrations, no models, no transaction tables. Your order and ledger state stays yours. That is the right design for a payments wrapper and materially lowers lock-in.
Integration feasibility
Installation is a one-liner (composer require abraham-flutterwave/laravel-payment), config publish, four env vars, then a working sandbox checkout. For a competent Laravel developer, first successful sandbox payment is a 1–3 day task. Production-grade is not: realistically 2–4 weeks once you add idempotent webhook ingestion, amount/currency validation, reconciliation, refunds, and observability.
Feasibility blockers to resolve before committing:
abraham-flutterwave, but the repository resolves to a personal GitHub account (bajoski34/Laravel) with 8 stars and 0 dependents. The README's support channels point to Flutterwave DX. This is consistent with a maintainer-rebranded community package rather than a first-party deliverable. Confirm canonical status with Flutterwave DX (email/Slack in README) before it appears in an architecture decision record.vendor/ is committed to the repo. The README states this is intentional "for those who do not have composer installed." That is a reproducibility and supply-chain smell: committed vendor trees drift from composer.json, bloat clones, and defeat vulnerability scanning. It also implies the maintainer's CI is not standard.account_number / account_bank / business email if subaccount tests fail, and warns tests may fail on OTP validation "depending on whether the service is down." These tests hit the live Flutterwave API. They cannot run in your CI, which means you get zero upstream regression signal on version bumps — every upgrade is a manual sandbox regression.Technical risk register
| Risk | Severity | Why | Mitigation |
|---|---|---|---|
| Provenance / supply chain | High | Personal-account repo, no first-party confirmation, committed vendor dir | Confirm with Flutterwave DX; pin commit SHA; mirror internally |
| Bus factor ≈ 1 | High | 8 stars, 0 dependents, single maintainer | Fork under MIT; budget internal ownership |
| Webhook trust without amount validation | High | README sample validates signature + tx_ref + status only, not amount or currency |
Server-side compare amount/currency/customer to your order record before fulfilment |
| PCI scope ambiguity | Medium | FLW_ENCRYPTION_KEY implies direct card charge capability; inline modal keeps card data off your servers |
Decide SAQ A-EP (modal only) vs. full scope; do not store/transmit PANs |
| Non-hermetic test suite | Medium | No CI-grade signal; every bump is manual QA | Build your own contract tests against sandbox |
| Octane/singleton state | Medium | use()/getHook() pattern holds per-request state |
Verify or forbid Octane on payment paths |
| Route registration | Low–Med | Package registers public routes by default (configurable since 2.1.0) | Override all routes; apply auth, CSRF, rate-limit middleware explicitly |
| Refund/dispute tooling | Medium | Refunds exist in the API surface; no chargeback/dispute workflow, no console commands (listed as TODO) | Build refund ops in-app; manage disputes in Flutterwave dashboard |
| Feature gaps | Medium | TODO list still names card, transfer, subaccount, plans, momo — i.e., payouts/marketplace/subscriptions are not shipped | Scope to collections-only; plan marketplace payouts separately |
Key questions for the maintainer / vendor
bajoski34/Laravel be migrated into a Flutterwave-owned org with release signing?Illuminate\Support\Facades\Http (mockable via Http::fake) or raw Guzzle? This determines whether we can write hermetic tests.Stack fit
| Dimension | Assessment |
|---|---|
| Framework | Laravel 6+ through 12 (Laravel 12 added in 2.1.1); not validated for Lumen or Symfony standalone |
| PHP | Floor of 7.3 (EOL Nov 2021) — treat as "runs on 8.1–8.3" and verify on your runtime |
| Front end | Blade/modal-friendly; SPA requires hosted-link mode or direct API |
| Data layer | No migrations shipped — your schema, your ledger |
| Async | None shipped; queueing, retries, and reconciliation are your build |
| Observability | A dedicated flutterwave log channel is documented; no metrics, no health checks |
| Multi-tenancy | Weak — global config for branding, country, currency |
Migration path
If you already run flutterwavedev/flutterwave-v3, kingflamez/laravelrave, or a hand-rolled Guzzle client, this is not a drop-in replacement. Different namespaces, different facade surface, different payload assembly, different webhook verification API. Plan a parallel-run cutover per rail, not a swap.
If you are greenfield, the sequencing below is the recommended path regardless — the adapter layer is what makes the choice reversible.
Reference architecture — always via an adapter
interface PaymentGateway {
public function createCheckout(array $order): CheckoutSession;
public function verify(string $reference): PaymentResult;
public function refund(string $reference, int $amountMinor): RefundResult;
public function handleWebhook(Request $request): WebhookEvent;
}
The package's facade is static, which means any direct call site is welded to it. Wrap it behind this interface so (a) domain code never imports Flutterwave\Payments\*, (b) you can add Paystack/Stripe for non-African markets without touching order logic, and (c) a fork or replacement is a single class change. This is the highest-leverage TPM decision in the whole evaluation.
Compatibility constraints to lock down
"abraham-flutterwave/laravel-payment": "2.1.1"), not a caret range. With a low release cadence and no upstream CI signal, minor bumps should be deliberate, tested events.FLW_ENVIRONMENT is a hard boundary — mixed keys are a common cause of silent test-mode charges in production.Sequencing and gates
| Phase | Work | Exit criteria | Est. |
|---|---|---|---|
| 0. Due diligence | Confirm provenance with Flutterwave DX; audit license, tags, vendor dir; review code for HTTP client and state handling | Written approval in ADR; commit SHA pinned | 3–5 days |
| 1. Spike | Sandbox integration on a throwaway branch; card + one mobile-money rail; inline and standard modal | Successful end-to-end payment + webhook received and verified | 1 week |
| 2. Adapter | PaymentGateway interface, FlutterwaveGateway, order/transaction state machine, idempotency table |
Domain code has zero direct package imports | 1–2 weeks |
| 3. Trust boundary | Signature verification, replay/duplicate suppression, amount + currency + customer reconciliation against order, async job dispatch | Pen-test-style adversarial cases pass (wrong amount, replayed payload, forged signature) | 1 week |
| 4. Resilience | Reconciliation poller for missed webhooks, PENDING-state handling for USSD/mobile money, alerting on signature failures | Documented recovery from dropped webhook without manual intervention | 1 week |
| 5. Pilot | One market, one product line, capped volume, canary cohort | 30 days clean; zero un-reconciled transactions; MTTR measured | 4 weeks |
Gate between 1 and 2: if due diligence fails or the spike reveals un-mockable HTTP or Octane incompatibility, fall back to direct F4B v3 integration and keep only the webhook-verification logic. The package's abstraction saves days, not months — do not treat it as load-bearing.
Maintenance
Assign a named internal owner. Ongoing cost is modest but non-zero: roughly 2–4 days per quarter for version bumps, sandbox regression runs, and incident review. The maintenance signal is mixed — 2.1.1 adding Laravel 12 support shows someone is watching framework compatibility, but the TODO list (subaccounts, payouts, plans, console commands) reads as a roadmap that hasn't moved, and the vendor/-committed convention suggests non-standard release engineering. Assume you are the maintainer of last resort. MIT licensing makes an internal fork viable; budget for it in the business case rather than treating it as a contingency.
Support
Two distinct support surfaces, frequently conflated:
Build an internal runbook covering: key rotation, sandbox/production key separation, webhook replay, manual reconciliation, refund execution, and escalation contacts. That runbook is worth more than the package.
Scaling
The SDK itself is not the bottleneck; the surrounding control plane is.
verifyTransactionReference() synchronously inside the webhook handler as the README sample implies; that adds a third-party round-trip to your webhook latency and invites Flutterwave-side retries.(tx_ref, flw_id). Plan archival; this table grows with every payment forever.tx_ref.Failure modes
| Failure | Detection | Blast radius | Mitigation |
|---|---|---|---|
| Webhook never delivered | Reconciliation job finds pending orders past TTL |
Orders stuck unfulfilled | Poll verifyTransactionReference with backoff; alert on age |
| Duplicate webhook delivery | Idempotency key collision | Double fulfilment / double credit | Unique constraint on (tx_ref, flw_id) + idempotent job |
| Signature mismatch | verifySignature() returns false → 401 |
Flutterwave retries; events delayed | Alert on 401 rate; verify raw body (not parsed JSON) against header |
| Amount/currency tampering | Server-side comparison to order fails | Revenue loss on underpriced fulfilment | Hard-fail order if amount or currency ≠ expected |
| Callback races webhook | Both paths attempt fulfilment | Double processing | Single idempotent fulfilment path shared by both |
Mobile-money PENDING never resolves |
Status stuck for hours | Unfulfilled high-intent orders | Explicit pending state, customer comms, automated follow-up poll |
| Sandbox/live key mixup | Test-mode charge in production logs | Real revenue silently not collected | Environment assertion at boot; separate credentials per environment |
| Route hijack / unauth access | Unexpected POSTs to /flutterwave/payment/checkout |
Forged checkout initiations | Override routes; auth, CSRF, rate-limit middleware |
| Upstream API outage | 5xx / timeouts | Checkout unavailable | Circuit breaker, graceful degradation message, retry queue |
| Maintainer abandonment | No release for N months | Frozen framework compatibility | Fork trigger defined in advance (e.g., no release in 9 months) |
Ramp-up
Developer ramp-up is genuinely fast — Laravel devs read facades fluently and the README covers the happy path in ~200 lines. Expect 1–2 days to first payment. The real learning curve is payments domain literacy, not this package: idempotency, reconciliation, PCI scope, settlement vs. authorization, chargeback handling, and the fact that a signed webhook is a claim to be verified, not a fact. The README's sample webhook handler, which verifies signature and status but never compares the amount to the order, is the single most likely production incident source for a team that copies it.
Production-readiness checklist (definition of done before the first real naira/dollar):
Flutterwave\Payments\* imports outside itTPM recommendation: conditionally adopt for a collections-only, single-market pilot with the adapter layer as a hard prerequisite. Do not standardize it as a platform dependency, do not use it for marketplace payouts or subscription plans (both still on the TODO list), and do not adopt it at all until Flutterwave DX confirms canonical ownership. The effort saved is real but measured in days; the bus-factor and provenance risk are measured in quarters.
How can I help you explore Laravel packages today?