itsmurumba/laravel-mpesa
A Laravel package for integrating Safaricom M-Pesa into your applications. It handles STK Push, C2B and B2C payments, transaction status queries, and callback processing behind a clean, configurable API so you can accept and send mobile money in minutes.
Architecture fit. Unchanged by this release. The package remains a thin, config-driven wrapper around Safaricom's Daraja API, which fits naturally if the consuming product is a Laravel monolith or modular monolith where M-Pesa is one payment rail among several. What it provides is the HTTP/client layer (STK Push, C2B, B2C, status/reversal-style calls), not a payment orchestration layer. The STK Push flow is inherently asynchronous — request, user prompt, callback — so the architectural value depends on how well the surrounding system handles queues, callbacks, persistence, and reconciliation. Treat the package as an adapter, not a domain: define an internal gateway contract (e.g., MpesaGateway interface, our own DTOs and exception types) and let this package sit behind it. That gives us a clean swap point if the upstream goes stale (MIT license makes a fork viable) and prevents Safaricom-specific shapes from leaking into order, billing, or ledger code. It also keeps multi-tenant concerns — per-tenant shortcodes, passkeys, callback URLs — in our config layer rather than scattered through call sites. The v2.0.0-beta.1 release does not change this picture, and the interface boundary is now more valuable than it was when the last assessment was written.
Integration feasibility. Still low friction mechanically: Composer install, Laravel auto-discovery, publishable config, facade. Two things change with this release. First, there is now a 2.x line in beta, so "install the package" is no longer an unambiguous instruction — we must choose a line and pin it. Second, the only change documented in v2.0.0-beta.1 is a CI fix enabling installable beta pre-releases from develop (PR #19). A major version bump whose changelog contains no breaking-change description is itself the finding: either the breaking changes exist and are undocumented, or the major bump was cut for packaging/CI reasons without a migration note. Both readings require us to read the diff between v1.0.9-dev.10 and v2.0.0-beta.1 before recommending either line. The feasibility work outside the package is unchanged: publicly reachable HTTPS callbacks (tunnel for local dev), per-app sandbox credentials from the Safaricom portal, and production shortcode/passkey provisioning outside our control. Verify by reading source, not the README — timeout handling, error-code mapping, retry/backoff, whether request payloads (containing phone numbers) are logged, and whether callback verification and replay protection are provided or left to us. My prior remains that callback security and idempotency are our responsibility.
Technical risk. Three clusters, one of which has materially worsened:
v1.0.9-dev.4 → v1.0.9-dev.10 → v2.0.0-beta.1 in a short window: repeated dev-tagged releases, no published release timestamps in the metadata we consume, and a major version bump with a one-line changelog. That pattern is consistent with a single maintainer iterating directly on develop and tagging as they go. Daraja itself also evolves — new endpoints appear, older behaviors get deprecated, sandbox/production drift is common — and a release process this opaque makes it hard to tell whether a given upstream change is a Daraja-tracking fix or a packaging refactor. The concrete danger: we cannot answer "what breaks if we upgrade" from the changelog, so every major is a full source diff for us.Key questions to answer before committing.
develop moved to 2.x exclusively? If 1.x is frozen, the beta becomes the only forward path, which changes the risk calculus.CheckoutRequestID/MerchantRequestID ourselves?Stack fit. Best fit is a Laravel 10/11/12 service on PHP 8.1+. Because the package spans many Laravel majors it is almost certainly a thin, largely version-agnostic wrapper — good for us (little to break on framework upgrade), and a mild signal of test depth. Pin an exact version and an explicit stability. The v2.0.0-beta.1 release exists specifically to make beta pre-releases installable from develop, which means an overly permissive minimum-stability or a dev-develop constraint in our root composer.json can silently pull a pre-release into a payments path. Lock to a stable release, keep minimum-stability: stable (using prefer-stable rather than dev where a dev constraint is unavoidable), and let Dependabot/Renovate surface updates rather than floating.
Migration path. Three scenarios now, not two:
Compatibility checks. Confirm illuminate/support and guzzlehttp/guzzle constraints resolve against our lockfile for the line we pick; confirm PHP extension requirements; confirm the facade alias and config key names don't collide; confirm any auto-registered routes are namespaced and can be disabled if we route callbacks ourselves (we probably should, so we control verification and idempotency). For the beta specifically, verify it installs cleanly under our stability settings before it appears in a Renovate PR, so a bot doesn't surprise us.
Sequencing.
v1.0.9-dev.10...v2.0.0-beta.1. Determine whether the major is cosmetic or breaking. Ask upstream for a migration note and for a statement on 1.x maintenance.CheckoutRequestID), persistence of request/response/raw callback, a timeout sweeper that reconciles via Transaction Status, and a reconciliation job for unmatched C2B payments..env on disk, PII redaction in logs.Maintenance. Modest if the package stays thin — most Laravel upgrades are a constraint bump and a smoke test. The real exposure is upstream process, and this release is evidence for it: a major version published with a one-line changelog and no published timestamp means our upgrade triage cost is dominated by source diffing, not by Composer. Budget one to two engineer-days per Laravel major for validation, plus an explicit allowance for release triage on each upstream major, plus discretionary time for patches we forward-port. Under MIT we can fork and vendor into a private repository at any time, and the case for doing so strengthens if 1.x stops receiving Daraja-drift fixes. Assign a named owner for the dependency — and that owner should be subscribed to upstream releases so a 2.0.0 stable or a 1.x security tag doesn't surface first as a Renovate PR.
Support. No vendor, no SLA, no paid tier — support is GitHub issues with low community volume, which in practice means we are the support organization. Escalation for API-side incidents goes to Safaricom Daraja support, and that path is slow; our on-call runbook must assume we can diagnose from our own logs and reconcile manually. Document Daraja escalation contacts and expected response windows as launch readiness. Do not depend on upstream responsiveness for a production incident: if the bug is in the wrapper, we patch our fork.
Scaling. Unchanged — the package is an HTTP client and doesn't constrain throughput; our queue workers, database, and Daraja's per-shortcode rate limits do. Put all outbound calls behind queued jobs with bounded concurrency, exponential backoff, and a circuit breaker so a Daraja slowdown doesn't exhaust PHP-FPM workers; set explicit connect/read timeouts (STK Push waits on a human); make the callback endpoint stateless and horizontally scalable, persisting the raw payload before any business logic runs. Watch for connection reuse if Guzzle clients are constructed per call.
Failure modes to design for.
CheckoutRequestID and make processing idempotent.dev-develop constraint or permissive stability flag pulling 2.0.0-beta.x into production; guard in CI with a lockfile diff check and an explicit deny on -dev/-beta/-alpha tags for this package.Ramp-up. A mid-level Laravel engineer is productive with the package in half a day. The domain is the long pole: shortcodes, passkeys, STK versus C2B versus B2C semantics, settlement and reconciliation, and Safaricom's error taxonomy take one to two weeks to internalize. Plan onboarding around a sandbox walkthrough, a credential-provisioning procedure owned by one person, a written runbook for stuck transactions, and a pre-production review that explicitly confirms callback verification and idempotency are in place. Add one item for the current release: a short written note in the repo on which package line we're on, why, and what the upgrade trigger is.
Recommendation. Adopt behind an internal gateway interface, pin an exact stable version, keep minimum-stability: stable, and assume ownership. The package earns its place as a convenience wrapper that saves a few days of Daraja plumbing; it does not earn a place as a load-bearing dependency without our own idempotency, reconciliation, and observability wrapped around it. Do not adopt v2.0.0-beta.1. Stay on the pinned 1.x line while it receives Daraja-drift fixes, complete the source diff and get an upstream answer on breaking changes and 1.x support, then move to 2.x once it reaches a stable tag — with the adapter's test suite as the gate, not the changelog. Given the low adoption signal and the release-cadence pattern observed here, maintain a fork-ready posture and re-evaluate at each upstream major and each of our own Laravel major upgrades.
How can I help you explore Laravel packages today?