š PHP Examples
Complete Payment Integration (Updated with Proper Account Reference)
ā ļø Critical: Account reference usage depends on account type.
Paybill accounts require merchant's account number for proper routing.
Till accounts can use order references.
Vanilla PHP - Complete Workflow
<?php
class PaynexusClient {
private $baseUrl = 'https://paynexus.mcbankske.space/api';
private $secretKey = 'your_secret_api_key_here';
public function getPaymentAccounts() {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->baseUrl . '/merchant/payment-accounts');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-API-Key: ' . $this->secretKey
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'success' => $httpCode === 200,
'data' => json_decode($response, true)['data'] ?? []
];
}
public function validatePhone($phone) {
$data = ['phone' => $phone];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->baseUrl . '/mpesa/validate-phone');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-API-Key: ' . $this->secretKey
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'success' => $httpCode === 200,
'data' => json_decode($response, true)['data'] ?? []
];
}
public function initiatePayment($paymentAccountId, $amount, $phone, $accountReference, $description = null) {
$data = [
'payment_account_id' => $paymentAccountId,
'amount' => $amount,
'phone' => $phone,
'account_reference' => $accountReference, // CRITICAL: Must be set correctly
'description' => $description ?: 'Payment via PayNexus',
'remark' => 'Website Payment'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->baseUrl . '/mpesa/payment/initiate');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-API-Key: ' . $this->secretKey
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'success' => $httpCode === 200,
'data' => json_decode($response, true)['data'] ?? []
];
}
}
// COMPLETE USAGE EXAMPLE WITH PROPER ACCOUNT REFERENCE HANDLING
$paynexus = new PaynexusClient();
// Step 1: Get payment accounts to determine account type
$accountsResponse = $paynexus->getPaymentAccounts();
if (!$accountsResponse['success']) {
die('Failed to get payment accounts: ' . json_encode($accountsResponse));
}
$mpesaAccount = null;
foreach ($accountsResponse['data'] as $account) {
if ($account['provider'] === 'mpesa') {
$mpesaAccount = $account;
break;
}
}
if (!$mpesaAccount) {
die('No M-Pesa payment account found');
}
// Step 2: Validate phone number
$phoneValidation = $paynexus->validatePhone('0798808796');
if (!$phoneValidation['success'] || !$phoneValidation['data']['valid']) {
die('Invalid phone number: ' . $phoneValidation['data']['error'] ?? 'Unknown error');
}
$normalizedPhone = $phoneValidation['data']['normalized'];
// Step 3: CRITICAL - Determine account reference based on account type
if ($mpesaAccount['type'] === 'paybill') {
// FOR PAYBILL: Use merchant's account number for proper fund routing
$accountReference = $mpesaAccount['account_number'];
if (!$accountReference) {
die('Paybill account number is required for proper routing');
}
$accountReference = substr($accountReference, 0, 12); // M-Pesa limit
echo "Using Paybill account - Account Reference: $accountReference\n";
} elseif ($mpesaAccount['type'] === 'till') {
// FOR TILL: Can use order reference (not used for routing)
$accountReference = 'ORDER_' . time();
$accountReference = substr($accountReference, 0, 12);
echo "Using Till account - Account Reference: $accountReference\n";
} else {
die('Unsupported payment account type: ' . $mpesaAccount['type']);
}
// Step 4: Initiate payment with correct account reference
$payment = $paynexus->initiatePayment(
$mpesaAccount['id'], // Payment account ID
100, // Amount in KES
$normalizedPhone,
$accountReference, // CRITICAL: Correct account reference
'Payment for website order'
);
if ($payment['success']) {
echo "ā
Payment initiated successfully!\n";
echo "š Checkout Request ID: " . $payment['data']['checkout_request_id'] . "\n";
echo "š¬ Customer message: " . $payment['data']['customer_message'] . "\n";
echo "š Account Type: " . $mpesaAccount['type'] . "\n";
echo "š Account Reference Used: " . $accountReference . "\n";
// Store checkout_request_id for status tracking
// Redirect customer to payment confirmation page
} else {
echo "ā Payment failed: " . ($payment['data']['error'] ?? 'Unknown error') . "\n";
}
?>
Laravel Integration
Service Class
// app/Services/PaynexusService.php
namespace App\Services;
use Illuminate\Support\Facades\Http;
class PaynexusService {
private $baseUrl;
private $secretKey;
public function __construct() {
$this->baseUrl = 'https://paynexus.mcbankske.space/api';
$this->secretKey = config('services.paynexus.secret_key');
}
public function initiatePayment(array $data) {
$response = Http::withHeaders([
'Content-Type' => 'application/json',
'X-API-Key' => $this->secretKey
])->post($this->baseUrl . '/mpesa/payment/initiate', $data);
return [
'success' => $response->successful(),
'data' => $response->json()
];
}
}
?>
Controller Usage - Updated with Account Reference Logic
// app/Http/Controllers/PaymentController.php
<?php
namespace App\Http\Controllers;
use App\Services\PaynexusService;
use Illuminate\Http\Request;
class PaymentController extends Controller
{
protected $paynexus;
public function __construct(PaynexusService $paynexus)
{
$this->paynexus = $paynexus;
}
public function initiate(Request $request)
{
try {
// Step 1: Get payment accounts to determine account type
$accountsResponse = $this->paynexus->getPaymentAccounts();
if (!$accountsResponse['success']) {
return response()->json([
'success' => false,
'error' => 'Failed to get payment accounts'
], 400);
}
$mpesaAccount = null;
foreach ($accountsResponse['data'] as $account) {
if ($account['provider'] === 'mpesa') {
$mpesaAccount = $account;
break;
}
}
if (!$mpesaAccount) {
return response()->json([
'success' => false,
'error' => 'No M-Pesa payment account found'
], 400);
}
// Step 2: Validate phone number
$phoneValidation = $this->paynexus->validatePhone($request->phone);
if (!$phoneValidation['success'] || !$phoneValidation['data']['valid']) {
return response()->json([
'success' => false,
'error' => 'Invalid phone number: ' . ($phoneValidation['data']['error'] ?? 'Unknown')
], 400);
}
// Step 3: CRITICAL - Determine account reference based on account type
if ($mpesaAccount['type'] === 'paybill') {
// FOR PAYBILL: Use merchant's account number for proper fund routing
$accountReference = $mpesaAccount['account_number'];
if (!$accountReference) {
return response()->json([
'success' => false,
'error' => 'Paybill account number is required for proper routing'
], 400);
}
$accountReference = substr($accountReference, 0, 12); // M-Pesa limit
} elseif ($mpesaAccount['type'] === 'till') {
// FOR TILL: Can use order reference (not used for routing)
$accountReference = $request->accountReference ?: 'ORDER_' . time();
$accountReference = substr($accountReference, 0, 12);
} else {
return response()->json([
'success' => false,
'error' => 'Unsupported payment account type: ' . $mpesaAccount['type']
], 400);
}
// Step 4: Initiate payment with correct account reference
$data = [
'payment_account_id' => $mpesaAccount['id'],
'amount' => $request->amount,
'phone' => $phoneValidation['data']['normalized'],
'account_reference' => $accountReference, // CRITICAL: Correct account reference
'description' => $request->description ?: 'Payment via PayNexus',
'remark' => 'Website Payment'
];
$result = $this->paynexus->initiatePayment($data);
return response()->json([
'success' => $result['success'],
'data' => $result['data'],
'account_type' => $mpesaAccount['type'],
'account_reference_used' => $accountReference,
'message' => $result['success'] ? 'Payment initiated successfully' : 'Payment failed'
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => 'Payment processing failed: ' . $e->getMessage()
], 500);
}
}
}
?>
š¢ JavaScript/Node.js Examples
Complete Node.js Integration (Updated with Proper Account Reference)
ā ļø Critical: Account reference usage depends on account type.
Paybill accounts require merchant's account number for proper routing.
Till accounts can use order references.
Payment Class - Complete Workflow
const https = require('https');
class PaynexusClient {
constructor(secretKey) {
this.baseUrl = 'https://paynexus.mcbankske.space/api';
this.secretKey = secretKey;
}
async getPaymentAccounts() {
return this.makeRequest('/merchant/payment-accounts', null, 'GET');
}
async validatePhone(phone) {
const data = { phone: phone };
return this.makeRequest('/mpesa/validate-phone', data);
}
async initiatePayment(paymentAccountId, amount, phone, accountReference, description = null) {
const data = {
payment_account_id: paymentAccountId,
amount: amount,
phone: phone,
account_reference: accountReference, // CRITICAL: Must be set correctly
description: description || 'Payment via PayNexus',
remark: 'Website Payment'
};
return this.makeRequest('/mpesa/payment/initiate', data);
}
makeRequest(endpoint, data = null, method = 'POST') {
return new Promise((resolve, reject) => {
let postData = '';
let path = '/api' + endpoint;
if (method === 'POST' && data) {
postData = JSON.stringify(data);
}
const options = {
hostname: 'paynexus.mcbankske.space',
path: path,
method: method,
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.secretKey,
...(method === 'POST' && postData ? { 'Content-Length': Buffer.byteLength(postData) } : {})
}
};
const req = https.request(options, (res) => {
let responseData = '';
res.on('data', (chunk) => responseData += chunk);
res.on('end', () => {
try {
const jsonResponse = JSON.parse(responseData);
resolve({
success: res.statusCode === 200,
data: jsonResponse.data || jsonResponse
});
} catch (error) {
resolve({
success: false,
error: 'Invalid JSON response'
});
}
});
});
req.on('error', reject);
if (method === 'POST' && postData) {
req.write(postData);
}
req.end();
});
}
}
// COMPLETE USAGE EXAMPLE WITH PROPER ACCOUNT REFERENCE HANDLING
async function processPayment() {
const paynexus = new PaynexusClient('your_secret_api_key_here');
try {
// Step 1: Get payment accounts to determine account type
const accountsResponse = await paynexus.getPaymentAccounts();
if (!accountsResponse.success) {
console.error('ā Failed to get payment accounts:', accountsResponse.error);
return;
}
const mpesaAccount = accountsResponse.data.find(acc => acc.provider === 'mpesa');
if (!mpesaAccount) {
console.error('ā No M-Pesa payment account found');
return;
}
// Step 2: Validate phone number
const phoneValidation = await paynexus.validatePhone('0798808796');
if (!phoneValidation.success || !phoneValidation.data.valid) {
console.error('ā Invalid phone number:', phoneValidation.data.error);
return;
}
const normalizedPhone = phoneValidation.data.normalized;
// Step 3: CRITICAL - Determine account reference based on account type
let accountReference;
if (mpesaAccount.type === 'paybill') {
// FOR PAYBILL: Use merchant's account number for proper fund routing
accountReference = mpesaAccount.account_number;
if (!accountReference) {
console.error('ā Paybill account number is required for proper routing');
return;
}
accountReference = accountReference.substring(0, 12); // M-Pesa limit
console.log('š Using Paybill account - Account Reference:', accountReference);
} else if (mpesaAccount.type === 'till') {
// FOR TILL: Can use order reference (not used for routing)
accountReference = 'ORDER_' + Date.now();
accountReference = accountReference.substring(0, 12);
console.log('š Using Till account - Account Reference:', accountReference);
} else {
console.error('ā Unsupported payment account type:', mpesaAccount.type);
return;
}
// Step 4: Initiate payment with correct account reference
const payment = await paynexus.initiatePayment(
mpesaAccount.id, // Payment account ID
100, // Amount in KES
normalizedPhone,
accountReference, // CRITICAL: Correct account reference
'Payment for website order'
);
if (payment.success) {
console.log('ā
Payment initiated successfully!');
console.log('š Checkout Request ID:', payment.data.checkout_request_id);
console.log('š¬ Customer message:', payment.data.customer_message);
console.log('š Account Type:', mpesaAccount.type);
console.log('š Account Reference Used:', accountReference);
// Store checkout_request_id for status tracking
// Show payment confirmation to user
} else {
console.error('ā Payment failed:', payment.data.error || 'Unknown error');
}
} catch (error) {
console.error('ā Payment processing error:', error.message);
}
}
// Execute the payment processing
processPayment();
Express.js Integration
Express Server
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
const PAYNEXUS_URL = 'https://paynexus.mcbankske.space/api';
const SECRET_KEY = 'your_secret_api_key_here';
app.post('/api/payment/initiate', async (req, res) => {
try {
const response = await axios.post(
`${PAYNEXUS_URL}/mpesa/payment/initiate`,
{
payment_account_id: 1,
amount: req.body.amount,
phone: req.body.phone,
account_reference: req.body.accountReference,
description: 'Payment',
remark: 'Payment'
},
{
headers: {
'Content-Type': 'application/json',
'X-API-Key': SECRET_KEY
}
}
);
res.json({
success: true,
transactionId: response.data.transaction_id,
message: 'Payment initiated'
});
} catch (error) {
res.status(400).json({
success: false,
error: error.response?.data || error.message
});
}
});
// Webhook endpoint
app.post('/webhook/mpesa', (req, res) => {
const payload = req.body;
console.log('Webhook received:', payload);
if (payload.ResultCode === 0) {
// Payment successful - update database
console.log(`Payment successful: ${payload.TransactionID}`);
}
res.json({ ResultCode: 0, ResultDesc: 'Success' });
});
app.listen(3000);
š Python Examples
Basic Python
Payment Class
import requests
import json
class PaynexusMpesa:
def __init__(self, secret_key):
self.base_url = 'https://paynexus.mcbankske.space/api'
self.secret_key = secret_key
self.headers = {
'Content-Type': 'application/json',
'X-API-Key': self.secret_key
}
def initiate_payment(self, phone, amount, account_reference):
data = {
'payment_account_id': 1,
'amount': amount,
'phone': phone,
'account_reference': account_reference,
'description': 'Payment',
'remark': 'Payment'
}
response = requests.post(
f"{self.base_url}/mpesa/payment/initiate",
headers=self.headers,
json=data
)
return {
'success': response.status_code == 200,
'data': response.json()
}
# Usage
mpesa = PaynexusMpesa('your_secret_api_key_here')
result = mpesa.initiate_payment('254712345678', 100, 'ORDER123')
if result['success']:
print(f"Payment initiated: {result['data']['transaction_id']}")
else:
print(f"Payment failed: {result['data']}")
Flask Integration
Flask App
from flask import Flask, request, jsonify
import requests
app = Flask(__name__)
PAYNEXUS_URL = 'https://paynexus.mcbankske.space/api'
SECRET_KEY = 'your_secret_api_key_here'
@app.route('/api/payment/initiate', methods=['POST'])
def initiate_payment():
data = request.get_json()
response = requests.post(
f"{PAYNEXUS_URL}/mpesa/payment/initiate",
headers={
'Content-Type': 'application/json',
'X-API-Key': SECRET_KEY
},
json={
'payment_account_id': 1,
'amount': data['amount'],
'phone': data['phone'],
'account_reference': data['accountReference'],
'description': 'Payment',
'remark': 'Payment'
}
)
if response.status_code == 200:
return jsonify({
'success': True,
'transactionId': response.json()['transaction_id']
})
return jsonify({
'success': False,
'error': response.json()
}), 400
@app.route('/webhook/mpesa', methods=['POST'])
def mpesa_webhook():
payload = request.get_json()
print(f"Webhook received: {payload}")
if payload.get('ResultCode') == 0:
# Payment successful
print(f"Payment successful: {payload.get('TransactionID')}")
return jsonify({'ResultCode': 0, 'ResultDesc': 'Success'})
if __name__ == '__main__':
app.run(debug=True)
š cURL Examples
Complete API Workflow (Updated with Proper Account Reference)
ā ļø Critical: Account reference usage depends on account type.
Paybill accounts require merchant's account number for proper routing.
Till accounts can use order references.
API Test Script - Complete Workflow
#!/bin/bash
BASE_URL="https://paynexus.mcbankske.space/api"
SECRET_KEY="your_secret_api_key_here"
PUBLIC_KEY="your_public_api_key_here"
echo "š Step 1: Health check"
curl -X GET "${BASE_URL}/health" \
-H "Content-Type: application/json"
echo -e "\nš Step 2: Get merchant info"
curl -X GET "${BASE_URL}/merchant" \
-H "Content-Type: application/json" \
-H "X-API-Key: ${PUBLIC_KEY}"
echo -e "\nš³ Step 3: Get payment accounts (CRITICAL for account reference)"
ACCOUNTS_RESPONSE=$(curl -s -X GET "${BASE_URL}/merchant/payment-accounts" \
-H "Content-Type: application/json" \
-H "X-API-Key: ${PUBLIC_KEY}")
echo "Payment accounts response:"
echo "$ACCOUNTS_RESPONSE" | jq '.'
# Extract account type and account number (requires jq)
ACCOUNT_TYPE=$(echo "$ACCOUNTS_RESPONSE" | jq -r '.data[] | select(.provider=="mpesa") | .type')
ACCOUNT_NUMBER=$(echo "$ACCOUNTS_RESPONSE" | jq -r '.data[] | select(.provider=="mpesa") | .account_number')
echo -e "\nš Account Type: $ACCOUNT_TYPE"
echo "š Account Number: $ACCOUNT_NUMBER"
echo -e "\nš Step 4: Validate phone number"
curl -X POST "${BASE_URL}/mpesa/validate-phone" \
-H "Content-Type: application/json" \
-H "X-API-Key: ${PUBLIC_KEY}" \
-d '{"phone": "254712345678"}'
echo -e "\nš° Step 5: Initiate payment with CORRECT account reference"
# CRITICAL: Set account reference based on account type
if [ "$ACCOUNT_TYPE" = "paybill" ]; then
# FOR PAYBILL: Use merchant's account number for proper fund routing
ACCOUNT_REFERENCE="$ACCOUNT_NUMBER"
echo "š Using Paybill account - Account Reference: $ACCOUNT_REFERENCE"
elif [ "$ACCOUNT_TYPE" = "till" ]; then
# FOR TILL: Can use order reference (not used for routing)
ACCOUNT_REFERENCE="ORDER_$(date +%s)"
echo "š Using Till account - Account Reference: $ACCOUNT_REFERENCE"
else
echo "ā Unknown account type: $ACCOUNT_TYPE"
exit 1
fi
# Ensure account reference is 12 characters or less
ACCOUNT_REFERENCE=$(echo "$ACCOUNT_REFERENCE" | cut -c1-12)
echo "š Final Account Reference (12 chars max): $ACCOUNT_REFERENCE"
curl -X POST "${BASE_URL}/mpesa/payment/initiate" \
-H "Content-Type: application/json" \
-H "X-API-Key: ${SECRET_KEY}" \
-d "{
\"payment_account_id\": 1,
\"amount\": 100,
\"phone\": \"254712345678\",
\"account_reference\": \"$ACCOUNT_REFERENCE\",
\"description\": \"Payment for website order\",
\"remark\": \"Website Payment\"
}"
echo -e "\nš Step 6: Check payment status (replace with actual checkout_request_id)"
curl -X POST "${BASE_URL}/mpesa/payment/status" \
-H "Content-Type: application/json" \
-H "X-API-Key: ${SECRET_KEY}" \
-d '{"transaction_id": "YOUR_CHECKOUT_REQUEST_ID"}'
echo -e "\nā
Workflow complete!"
echo "š Remember to store the checkout_request_id for status tracking"
echo "š Account reference used: $ACCOUNT_REFERENCE (Account Type: $ACCOUNT_TYPE)"
š API v2 Enhanced Examples
Batch Payment Processing
PHP - Batch Payments
<?php
class PaynexusBatch {
private $baseUrl = 'https://paynexus.mcbankske.space/api/v2';
private $secretKey = 'your_secret_api_key_here';
public function initiateBatchPayments(array $payments) {
$data = [
'payments' => array_map(function($payment) {
return [
'payment_account_id' => $payment['account_id'],
'amount' => $payment['amount'],
'phone' => $payment['phone'],
'account_reference' => $payment['reference'],
'description' => $payment['description'] ?? 'Batch payment',
];
}, $payments)
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->baseUrl . '/payments/batch');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'X-API-Key: ' . $this->secretKey,
'API-Version: v2'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'success' => $httpCode === 200,
'data' => json_decode($response, true),
'http_code' => $httpCode
];
}
}
// Usage - Process 10 payments at once
$batch = new PaynexusBatch();
$payments = [
['account_id' => 1, 'amount' => 100, 'phone' => '254712345678', 'reference' => 'ORD001'],
['account_id' => 1, 'amount' => 200, 'phone' => '254712345679', 'reference' => 'ORD002'],
// ... more payments
];
$result = $batch->initiateBatchPayments($payments);
if ($result['success']) {
echo "Batch initiated! Batch ID: " . $result['data']['batch_id'];
echo "Payments processed: " . count($result['data']['payments']);
} else {
echo "Batch failed: " . $result['data']['message'];
}
?>
Analytics Dashboard Integration
JavaScript - Real-time Analytics
class PaynexusAnalytics {
constructor(secretKey) {
this.baseUrl = 'https://paynexus.mcbankske.space/api/v2';
this.secretKey = secretKey;
}
async getPaymentAnalytics(days = 30) {
const response = await fetch(`${this.baseUrl}/analytics/payments?days=${days}`, {
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.secretKey,
'API-Version': 'v2'
}
});
return response.json();
}
async getRevenueAnalytics(days = 30) {
const response = await fetch(`${this.baseUrl}/analytics/revenue?days=${days}`, {
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.secretKey,
'API-Version': 'v2'
}
});
return response.json();
}
async getPerformanceMetrics() {
const response = await fetch(`${this.baseUrl}/analytics/performance`, {
headers: {
'Content-Type': 'application/json',
'X-API-Key': this.secretKey,
'API-Version': 'v2'
}
});
return response.json();
}
// Real-time dashboard update
async updateDashboard() {
try {
const [payments, revenue, performance] = await Promise.all([
this.getPaymentAnalytics(7),
this.getRevenueAnalytics(7),
this.getPerformanceMetrics()
]);
// Update dashboard UI
this.updateCharts(payments);
this.updateRevenueCards(revenue);
this.updatePerformanceMetrics(performance);
} catch (error) {
console.error('Analytics update failed:', error);
}
}
updateCharts(data) {
// Update payment charts with data
const chartData = data.daily_stats.map(day => ({
date: day.date,
revenue: day.revenue,
payments: day.total_payments,
success_rate: day.success_rate
}));
// Update your chart library (Chart.js, D3.js, etc.)
this.renderPaymentChart(chartData);
}
updateRevenueCards(data) {
document.getElementById('total-revenue').textContent =
`KES ${data.total_revenue.toLocaleString()}`;
document.getElementById('avg-revenue').textContent =
`KES ${data.daily_average_revenue.toLocaleString()}`;
document.getElementById('growth-rate').textContent =
`${data.revenue_growth}%`;
}
updatePerformanceMetrics(data) {
document.getElementById('avg-response-time').textContent =
`${data.api_performance.avg_response_time}ms`;
document.getElementById('success-rate').textContent =
`${data.api_performance.error_rate}%`;
document.getElementById('cache-hit-rate').textContent =
`${data.cache_performance.hit_rate}%`;
}
}
// Initialize analytics dashboard
const analytics = new PaynexusAnalytics('your_secret_api_key_here');
// Update dashboard every 30 seconds
setInterval(() => analytics.updateDashboard(), 30000);
// Initial load
analytics.updateDashboard();
Webhook Testing & Retry Management
Python - Webhook Testing
import requests
import json
from flask import Flask, request, jsonify
class PaynexusWebhookManager:
def __init__(self, secret_key):
self.base_url = 'https://paynexus.mcbankske.space/api/v2'
self.secret_key = secret_key
self.headers = {
'Content-Type': 'application/json',
'X-API-Key': self.secret_key,
'API-Version': 'v2'
}
def test_webhook(self, webhook_url, test_payload=None):
"""Test webhook endpoint with sample data"""
if test_payload is None:
test_payload = {
"ResultCode": 0,
"ResultDesc": "Success",
"TransactionID": "TEST123456",
"TransactionAmount": 1000,
"PhoneNumber": "+254712345678",
"MerchantRequestID": "TEST789"
}
response = requests.post(
f"{self.base_url}/webhooks/test",
headers=self.headers,
json={
"webhook_url": webhook_url,
"test_payload": test_payload
}
)
return {
'success': response.status_code == 200,
'data': response.json()
}
def retry_failed_webhook(self, webhook_id):
"""Retry a failed webhook delivery"""
response = requests.post(
f"{self.base_url}/webhooks/{webhook_id}/retry",
headers=self.headers
)
return {
'success': response.status_code == 200,
'data': response.json()
}
def get_webhook_status(self, webhook_id):
"""Get webhook delivery status"""
response = requests.get(
f"{self.base_url}/webhooks/{webhook_id}/status",
headers=self.headers
)
return {
'success': response.status_code == 200,
'data': response.json()
}
# Flask webhook handler with signature verification
app = Flask(__name__)
webhook_manager = PaynexusWebhookManager('your_secret_api_key_here')
@app.route('/webhook/mpesa', methods=['POST'])
def mpesa_webhook():
# Verify webhook signature
signature = request.headers.get('X-PayNexus-Signature', '')
payload = request.get_json()
if not verify_webhook_signature(payload, signature):
return jsonify({'error': 'Invalid signature'}), 401
# Process webhook payload
if payload.get('ResultCode') == 0:
# Payment successful
transaction_id = payload.get('TransactionID')
amount = payload.get('TransactionAmount')
phone = payload.get('PhoneNumber')
# Update your database
update_payment_status(transaction_id, 'completed', amount, phone)
# Send real-time notification to frontend
send_websocket_notification({
'type': 'payment_completed',
'transaction_id': transaction_id,
'amount': amount,
'phone': phone
})
print(f"Payment successful: {transaction_id} - KES {amount}")
else:
# Payment failed
transaction_id = payload.get('TransactionID')
error_desc = payload.get('ResultDesc', 'Unknown error')
update_payment_status(transaction_id, 'failed', None, None, error_desc)
print(f"Payment failed: {transaction_id} - {error_desc}")
return jsonify({'ResultCode': 0, 'ResultDesc': 'Success'})
def verify_webhook_signature(payload, signature):
"""Verify webhook signature for security"""
secret = 'your_webhook_secret'
expected_signature = hash_hmac('sha256', json.dumps(payload), secret)
return hash_equals(signature, expected_signature)
def update_payment_status(transaction_id, status, amount=None, phone=None, error=None):
"""Update payment status in database"""
# Your database update logic here
pass
def send_websocket_notification(data):
"""Send real-time notification to frontend"""
# Your WebSocket notification logic here
pass
if __name__ == '__main__':
app.run(debug=True)
š§ Common Integration Patterns
Error Handling
try {
const result = await mpesa.initiatePayment(phone, amount, ref);
if (result.success) {
// Handle success
console.log('Payment successful:', result.data.transaction_id);
} else {
// Handle API error
console.error('API Error:', result.data.message);
}
} catch (error) {
// Handle network error
console.error('Network Error:', error.message);
}
try:
result = mpesa.initiate_payment(phone, amount, ref)
if result['success']:
# Handle success
print(f"Payment successful: {result['data']['transaction_id']}")
else:
# Handle API error
print(f"API Error: {result['data']['message']}")
except requests.exceptions.RequestException as e:
# Handle network error
print(f"Network Error: {e}")
Webhook Security
Signature Verification (PHP)
<?php
function verifyWebhookSignature($payload, $signature) {
$secret = 'your_webhook_secret';
$expectedSignature = hash_hmac('sha256', json_encode($payload), $secret);
return hash_equals($signature, $expectedSignature);
}
// In your webhook handler
$signature = $_SERVER['HTTP_X_PAYNEXUS_SIGNATURE'] ?? '';
$payload = json_decode(file_get_contents('php://input'), true);
if (!verifyWebhookSignature($payload, $signature)) {
http_response_code(401);
echo json_encode(['error' => 'Unauthorized']);
exit;
}
?>
Payment Status Polling
Reliable Status Checking
// Poll payment status
async function pollPaymentStatus(transactionId, maxAttempts = 10) {
for (let i = 0; i < maxAttempts; i++) {
const status = await mpesa.checkPaymentStatus(transactionId);
if (status.completed) {
return status;
}
// Wait 5 seconds before next check
await new Promise(resolve => setTimeout(resolve, 5000));
}
throw new Error('Payment status check timeout');
}