Skip to content

feat: PesaPal payment integration#1481

Open
AbogeJr wants to merge 3 commits into
EnAccess:mainfrom
AbogeJr:feat/pesapal-payment-provider
Open

feat: PesaPal payment integration#1481
AbogeJr wants to merge 3 commits into
EnAccess:mainfrom
AbogeJr:feat/pesapal-payment-provider

Conversation

@AbogeJr
Copy link
Copy Markdown
Contributor

@AbogeJr AbogeJr commented May 21, 2026

Brief summary of the change made

Adds a PesaPal payment provider plugin so mini-grid operators in East Africa (Kenya, Uganda, Tanzania) can accept hosted-checkout payments via PesaPal v3

docs/integrations-guide/pesapal-integration.md.

closes: #1044

Are there any other side effects of this change that we should be aware of?

Describe how you tested your changes?

  1. set up two cloudflared quick tunnels (frontend 8001, backend 8000) with APP_URL and
    MPM_BACKEND_URL pointed at the backend tunnel
  2. Enable the plugin via Settings
  3. Paste demo consumer_key / consumer_secret as provided on https://developer.pesapal.com/api3-demo-keys.txt, save. Backend calls RegisterIPN against /api/pesapal/ipn/{companyId}; the response populates ipn_id and ipn_registered_at on the credential.
  4. Open the permanent payment URL from /pesapal/overview in a new window.
  5. Submit a known meter serial + KES 10. Browser is redirected to PesaPal hosted checkout, payment completed with M-Pesa.
  6. PesaPal redirects the customer back to /#/pesapal/public/result/...
Screenshot from 2026-05-20 22-57-40 Screenshot from 2026-05-20 22-57-29 Screenshot from 2026-05-20 22-56-02 Screenshot from 2026-05-20 22-51-57 Screenshot from 2026-05-20 22-50-54 Screenshot from 2026-05-20 22-20-05

Pull Request checklist

Please confirm you have completed any of the necessary steps below.

  • Meaningful Pull Request title and description
  • Changes tested as described above
  • Added appropriate documentation for the change.
  • Created GitHub issues for any relevant followup/future enhancements if appropriate.

Copy link
Copy Markdown
Contributor

@beesaferoot beesaferoot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AbogeJr, good work with the PR. I followed the steps and was able to test payment failure flow (because I don't have an active MPesa Number)

I noted some minor improvements below.

$hash = $this->generatePermanentHash($companyId);
$token = $this->generateHashFromCompanyId($companyId);

return "/pesapal/public/payment/{$hash}?ct=".$token;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PesapalCompanyHashService has a lot in common with PaystackCompanyHashService can we extract that out into the core Services and reuse that somehow?

Comment on lines +51 to +82
public function updateCredentials(array $data): PesapalCredential {
$credential = $this->getCredentials();
$tokenService = $this->container->make(PesapalTokenService::class);

$secretsRotated = array_key_exists('consumer_key', $data)
|| array_key_exists('consumer_secret', $data);

if (array_key_exists('consumer_key', $data)) {
$data['consumer_key'] = Crypt::encrypt($data['consumer_key']);
}
if (array_key_exists('consumer_secret', $data)) {
$data['consumer_secret'] = Crypt::encrypt($data['consumer_secret']);
}

$environmentChanged = array_key_exists('environment', $data)
&& $data['environment'] !== $credential->getEnvironment();

$credential->update($data);
$credential->refresh();

if (empty($credential->getAttribute('consumer_key')) || empty($credential->getAttribute('consumer_secret'))) {
throw new \RuntimeException('Consumer key and consumer secret are required.');
}

if ($secretsRotated || $environmentChanged) {
$tokenService->forget($credential);
}

$this->ensureIpnRegistered($credential);

return $credential;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's not currently used on the Paystack plugin (which needs to be refactored) but can we use the EncryptsCredentials trait for encryting model fields. It's used everywhere else.

@beesaferoot
Copy link
Copy Markdown
Contributor

Other minor fixes to be made.

UI aligment

The warning message should be properly aligned.

Screenshot 2026-05-25 at 3 26 36 PM

Failed transactions still has status as "requested"

Failed transactions should correctly trigger the right failure routines that update the transaction status.

Screenshot 2026-05-25 at 3 30 16 PM

@AbogeJr
Copy link
Copy Markdown
Contributor Author

AbogeJr commented May 26, 2026

@beesaferoot
Noted. Thanks for the quick review. I'll ping you as soon as the changes are up

@AbogeJr
Copy link
Copy Markdown
Contributor Author

AbogeJr commented May 27, 2026

@beesaferoot
I have addressed the changes requested. Please let me know once you check it out.
image

Tested on Live as well.

image image image image image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request]: PesaPal payment integration

2 participants