michaelgatuma/laravel-kopokopo
Laravel package wrapping the Kopo Kopo mobile money API. Accept M-Pesa payments, send money to recipients, and process transaction webhooks with an expressive, configurable interface. Simplifies connecting your Laravel app to Kopo Kopo's payment services in Kenya.
Architecture fit. The package is a thin Laravel service provider plus facade over Kopokopo's REST API — configuration, HTTP client, credential wiring, and (claimed) webhook handling. This fits a conventional Laravel monolith with a checkout or wallet flow well: STK Push is a single call, the response hands back a reference, and the webhook arrives out-of-band. It does not fit two common shapes without work: (a) multi-tenant setups where each tenant has its own till and credentials, because configuration is global, and (b) multi-PSP orchestration, because it is a single-vendor adapter. The recommended architecture is to treat it as an adapter behind an internal PaymentGateway port (collect, disburse, status, refund) so the vendor choice stays reversible and vendor types never leak into domain code. Confirm the binding is a resolvable singleton (app(Kopokopo::class) in tinker) before building on it.
Integration feasibility. The install path is low-effort and well-documented: composer require, vendor:publish --tag=kopokopo-config, .env credentials, sandbox vs. production base URL via config, a public HTTPS webhook route in routes/api.php, and a persisted correlation reference (metadata.order_id) written before the browser redirect. Realistic effort for a working sandbox STK push plus a webhook receiver is a few days, not weeks. The unknowns are on the periphery — which Kopokopo endpoints are actually wrapped (B2C, payouts, refunds, status polling) versus which you'll hand-roll, and whether signature verification ships or must be authored.
Technical risk. Material, and it is almost entirely dependency risk, not code risk. The repository has 4 stars, a single maintainer, and a last release of January 2023 — roughly two years of no maintenance signal at this point. There is no substantive changelog even for 1.3.1, the target is Laravel 8, and the "Laravel 9+" auto-discovery claim is unverified against current Laravel/PHP. It is MIT, so forkability is real. Secondary risks are payment-domain rather than package-specific: OAuth token TTL versus long-lived queue/Octane workers (sporadic 401s), silent errors returned inside HTTP 200 bodies, strict amount (integer KES) and MSISDN (2547XXXXXXXX) formatting, HTTPS-only reachable callbacks, and sandbox behavior diverging from production. Absence of a changelog is not evidence of absence of change.
Key questions for engineering before commitment.
composer.json constraints, and do we have a green test run on our target version?Stack fit. Assumes Laravel 8 and PHP 8.x. Auto-discovery means no config/app.php registration on 9+, but this must be tested, not assumed, against our actual Laravel and PHP versions and our deployment mode (queue workers, Octane, config:cache). Pin an explicit version in composer.json — floating the constraint on an unmaintained package is how surprise breakage enters production.
Migration path. Greenfield Laravel app: no migration, just wire it. Replacing a direct Daraja or hand-rolled HTTP integration: keep the same internal port contract, implement the new adapter against it, and run both behind a feature flag until a real low-value production transaction clears end-to-end. Never expose package types (facade calls, response arrays) to controllers or jobs — controllers and jobs call the port.
Compatibility. Validate against the target matrix in CI, and specifically test the things this package touches: config:cache (keep env() only inside config/), OAuth token behavior under a long-running worker, and the raw webhook body preservation through middleware (signature verification breaks if anything re-encodes it). Use a tunnel (ngrok/Expose) for local webhook testing and confirm the tunnel preserves the raw body.
Sequencing.
tests/Fixtures/.PaymentGateway port and implement the adapter; fake at the HTTP layer (Http::fake()), not by mocking the package class.WithoutOverlapping.pending beyond N minutes — treat as a first-class path, not a fallback.Do not bump to 1.3.1 blindly: review the 1.3.0 → 1.3.1 diff and re-run contract tests. No breaking changes are indicated, but notes are empty.
Maintenance. Community-maintained, single-owner, no support contract, no SLA, no security attestation. Operate accordingly: pin the exact version, assign a named internal owner for the dependency, record fork rights and a documented fallback (fork or swap the adapter), and watch the repo for staleness. Budget for the possibility that you become the maintainer of your fork.
Support. There is no vendor support line. On-call for payment failures is yours. Runbooks must cover: webhook not arriving, duplicate webhook, pending-stuck order, 401 storm from expired tokens, and API errors hidden in 200 responses. Log every outbound API response body and every webhook payload; without that, mobile-money incidents are unresolvable after the fact.
Scaling. Outbound calls are stateless and scale with workers; the real constraints are Kopokopo's rate limits and your webhook throughput. Queue the webhook processing, throttle the endpoint, and keep webhook latency tiny. In long-lived workers and Octane, resolve the client per job or reset cached token state on JobProcessed — a cached client that outlives its OAuth token produces sporadic 401s that only appear on long-running workers. Multi-tenant scaling requires the per-tenant client factory and scoped bindings; do not set global config per request in a shared worker.
Failure modes to design for.
WithoutOverlapping.config:clear after .env edits; a typo'd secret otherwise looks like an auth bug.Ramp-up. Code ramp is genuinely small — Laravel-native, facades, DI, a readable config file; a mid-level developer can produce a working sandbox STK push in a day or two. The real ramp is payments domain literacy: idempotency, ledger design, reconciliation, and finance-ops runbooks. Budget days for integration and a defined owner with payments context for launch. The overall posture is a cheap, reversible, time-boxed spike behind an owned interface — go/no-go after the sandbox fixtures, production canary, and reconciliation path are proven.
How can I help you explore Laravel packages today?