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

Laravel Pesa Laravel Package

openpesa/laravel-pesa

Simplify mobile money payments in Laravel with openpesa/laravel-pesa. Configure API credentials, send payment requests, check transaction status, and process callbacks for Tanzanian payment providers through one clean package interface.

View on GitHub
Deep Wiki
Context7

Getting Started

This is the Laravel wrapper around the OpenPesa gateway for Tanzanian mobile money (M-Pesa / Tigo Pesa / Airtel Money), so treat it as a thin HTTP client plus a config file — not a billing system. It signs requests, posts them to OpenPesa, and hands back a response object.

  1. composer require openpesa/laravel-pesa — v1.0.2 is the first release that explicitly supports Laravel 9 and PHP 8.1. If you are still on Laravel 8 / PHP 8.0, pin to v1.0.0 rather than assuming forward compatibility.
  2. Publish the config: php artisan vendor:publish --provider="OpenPesa\LaravelPesa\PesaServiceProvider" (check config/pesa.php after publishing — the key is usually pesa, not laravel-pesa).
  3. Populate .env with your OpenPesa credentials (OPENPESA_API_KEY, OPENPESA_PUBLIC_KEY, OPENPESA_ENVIRONMENT, and the service provider / shortcode identifiers). These are not M-Pesa portal credentials — they come from your OpenPesa dashboard, and mixing the two is the single most common first-day mistake.
  4. First use case: a C2B push. Take a customer's MSISDN, amount, and your own reference, hand it to the facade, and log the raw response. Do this in a tinker session against the sandbox environment before writing a single controller.

Where to look first, in order: config/pesa.php (what is configurable), the facade's underlying client class (what methods actually exist — the README lags the code), then the tests directory. v1.0.2 moved the suite to Pest, which means the tests are now a genuinely readable specification of the payload shapes each endpoint expects. Read them before trusting the docs.

Implementation Patterns

Wrap the facade, don't call it from controllers. The package returns gateway response objects/arrays whose shape is dictated by OpenPesa, not by you. Put a PesaService or a small MpesaGateway class in front of it so that when the response format shifts you change one file. Inject that wrapper; never use OpenPesa\...\Facades\Pesa; inside a controller or a Blade-adjacent class.

Initiation is async — model it as a state machine. A push returns an acknowledgement, not a payment. Persist a transactions row immediately (status = 'pending', your reference, amount, msisdn) before calling the gateway, then reconcile later. Two reconciliation paths, and you should build both:

  • Callback/webhook route: a public, CSRF-exempt POST route that receives OpenPesa's callback, looks up your transaction by reference, and transitions the state. Keep the route outside auth middleware but validate the payload against a stored record rather than trusting it — never let the callback body be the source of truth for the amount.
  • Polling fallback: a scheduled php artisan command every few minutes that queries status for transactions stuck in pending past a threshold. Callbacks get lost; this is your safety net, and it's also how you debug.

Always send your own idempotency reference. Generate a ULID/UUID per attempt, store it, and use it as the transaction reference. Retries then collapse onto the same logical payment instead of double-charging a customer. Combined with Laravel's queue retry semantics (tries, backoff), this is the difference between a resilient integration and an incident.

Queue the outbound calls. Wrap the gateway call in a job (ShouldQueue, $tries = 3, exponential backoff, failed() handler that marks the transaction failed). The gateway is a third party in a different network; do not hold an HTTP request open for it in a checkout flow.

Abstract the environment. config('pesa.environment') should drive both the base URL and the credentials, and it should be impossible to accidentally boot production credentials in a test suite. Assert this in a test: the sandbox config is what CI runs against.

Testing. With Pest now in the suite, follow the same approach in your own app: Http::fake() on the OpenPesa endpoints and assert your wrapper produces the right payload from a given transaction model. Do not hit the sandbox from your test suite — it's rate-limited and stateful.

Gotchas and Tips

  • v1.0.2 removed the artisan command that shipped in v1.0.0. PR #17 is titled "remove command." If any deploy script, cron entry, or runbook references a pesa:* command from the initial release, it will now fail with "command not found." Grep your codebase and your scheduler for it. This is the only behavior change in the release and it is undocumented in the notes beyond the changelog line.
  • The release is CI and compatibility work, not features. Pest, PHPStan, PHP CS Fixer, and more GitHub Actions, plus Dependabot bumps. Nothing here changes the API surface. Upgrading is low-risk provided you weren't using the removed command and you're on PHP 8.1+.
  • PHP 8.1 support does not mean PHP 8.2/8.3 support. The notes stop at 8.1. If your app runs 8.2+, verify the package actually installs and the suite is green before rolling it out; PHPStan passing on 8.1 tells you nothing about 8.3 deprecations.
  • Laravel 9 support was added here — so Laravel 10/11 are untested. Check illuminate/support in composer.json for the actual constraint rather than assuming. Older Laravel + this release is a combination nobody has validated.
  • Phone number format is the classic silent failure. M-Pesa in Tanzania expects 255XXXXXXXXX. A leading 0 or a + prefix may produce a gateway error that looks like an auth error in the response body. Normalize on ingest and validate with a regex before ever calling the API.
  • Amounts are integers in TZS. Don't pass a float or a formatted string; some gateways silently truncate. Store the amount as an integer column and format only at the presentation layer.
  • Config caching bites. After editing .env, run php artisan config:clear. A stale config:cache from a prior deploy will happily send sandbox credentials to production and you'll debug it for an hour.
  • Log the raw request and response, redacting the API key. The gateway's error messages are often unhelpful ("Invalid request"); the underlying payload is what tells you which field was wrong. A dedicated pesa log channel with a day or two of retention pays for itself during onboarding.
  • The README is behind the code. When the docs and the tests disagree, trust the tests. The Pest migration makes this easier than it has ever been.
  • Extension points are limited by design. This is a wrapper, not a pluggable gateway abstraction — expect to fork or subclass the client if you need a different provider. Budget for that rather than fighting it.
  • No prior assessment existed; this is the baseline. Pin to ^1.0.2, keep the wrapper boundary in place, and re-evaluate if a v2 introduces a reworked client or a Laravel 10/11 constraint.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console