Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Mpesa Laravel Package

safaricom/mpesa

Laravel package for Safaricom M-Pesa integration, simplifying STK Push, C2B, B2C, transaction status, and callback handling. Configure credentials, initiate payments, and reconcile responses with clean, expressive PHP APIs built for Laravel applications.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Revision note (1.0.8). Upstream published 1.0.8 — "Allows parsing of .env parameters." That is a one-line note with no changelog, no dependency/compatibility statement, and no date or scope. Two conclusions follow: (a) the "permanently abandoned" framing softens to "very low cadence, unproven maintenance" — a single release after a multi-year gap is not a maintenance commitment; (b) the feature itself is a mild negative for our use case, because a transport library that reads .env directly conflicts with Laravel's config model. Net verdict is unchanged: fork, don't depend.

Architecture fit. safaricom/mpesa remains a thin, framework-agnostic PHP client over the M-Pesa Daraja REST surface (OAuth token, STK Push/Query, C2B register/simulate, B2C, B2B, reversal, balance, transaction status). That still makes it a natural anti-corruption layer — behind a first-party MpesaGateway interface in the payments domain, never called from controllers or models. It is not a Laravel package: no service provider, facade, publishable config, queue integration, or events. Everything Laravel-shaped (config, DI binding, retries, jobs, webhook routes, persistence of CheckoutRequestID/MerchantRequestID) is ours.

The 1.0.8 release adds a fourth thing that is not Laravel-shaped: an in-library .env parser. In a Laravel app this is an anti-pattern, not a convenience — env() is only meaningful inside config/ files, and after php artisan config:cache Laravel does not load .env at all. A library that resolves credentials from ambient environment state will behave inconsistently across local, CI, cached-config production, and container images shipped without a .env file. It also widens the secret-handling surface: a parser that ingests consumer secret, passkey, and initiator password from process env is one stack trace away from exposure.

Integration feasibility. Three gates now, before any code:

Gate Concern Why it matters
Dependency resolution 2019-era composer.json likely pins PHP ~7.0–7.2 and Guzzle 5/6; 1.0.8 does not advertise a bump Laravel 9+ ships Guzzle 7 and requires PHP 8.0+; composer require may fail or force an app-wide downgrade
API currency Daraja has evolved (STK, dynamic QR, Bill Manager, ratiba, new result codes) A 2019-core client may cover only the core four products and miss current fields/endpoints
Config model (new) .env parsing — is it implicit on construction or opt-in? Does it survive config:cache? Determines whether the library can be used at all in a cached-config production deployment

None is fatal; all argue for vendoring/forking under our namespace and deleting the env loader.

Technical risk register.

Risk Likelihood Impact Mitigation
Low-cadence upstream; security patches not guaranteed High High Fork into app/Services/Mpesa or internal acme/mpesa-client; we own it. One release does not reset this
Unpinned/outdated transitive deps (Guzzle, PSR) High Medium Rebuild against Guzzle 7 + PHP 8.x, or drop Guzzle and use Laravel's Http facade
In-library .env parsing (1.0.8) bypasses config() / breaks under config:cache High Medium Strip in fork; credentials only via config('services.mpesa.*'); fail-fast validation at boot
Credential handling left to caller — and now possibly auto-read from ambient env High High Secret manager + config() only; never log payloads with MSISDN or credentials; scrub exception payloads
No idempotency or callback verification primitives High High DB-level dedupe on CheckoutRequestID/TransID; verify callback source
Untested error/result-code paths Medium High Result-code map (0, 1, 1032, 1037, 2001…) → explicit domain outcomes
No retry/backoff, default timeouts Medium Medium Laravel HTTP retry policy + explicit connect/read timeouts

Key questions (answer before committing):

  1. Does 1.0.8's env parsing run implicitly on client construction or behind an explicit call, and can it be disabled?
  2. Does 1.0.8 ship any dependency or PHP-version change, or is it scoped strictly to the parser? (No changelog was published — verify against the diff, not the note.)
  3. Which Daraja products are in scope for v1, and does the client cover their current request/response schemas?
  4. What PHP/Laravel versions must we support, and does the dependency graph resolve against them?
  5. Do we have sandbox credentials and a Safaricom Daraja support contact, or is this speculative?
  6. Compliance: CBK guidance, data residency, audit retention, PCI scope for MSISDN handling — does that dictate build-vs-buy?
  7. Who owns the fork — platform team or payments squad — and what is the review cadence?
  8. Is there a maintained alternative (community Laravel M-Pesa packages, or a ~400-line in-house client) that dominates this on total cost?

Integration Approach

Stack fit. Unchanged: the package is transport, and three layers go around it.

  1. MpesaClient (forked/adapter) — normalized methods (stkPush, stkQuery, b2c, c2bRegister) returning typed DTOs, not raw arrays. The 1.0.8 env parser is deleted in the fork; the client receives a config object in its constructor and nothing else.
  2. MpesaGateway (Laravel service) — bound in a service provider, config('services.mpesa.*') driven, sandbox/production base URLs per environment, OAuth token cached with an expiry margin (never minted per request).
  3. Domain layer — InitiatePayment / HandleCallback actions, queued outbound jobs, queued callback listener, PaymentInitiated/PaymentSettled events, mpesa_transactions as reconciliation source of truth.

