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

Pesa Laravel Package

openpesa/pesa

OpenPesa Pesa is a Laravel/PHP package for integrating Tanzania's OpenPESA payment gateway, enabling mobile money collections, disbursements, and transaction status checks through a simple API.

Deep Wiki
Context7

Getting Started

Install the package and immediately inspect what you actually got:

composer require openpesa/pesa

Open these first:

  1. vendor/openpesa/pesa/README.md — authoritative payload keys and method names for this release.
  2. vendor/openpesa/pesa/composer.json — PHP/Laravel constraints and whether a service provider is auto-discovered.
  3. vendor/openpesa/pesa/config/ or src/ — config keys, client class, exception types, and whether it uses Laravel’s HTTP client or Guzzle directly.

If the package exposes a Laravel provider, publish its config:

php artisan vendor:publish --provider="OpenPesa\PesaServiceProvider"

Set credentials in .env and never commit them:

PESA_PUBLIC_KEY=
PESA_SECRET_KEY=
PESA_ENVIRONMENT=sandbox
# PESA_BASE_URL=   # if supported by your version

If there is no auto-discovered provider, bind the client yourself in AppServiceProvider using the constructor shown in the README:

$this->app->singleton(\OpenPesa\Pesa::class, function () {
    return new \OpenPesa\Pesa(config('pesa')); // adjust to actual constructor
});

Your first use case should be a sandbox collection: initiate a USSD push for a known test MSISDN, persist the raw request and response, then query the transaction status. Do this from an Artisan command or temporary route before wiring it into checkout:

$reference = 'PAY-'.Str::uuid();

$response = app(\OpenPesa\Pesa::class)->pushUSSD([
    'amount' => 1000,
    'msisdn' => '255712345678',
    'reference' => $reference,
    // remaining keys depend on the API version — copy them from the README
]);

logger()->info('pesa.push', [
    'reference' => $reference,
    'response' => $response,
]);

Do not treat a successful HTTP response as a completed payment. The first milestone is “request accepted and traceable,” not “customer charged.”

Implementation Patterns

Wrap the package behind your own gateway. Given the package’s age and small surface area, do not let OpenPesa\Pesa leak into controllers, jobs, and views. Create an application-level service or interface:

interface MobileMoneyGateway
{
    public function collect(Payment $payment): PesaResult;
    public function status(Payment $payment): PesaResult;
}

Bind the OpenPesa implementation in the container. This lets you fake it in tests, swap providers later, and centralize payload normalization.

Persist before calling the API. Create a payments record first with your own UUID reference, amount, currency, normalized MSISDN, status (pending), and request payload. Then dispatch a queued job. Store the provider response, provider transaction ID, and raw error body when it returns. This is your audit trail and reconciliation source.

Use a queued job with conservative retries. Network timeouts happen, but retrying a collection blindly can double-charge if the first request actually succeeded. Prefer:

  • One job per payment reference, with WithoutOverlapping.
  • Short backoff and low retry count.
  • On retry, query transaction status first if the provider supports it.
  • Mark the payment unknown rather than failed when the outcome is ambiguous.

Model payment status as a state machine. Suggested states: pending, processing, successful, failed, cancelled, unknown. Only transition forward from pending/processing, and make updates idempotent:

Payment::where('reference', $reference)
    ->whereIn('status', ['pending', 'processing'])
    ->update([...]);

Handle callbacks/webhooks as the primary completion signal, with polling as fallback. Exclude the callback route from CSRF, accept the raw body, log it, verify any signature the provider supports, persist it, respond 200 quickly, and process in a job. Use the provider transaction ID plus your reference for idempotency. Add a scheduled reconciliation command that queries stale pending payments.

Normalize inputs at the boundary. Validate amount as a positive integer in TZS, normalize phone numbers to the provider’s expected format (commonly 2557XXXXXXXX, no +), enforce unique merchant references, and reject unsupported currencies before calling the API.

Test at your own boundary. Unit and feature tests should use a fake MobileMoneyGateway, not the real package. For HTTP-level tests, use Http::fake() if the package uses Laravel’s HTTP client; otherwise use Guzzle’s MockHandler. Keep a small, manually run sandbox smoke test separate from CI.

Use config, not env(), in application code. Read config('pesa.*') so php artisan config:cache works. If the package itself calls env() internally, verify behavior after caching and consider forking or overriding that config.

Gotchas and Tips

  • Treat this as a thin, low-maintenance wrapper. With 29 stars and a last release in 2022, expect no guarantee of Laravel 10/11 or PHP 8.3 compatibility. Pin the version, run your test suite on your target PHP/Laravel versions, and be prepared to fork. Check composer why-not openpesa/pesa if installation conflicts.
  • HTTP 200 does not mean paid. Mobile-money APIs commonly return “request accepted.” Completion comes from a callback or status query. Build the UI around pending, not instant success.
  • Idempotency is your responsibility. Generate a unique reference per payment attempt and never reuse it. If the package does not send an idempotency key, guard retries with a status check and database locking.
  • Sandbox and production credentials are easy to mix up. Confirm base URL, keys, and shortcode/service-provider code per environment. Log the environment name—not the keys—with each request.
  • Session/token handling can expire. If the package authenticates and caches a token, verify behavior across long-running queue workers. Re-authenticate on auth errors rather than retrying forever.
  • Timeouts and TLS errors need explicit handling. Set a reasonable HTTP timeout if the package allows it. Distinguish validation errors, provider declines, auth failures, and transport failures; each needs a different user message and retry policy.
  • Inspect raw responses when debugging. Many SDKs throw generic exceptions or return arrays with nested error codes. Log the full response body with secrets redacted, plus your reference and provider transaction ID.
  • Do not log full MSISDN, secrets, or raw authorization headers. Mobile-money data is sensitive. Encrypt or mask phone numbers if stored long-term, and apply a retention policy.
  • Config cache quirks are common in older Laravel packages. After changing .env, run php artisan config:clear locally. In production, redeploy or run config:cache as part of release.
  • Webhook routes need operational care. Make them public but rate-limited, verify signatures where available, reject replays, and alert on callback failures. If the package has no signature verification, do it at your edge or in your controller.
  • Extension points are limited but sufficient: container binding, published config, your own gateway abstraction, and a fork if necessary. The package provides no migrations, UI, reconciliation, or admin tooling—those are yours.
  • Keep a manual sandbox checklist. Test successful collection, declined payment, invalid MSISDN, timeout, duplicate reference, callback replay, and status query. Automated tests should mock the provider; sandbox tests should be deliberate and low-volume.
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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle