M-Pesa, Kenya's leading mobile money service from Safaricom, enables seamless payments on websites. While HTML provides the frontend structure, full integration requires a backend (like PHP or Node.js) for API calls to Safaricom's Daraja platform, as pure HTML can't handle secure server-side authentication or callbacks.
Prerequisites
Successful M-Pesa integration starts with preparation. Developers need a Safaricom Daraja account, consumer key/secret, and HTTPS hosting, since M-Pesa mandates secure endpoints. Basic knowledge of HTML forms, JavaScript for frontend prompts, and a server-side language is essential. Sandbox testing via developer.safaricom.co.ke simulates live transactions without real money.
Step-by-Step Integration Process
Follow these numbered steps to integrate M-Pesa's Lipa Na M-Pesa (STK Push) into an HTML-based site. This focuses on Customer-to-Business (C2B) payments, common for e-commerce.
-
Register on Daraja Portal: Visit developer.safaricom.co.ke, sign up, and create a sandbox app. Select "Lipa Na M-Pesa Online" as the product. Note your Consumer Key and Secret—these authenticate API requests. Go live later via M-Pesa for Business approval.
-
Set Up Development Environment: Ensure your server supports HTTPS (use Let's Encrypt for free SSL). Install a backend like PHP with cURL for API calls. Create folders for your project: one for HTML frontend, another for backend scripts.
-
Generate Access Token: Use your Consumer Key/Secret to fetch a token from Safaricom's OAuth endpoint. In PHP, base64-encode "consumer_key:consumer_secret" and POST to
https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials(switch to production URL later). Store the token, valid for 1 hour. -
Create HTML Payment Form: Build a simple form in HTML with fields for phone number, amount, and a "Pay Now" button. Use JavaScript to validate inputs (e.g., Kenyan phone format 2547xxxxxxxx) before submitting to backend via AJAX or form POST.
-
Implement STK Push Backend: On form submission, send a POST request to M-Pesa's
/mpesa/stkpush/v1/processrequestendpoint. Payload includes BusinessShortCode (your till number), Password (ShortCode+passkey+timestamp, base64-encoded), Timestamp, TransactionType="CustomerPayBillOnline", Amount, PartyA (phone), PartyB (ShortCode), PhoneNumber, CallBackURL, AccountReference, and TransactionDesc. -
Handle Callbacks: Set a Validation/Confirmation/Callback URL in Daraja portal. Your backend receives asynchronous JSON responses with transaction status (e.g., "Completed"). Verify ResultCode=0 for success, update database, and notify user.
-
Test in Sandbox: Use Daraja simulator or test phone numbers. Initiate payment from your HTML form; M-Pesa sends a push on phone. Enter PIN to complete. Check logs for CheckoutRequestID to track.
-
Go Live: Apply for production credentials via M-Pesa for Business. Replace sandbox URLs/keys, update ShortCode to live PayBill, and submit endpoints for approval. Test thoroughly before launch.
Sample Code Snippets
Here's practical code to illustrate. Always secure keys in environment variables, not hardcoded.
HTML Frontend (index.html)
xml
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>M-Pesa Payment</title> </head> <body> <form id="paymentForm" action="stkpush.php" method="POST"> <label>Phone Number:</label> <input type="tel" id="phone" name="phone" placeholder="2547xxxxxxxx" required><br> <label>Amount:</label> <input type="number" id="amount" name="amount" min="10" required><br> <button type="submit">Pay with M-Pesa</button> </form> <script> document.getElementById('paymentForm').addEventListener('submit', function(e) { const phone = document.getElementById('phone').value; if (!phone.startsWith('2547') || phone.length !== 12) { alert('Enter valid Kenyan phone number'); e.preventDefault(); } }); </script> </body> </html>
This form collects data and posts to backend.
PHP Backend (stkpush.php)
php
<?php // Fetch token (simplified) $consumerKey = 'your_key'; $consumerSecret = 'your_secret'; $credentials = base64_encode($consumerKey . ':' . $consumerSecret); $tokenUrl = 'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials'; $curl = curl_init($tokenUrl); curl_setopt($curl, CURLOPT_HTTPHEADER, ["Authorization: Basic $credentials"]); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($curl); $token = json_decode($response)->access_token; curl_close($curl); // STK Push $phone = $_POST['phone']; $amount = $_POST['amount']; $shortcode = 174379; // Your ShortCode $passkey = 'your_passkey'; $timestamp = date('YmdHis'); $password = base64_encode($shortcode . $passkey . $timestamp); $callback = 'https://yourdomain.com/callback.php'; $stkUrl = 'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest'; $data = [ 'BusinessShortCode' => $shortcode, 'Password' => $password, 'Timestamp' => $timestamp, 'TransactionType' => 'CustomerPayBillOnline', 'Amount' => $amount, 'PartyA' => $phone, 'PartyB' => $shortcode, 'PhoneNumber' => $phone, 'CallBackURL' => $callback, 'AccountReference' => 'Test', 'TransactionDesc' => 'Payment' ]; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $stkUrl); curl_setopt($curl, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Authorization: Bearer ' . $token ]); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data)); $response = curl_exec($curl); $info = curl_getinfo($curl); curl_close($curl); if ($info['http_code'] == 200) { echo "Payment initiated. Check phone."; } else { echo "Error: " . $response; } ?>
Adapt ShortCode/passkey from Daraja.
Callback Handler (callback.php)
php
<?php $json = file_get_contents('php://input'); $data = json_decode($json, true); if ($data['Body']['stkCallback']['ResultCode'] == 0) { // Payment successful, update DB $mpesaReceipt = $data['Body']['stkCallback']['CallbackMetadata'][1]['Value']; echo "Success: " . $mpesaReceipt; } else { echo "Failed"; } file_put_contents('callback.log', $json); // Log for debugging ?>
Common Challenges and Tips
-
Token Expiry: Refresh tokens automatically before requests.
-
HTTPS Required: Sandbox rejects HTTP.
-
Phone Format: Always use 254 format internationally.
-
Debugging: Log all responses; use Daraja simulator for edge cases.
-
Security: Never expose keys in frontend; validate callbacks with timestamps/signatures.
Security Best Practices
Use HTTPS everywhere, store sensitive data encrypted, and implement rate limiting. Validate all inputs to prevent injection. For production, register a real PayBill/Till number.
Going Beyond Basics
Extend to B2C (disbursements) or dynamic QR codes. Libraries like mpesa-php simplify code. For Kenyan businesses like yours in ICT, this boosts e-commerce conversions.