Migration path. Four options, ranked:

Option Effort Verdict
A. composer require upstream as-is 1–2 d Reject — dependency risk, no owner, and 1.0.8 adds a config model that fights Laravel's
B. Fork into internal package, upgrade deps, remove env parser, add tests 5–8 d Recommend — cheapest credible path, preserves vendor payload fidelity
C. Use strictly as spec, write ~400-line in-house client on Laravel Http 6–10 d Viable if scope is only STK + C2B
D. Adopt a maintained community Laravel package 3–5 d + vetting Evaluate in parallel; wins if it covers callback verification and idempotency

Recommended: B with a C-shaped boundary — the MpesaGateway interface makes swapping to a maintained package a one-class change later.

Compatibility. Pin PHP 8.1+/8.2+, Guzzle 7 (or bypass Guzzle via Http to eliminate the conflict), PSR-4 under our vendor namespace, PSR-3 logger injection. Explicitly test:

  • php artisan config:cache — the deployment mode where library-side .env reading silently yields nulls. This is now a required test case, not a nicety.
  • Container image with no .env on disk — production and CI reality.
  • TLS 1.2+, HTTPS-only callback URLs, sandbox/production host switch as config, not code.

Sequencing.

  • Week 1 — Spike (timeboxed 3 days): install in a scratch Laravel 11 app; document exact dependency failures; exercise sandbox STK Push end-to-end. New gate: confirm whether 1.0.8's env parser activates implicitly, and verify behaviour under config:cache and with no .env present. Gate: does it run at all on target stack, and does it run in a cached-config container?
  • Week 2 — Fork and harden: vendor the code, remove the env parser, upgrade dependencies, add DTOs, token cache, timeout/retry policy, result-code mapper, boot-time config validation.
  • Week 3 — Vertical slice: STK Push + callback end-to-end — initiation, persistence, callback ingestion, idempotency, reconciliation job, observability.
  • Week 4 — Breadth + hardening: C2B/B2C as scoped, callback auth, failure-path tests against mocked Daraja responses, runbook.
  • Week 5 — Cutover: production credentials, canary volume, dashboards and alert thresholds live.

Operational Impact

Maintenance. Still a permanent fork commitment, not a dependency: ~2–4 engineer-days per quarter for dependency bumps, Daraja schema drift, and security review. The 1.0.8 release should not be read as revival — a single undocumented feature after a long gap is a data point, not a cadence. Keep a named owning team and an explicit sunset/replace trigger ("if a maintained package covers STK+C2B+B2C with callback verification by Q3, migrate"). Additional standing task introduced by 1.0.8: monitor whether upstream keeps expanding env/config handling, and if so, whether any of it is worth back-porting into the fork. Default answer: no.

Support. No vendor SLA on the package. Two-tier escalation: internal on-call for our code, Safaricom Daraja support portal for platform issues (shortcodes, go-live approval, callback whitelisting, rate limits). Document the Daraja path and expected response times in the runbook — go-live approval alone can take days and must be scheduled ahead of a release, not during it. Note that 1.0.8 shipped without a changelog: treat every future release as opaque and diff it before adopting.

Scaling. The client is stateless HTTP and scales horizontally; constraints sit elsewhere:

  • Token reuse — cache the OAuth token; per-request minting throttles you.
  • Outbound throughput — queue and rate-limit STK/B2C; Daraja enforces per-shortcode limits and B2C has daily/float ceilings.
  • Callback ingest — ack in <2s and hand to a queue; slow handlers invite retries that duplicate work.
  • Reconciliation — nightly Transaction Status sweep for anything stuck PENDING; the safety net for every dropped callback.

Failure modes to design for explicitly.

Failure Symptom Handling
Env parser active in a cached-config deployment Blank/null credentials at runtime, or secrets sourced from an unexpected place Boot-time config validation that fails loudly; fork has no env loader; test under config:cache
Token expiry / 404 on token Auth errors mid-traffic Refresh-and-retry once, then fail loudly
Callback never arrives Payment stuck PENDING Scheduled Transaction Status query + timeout to terminal state
Duplicate callback Double-crediting a customer Unique index on TransID; idempotent state machine
User cancels / wrong PIN / insufficient funds Non-zero ResultCode Map to domain outcomes; never treat transport 200 as success
Gateway timeout with side effect (push actually sent) Orphaned transaction Persist the request before sending; query, don't re-push
MSISDN format drift (254… / 0… / +254…) Request rejected Normalize at the boundary, one tested helper

Ramp-up. A mid-level Laravel engineer is productive on the fork in ~3–5 days given a runbook, sandbox credentials, and one worked example. The curve is M-Pesa semantics, not PHP: result codes, shortcode/float limits, the gap between an accepted push and a completed payment, sandbox/production quirks. 1.0.8 adds a small trap for newcomers — a documented config path that looks usable but breaks under config:cache — so the runbook must state plainly that the fork does not use the library's env parsing and that all settings flow through config('services.mpesa.*'). Budget one engineer as M-Pesa SME, pair a second for bus-factor, and treat the runbook plus result-code map as the primary onboarding artifacts — they outlive the package.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums