๐ Prerequisites
PayNexus merchant account
API keys (Public and Secret)
Web server (for webhooks)
Basic programming knowledge
๐ Step 1: Get Your API Keys
- Log into your PayNexus dashboard
- Navigate to API Keys section
- Create a new API key for your payment account:
- Public Key (
pk_...) - For read operations like phone validation - Secret Key (
sk_...) - For write operations like payment initiation
- Public Key (
- Each API key is linked to a specific payment account, ensuring payments go to the correct merchant
Security Note: Never expose your secret keys in client-side code! Use public keys for client-side operations.
Important: API keys are automatically associated with your merchant account and payment account. The system validates that you can only access your own payment accounts.
๐ป Step 2: Choose Your Language
PHP
Perfect for Laravel, WordPress, or custom PHP applications
JavaScript/Node.js
Great for Express.js, React, or modern web applications
Python
Ideal for Django, Flask, or Python-based services
cURL
For testing and command-line integration
โก Basic Integration
โ ๏ธ Critical: Account reference usage depends on account type.
Paybill accounts require merchant's account number for proper routing.
Till accounts can use order references.
Here's how to make your first payment:
PHP Example - Complete Workflow
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class PaynexusClient {
private $client;
private $baseUrl;
private $secretKey;
public function __construct($secretKey) {
$this->baseUrl = 'https://paynexus.mcbankske.space/api';
$this->secretKey = $secretKey;
$this->client = new Client([
'base_uri' => $this->baseUrl,
'headers' => [
'X-API-Key' => $secretKey,
'Content-Type' => 'application/json'
]
]);
}
public function getPaymentAccounts() {
$response = $this->client->get('/merchant/payment-accounts');
return json_decode($response->getBody(), true);
}
public function validatePhone($phone) {
$response = $this->client->post('/mpesa/validate-phone', [
'json' => ['phone' => $phone]
]);
return json_decode($response->getBody(), true);
}
public function initiatePayment($paymentAccountId, $amount, $phone, $accountReference, $description = null) {
$response = $this->client->post('/mpesa/payment/initiate', [
'json' => [
'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 json_decode($response->getBody(), true);
}
}
// COMPLETE USAGE EXAMPLE WITH PROPER ACCOUNT REFERENCE HANDLING
$paynexus = new PaynexusClient('sk_your_secret_key_here');
try {
// Step 1: Get payment accounts to determine account type
$accountsResponse = $paynexus->getPaymentAccounts();
if (!$accountsResponse['success']) {
die('Failed to get payment accounts');
}
$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');
}
$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
$result = $paynexus->initiatePayment(
$mpesaAccount['id'], // Payment account ID
100, // Amount in KES
$normalizedPhone,
$accountReference, // CRITICAL: Correct account reference
'Payment for website order'
);
if ($result['success']) {
echo "โ
Payment initiated successfully!\n";
echo "๐ Checkout Request ID: " . $result['data']['checkout_request_id'] . "\n";
echo "๐ Account Type: " . $mpesaAccount['type'] . "\n";
echo "๐ Account Reference Used: $accountReference\n";
} else {
echo "โ Payment failed: " . ($result['error'] ?? 'Unknown error') . "\n";
}
} catch (Exception $e) {
echo "โ Error: " . $e->getMessage() . "\n";
}
?>
JavaScript Example - Complete Workflow
const axios = require('axios');
class PaynexusClient {
constructor(secretKey) {
this.client = axios.create({
baseURL: 'https://paynexus.mcbankske.space/api',
headers: { 'X-API-Key': secretKey }
});
}
async getPaymentAccounts() {
const response = await this.client.get('/merchant/payment-accounts');
return response.data;
}
async validatePhone(phone) {
const response = await this.client.post('/mpesa/validate-phone', { phone });
return response.data;
}
async initiatePayment(paymentAccountId, amount, phone, accountReference, description = null) {
const response = await this.client.post('/mpesa/payment/initiate', {
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 response.data;
}
}
// COMPLETE USAGE EXAMPLE WITH PROPER ACCOUNT REFERENCE HANDLING
const paynexus = new PaynexusClient('sk_your_secret_key_here');
const processPayment = async () => {
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');
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');
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 result = await paynexus.initiatePayment(
mpesaAccount.id, // Payment account ID
100, // Amount in KES
normalizedPhone,
accountReference, // CRITICAL: Correct account reference
'Payment for website order'
);
if (result.success) {
console.log('โ
Payment initiated successfully!');
console.log('๐ Checkout Request ID:', result.data.checkout_request_id);
console.log('๐ Account Type:', mpesaAccount.type);
console.log('๐ Account Reference Used:', accountReference);
} else {
console.error('โ Payment failed:', result.error || 'Unknown error');
}
} catch (error) {
console.error('โ Error:', error.message);
}
};
processPayment();
Python Example - Check Status
import requests
# Initialize session with your secret API key
session = requests.Session()
session.headers.update({'X-API-Key': 'sk_your_secret_key_here'})
base_url = 'https://paynexus.mcbankske.space/api'
def check_payment_status(payment_reference):
"""Check payment status using payment reference"""
try:
response = session.get(f'{base_url}/payments/{payment_reference}')
result = response.json()
if result['success']:
payment = result['data']
print(f"Payment Status: {payment['status']}")
print(f"Amount: {payment['amount']} {payment['currency']}")
print(f"Phone: {payment['phone']}")
print(f"Created: {payment['created_at']}")
if payment.get('provider_transaction_id'):
print(f"MPESA Transaction ID: {payment['provider_transaction_id']}")
else:
print(f"Error: {result['message']}")
except requests.exceptions.RequestException as error:
print(f"Request failed: {error}")
def check_status_by_checkout_id(checkout_request_id):
"""Check payment status using checkout request ID"""
try:
response = session.post(f'{base_url}/payments/status-by-checkout-id', json={
'checkout_request_id': checkout_request_id
})
result = response.json()
if result['success']:
payment = result['data']
print(f"Payment Status: {payment['status']}")
print(f"Reference: {payment['reference']}")
print(f"Amount: {payment['amount']} {payment['currency']}")
else:
print(f"Error: {result['message']}")
except requests.exceptions.RequestException as error:
print(f"Request failed: {error}")
# Example usage
check_payment_status('ORDER_123') # Use your actual payment reference
check_status_by_checkout_id('ws_CO_123456789') # Use your actual checkout request ID
๐งช Step 4: Test Your Integration
Health Check
curl https://paynexus.mcbankske.space/api/health
Phone Validation
curl -X POST https://paynexus.mcbankske.space/api/mpesa/validate-phone \ -H "X-API-Key: pk_your_public_key_here" \ -H "Content-Type: application/json" \ -d '{"phone": "0798808796"}'
Get Payment Accounts
curl -X GET https://paynexus.mcbankske.space/api/merchant/payment-accounts \ -H "X-API-Key: sk_your_secret_key_here"
Returns your available payment accounts with their IDs
Check Transaction Status (by Reference)
curl -X GET "http://127.0.0.1:8000/api/payments/YOUR_PAYMENT_REFERENCE" \ -H "X-API-Key: sk_your_secret_key_here"
Check payment status using the payment reference returned during initiation
Check Transaction Status (by Checkout ID)
curl -X POST "http://127.0.0.1:8000/api/payments/status-by-checkout-id" \ -H "X-API-Key: sk_your_secret_key_here" \ -H "Content-Type: application/json" \ -d '{"checkout_request_id": "YOUR_CHECKOUT_REQUEST_ID"}'
Check payment status using the checkout_request_id from MPESA response
MPESA Real-time Status Check
curl -X POST "http://127.0.0.1:8000/api/mpesa/payment/status" \ -H "X-API-Key: sk_your_secret_key_here" \ -H "Content-Type: application/json" \ -d '{"checkout_request_id": "YOUR_CHECKOUT_REQUEST_ID"}'
Real-time status check directly from MPESA (requires additional API calls)
๐ Step 5: Set Up Webhooks
Create a webhook endpoint to receive payment notifications:
Basic Webhook Handler (PHP)
<?php
// webhook.php
header('Content-Type: application/json');
$payload = json_decode(file_get_contents('php://input'), true);
if ($payload['ResultCode'] == 0) {
// Payment successful
$transactionId = $payload['TransactionID'];
$amount = $payload['Amount'];
// Update your database
// Mark order as paid
// Send confirmation email
echo json_encode(['ResultCode' => 0, 'ResultDesc' => 'Success']);
} else {
// Payment failed
echo json_encode(['ResultCode' => 0, 'ResultDesc' => 'Received']);
}
?>
๐ Step 6: Go Live!
Use production API keys
Set up HTTPS for webhooks
Monitor your payments
Test with real M-Pesa transactions
Congratulations! ๐ You're ready to accept M-Pesa payments through PayNexus.
โ Common Issues & Solutions
| Issue | Solution |
|---|---|
| "Unauthorized" | Check API key validity and permissions. Ensure you're using the correct key type (pk_ for read, sk_ for write operations) |
| "Payment not found or not accessible" | Verify the payment reference/checkout ID belongs to your merchant account. Use GET /merchant/payment-accounts to get your valid IDs |
| "Transaction not found" | Check if the checkout_request_id is correct and the payment was initiated successfully |
| "Status check failed" | The MPESA service might be temporarily unavailable. Try again later or use the database status check instead |
| "Invalid phone number" | Use phone validation first. Format should be 254XXXXXXXXX for Kenya numbers |
| "Merchant authentication required" | Ensure your API key has merchant.auth middleware applied and your merchant account is approved |
| Webhook not working | Ensure HTTPS and correct URL |