mpesa-stk-push
Skill0 starsChanged 48 days agoMITView on GitHub
Accept M-Pesa payments in Laravel with Safaricom Daraja STK push (Lipa Na M-Pesa Online): send the PIN prompt to a phone, receive the callback, poll the status, and go live. Use when adding M-Pesa, Daraja, STK push, Paybill or Till payments, mobile money checkout in Kenya, or debugging M-Pesa callbacks, timestamps, passwords or result codes.
- Reads credentials
In this repository11 other files it gives its agents.
- bot-proof-formsSkill
- image-uploadsSkill
- inertia-admin-tablesSkill
- json-ld-structured-dataSkill
- paypal-checkoutSkill
- pre-push-checksSkill
- secrets-scannerSkill
- seo-crawlabilitySkill
- shared-network-rate-limitsSkill
- social-share-previewsSkill
- wordpress-migrationSkill
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 1 is distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 15
All files are in stubs/. Copy each into the app at the same path, dropping the .stub suffix.
- Very specific: line 30
Add require __DIR__.'/mpesa.php'.
- Very specific: line 32
Add these keys with empty values to .env.example.
- Very specific: line 50
Don't read, print or copy the contents of .env.
- Very specific: line 63
Timestamp: must be Kenya time (Africa/Nairobi) as YmdHis, whatever your app's timezone.
- Very specific: line 66
Phone numbers: must be 2547XXXXXXXX or 2541XXXXXXXX.
- Distinctive: line 82
Make sure the callback URL is public HTTPS and not behind basic auth or a login.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
M-Pesa STK push (Safaricom Daraja)
Production-tested code for taking M-Pesa payments: the customer enters their phone number, gets a PIN prompt, and your app records the result from Safaricom's callback, with a status-query fallback when the callback never arrives.
What you get
All files are in stubs/. Copy each into the app at the same path, dropping the .stub suffix.
| File | Purpose |
config/mpesa.php | Keys, short code, Paybill vs Till, callback token, limits |
app/Support/Payments/Mpesa.php | Daraja client: token caching, STK push, status query, phone normalising, result codes |
app/Http/Controllers/MpesaPaymentController.php | Start a payment, status polling endpoint, Safaricom callback |
routes/mpesa.php | The three routes. require it from routes/web.php |
app/Models/Payment.php, app/Enums/PaymentStatus.php, migration, factory | A reusable payments table with safe status transitions (shared with the paypal-checkout skill) |
resources/js/mpesa-checkout.js | Browser helper: start the payment and poll until it finishes |
tests/Feature/MpesaPaymentTest.php | 18 Pest tests covering the whole flow with Http::fake() |
Steps
- Copy the stubs. If
app/Models/Payment.phpalready exists frompaypal-checkout, keep the existing one (they're identical). - Add
require __DIR__.'/mpesa.php';to the end ofroutes/web.php. - Run
php artisan migrate. - Add these keys with empty values to
.env.example. The owner fills in their own.env:
The owner gets sandbox keys from their own app at developer.safaricom.co.ke (the sandbox short code isMPESA_ENVIRONMENT=sandbox MPESA_CONSUMER_KEY= MPESA_CONSUMER_SECRET= MPESA_SHORTCODE=174379 MPESA_PASSKEY= MPESA_TYPE=paybill MPESA_CALLBACK_TOKEN=174379, and the sandbox passkey is on the Daraja STK push simulator page). The owner generates the callback token locally withphp -r "echo bin2hex(random_bytes(20));". - Run
php artisan test --filter=Mpesa. - Build the checkout UI with
payWithMpesa()fromresources/js/mpesa-checkout.js, or post toroute('mpesa.store')and poll the returnedstatus_urlevery 4 seconds.
Agent safety
- This skill writes integration code and tests. Never start a real payment or call the live API yourself: the tests use
Http::fake(), and real M-Pesa prompts only happen when a customer pays in the running app, in sandbox until the owner switches to live. - Never ask the user to paste keys into the chat, and never write key values into any file. Add the variable names with empty values to
.env.example; the owner sets the real values in their own.envor the host's environment settings. - Don't read, print or copy the contents of
.env.
Rules that matter
- Never trust an amount from the browser for priced things. The controller takes
amountfrom the request, which is right for donations. For orders or invoices, compute the amount on the server and attach the payment to the order through thepayablemorph relation. - The callback is the source of truth, but it can't reach
localhostand is sometimes late. The status endpoint asks Daraja directly once a payment has been pending for 20 seconds. Keep both. - Check the paid amount in the callback.
CallbackMetadata.Amountsmaller than the payment means it failed, whatever the result code says. - Protect the callback with a secret in the URL (
/mpesa/callback/{token}), compared withhash_equals. Safaricom doesn't sign callbacks. Exempt that route from CSRF (the stub useswithoutMiddleware(ValidateCsrfToken::class)), and always answer{"ResultCode":0}so Safaricom stops retrying. - Completed is final.
markCompleted()also rescues a payment marked failed or abandoned (a stray cancel, a query that timed out), butmarkFailed()never overrides a completed payment. Duplicate callbacks are harmless. - Identify payments by a UUID
referencein URLs, never the numeric ID.
Daraja gotchas
- Timestamp must be Kenya time (
Africa/Nairobi) asYmdHis, whatever your app's timezone. A UTC timestamp gives "Invalid Timestamp" or a wrong password. - Password =
base64(shortcode + passkey + timestamp), with the same timestamp sent in the request. - Till vs Paybill: a Till uses
CustomerBuyGoodsOnlinewithPartyBset to the till number. That differs from the store short code used asBusinessShortCode. A Paybill usesCustomerPayBillOnlinewith the short code in both fields. - Phone numbers must be
2547XXXXXXXXor2541XXXXXXXX.Mpesa::normalizePhone()accepts07...,01...,+254...and spaces. - Whole shillings only. The amount is rounded to an integer.
AccountReferencemax 12 characters,TransactionDescmax 13. Longer values are rejected, so the client truncates them.- Status query while the customer is still deciding returns HTTP 500 with
errorCode500.001.1001. Treat that as "still pending", not an error. - Access tokens last an hour. They're cached (minus two minutes) per environment.
- Result codes: 0 paid, 1 insufficient balance, 1032 cancelled by user, 1037 timed out (phone unreachable), 2001 wrong PIN.
Mpesa::describeResult()turns them into customer-friendly text.
Testing a real prompt locally
This is for the developer to do by hand, with their own sandbox keys and phone. Safaricom must reach the callback over public HTTPS, so the developer can expose their local site through a tunnel service they already use and set MPESA_CALLBACK_URL to https://<tunnel-host>/mpesa/callback/<token>. Without a tunnel, the status-query fallback still completes sandbox payments after about 20 seconds.
Going live
- Get a Paybill or Till (through Safaricom or your bank) with Lipa Na M-Pesa Online enabled.
- On the Daraja portal, create a production app and use Go Live to link your short code. The live passkey arrives by email.
- Set
MPESA_ENVIRONMENT=live, plus the live key, secret, short code, passkey andMPESA_TYPE. - Make sure the callback URL is public HTTPS and not behind basic auth or a login.
- The owner makes one small real payment themselves and checks that the callback marked it completed with a receipt number.
Rate limits
The stub routes use simple per-IP throttles. Mobile carriers share one IP across many customers, so pair this with the shared-network-rate-limits skill, which limits prompts per phone number instead.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

