savannabits/daraja
Laravel package for Safaricom's M-Pesa Daraja API, simplifying STK Push, C2B, B2C, transaction status and reversal requests with fluent, configurable methods so you can integrate mobile payments into your PHP app quickly.
Payment method expansion into Kenya / East Africa. This package is the fastest credible path to shipping M-Pesa as a checkout option in a Laravel app: STK Push (Lipa na M-Pesa Online) for customer-initiated collection, C2B for paybill/till flows, B2C for payouts and refunds, plus status query and reversal. If your roadmap item is "accept M-Pesa," this is a build-vs-buy answer weighted heavily toward buy — the integration surface is 1–3 engineering days.
Feature scoping: STK Push first, everything else later. The package covers the full Daraja surface, but the product decision worth making explicitly is that v1 ships collection only. B2C payouts, reversals, and balance queries carry separate initiator credentials, separate Safaricom approvals, and disproportionate blast radius (a bug in reversal logic is a real refund). Sequence them as separate roadmap items with their own sign-off, not as part of the initial payment release.
Roadmap dependency: framework upgrade. Declared support is PHP 8.1 / Laravel 9, last released November 2022 — Laravel 9 is past EOL. If the product is on Laravel 8/PHP 8.0, adopting this package implies a framework upgrade. That is a real schedule item to surface in planning, not a mid-sprint surprise. On Laravel 10/11/12 it may work; "may work" has to be validated and recorded before it goes in a release commitment.
Multi-tenancy is a product question, not an engineering one. If the product roadmap includes marketplaces, platforms, multi-branch, or per-merchant shortcodes, that has to be answered before the payments data model is committed. If the client is bound as a singleton with fixed config, retrofitting per-tenant shortcodes and token caching is expensive. Decide single-shortcode vs multi-tenant now; it changes the architecture and possibly the go-to-market sequencing.
Operational scope that must be funded as product work, not assumed. Reconciliation (polling status endpoints because callbacks genuinely get lost), duplicate/out-of-order callback handling, an indeterminate transaction state for timeouts, and a manual-review queue with alerting. These aren't optional hardening — without them, the failure mode is a customer charged with no record of it. Budget them as acceptance criteria in the payments epic.
Do not treat this as a strategic bet. 12 stars, zero dependents, one primary maintainer, no release since late 2022, and a v1.0.0 that was a single "complete improvement" PR with no migration guide. That profile is fine for a pilot behind your own interface with a pinned version. It is not fine as a load-bearing dependency you never look at again. The PM decision is to accept the vendor risk knowingly, name an internal owner, and pre-approve a fork budget — the package surface is small enough that forking is cheap, and MIT licensing makes that clean.
Adopt when all of these hold:
PaymentGateway interface, pin the exact version (1.0.0, not ^1.0), and commit the published config to the repo.Look elsewhere — or budget a fork — when:
For executives: this is a cheap, contained unlock, and the cost is in paperwork, not code.
M-Pesa is non-negotiable for selling into Kenya. This package is a small MIT-licensed Laravel wrapper around Safaricom's official Daraja API — it removes the request-payload, OAuth-token, and callback plumbing, which is the fiddly part and roughly 1–3 engineering days. It caches OAuth tokens for us, which is the main reason to use it rather than hand-rolling.
The honest risk assessment: it's a small, single-maintainer package with no dependent projects and no release since late 2022. We mitigate that structurally rather than by hoping — we pin the version, we isolate every call behind our own payment interface so the vendor is replaceable in one file, and we pre-approve a fork budget because the surface is small. In exchange we get to market weeks earlier than a from-scratch Daraja client.
The real schedule driver is Safaricom: shortcode provisioning and go-live approval take weeks even after the code is done. We should start that paperwork the same week we start the technical spike, or the engineering work finishes and sits idle waiting on approval.
For engineering: adopt it, but treat v1.0.0 as an unverified rewrite and treat it as money code.
Read config/daraja.php and src/ before writing anything — the 0.2.0 → 1.0.0 jump was a single PR with no migration guide, so renamed config keys fail at runtime, not at boot. Pin 1.0.0 exactly and commit the published config.
Non-negotiables: one PaymentGateway interface with MpesaPaymentService as the only file importing the vendor namespace; persist a row with the CheckoutRequestID before sending; a single applyStatus() state machine with lockForUpdate serving both the synchronous response and the async callback; terminal states absorbing and duplicate callbacks answered with HTTP 200; every outbound call queued with bounded concurrency and backoff; a status-polling reconciliation job every 5–15 minutes treating unknown result codes as indeterminate → query, never as failed (1037 is the one that silently charges people). Normalise responses into our own DTOs so a Safaricom field rename is a one-file change, and replay recorded sandbox fixtures through Http::fake() in contract tests so we catch payload drift on any future upgrade.
Harden the callback route like the public, unauthenticated endpoint it is: no auth middleware, CSRF exception, IP allowlisting where possible, and amounts looked up from our database — never from the callback body.
How can I help you explore Laravel packages today?