š Public Key Usage (Read-Only)
Public keys (pk_...) are safe to use in client-side code for read-only operations. They cannot initiate payments or perform write operations.
Security Note: Public keys are enforced as read-only at the middleware level. Safe to use in frontend JavaScript, mobile apps, and public integrations.
JavaScript (Frontend)
Browser JavaScript - Read Operations Only
// Safe to use in browser JavaScript
const PUBLIC_KEY = 'pk_ce590814cd5e56a107e0b2b82def52b06306bd213a9fc3f89d097';
// Get merchant information
async function getMerchantInfo() {
const response = await fetch('https://paynexus.co.ke/api/merchant', {
headers: {
'X-API-Key': PUBLIC_KEY,
'Content-Type': 'application/json'
}
});
return response.json();
}
// Get payment accounts
async function getPaymentAccounts() {
const response = await fetch('https://paynexus.co.ke/api/merchant/payment-accounts', {
headers: {
'X-API-Key': PUBLIC_KEY,
'Content-Type': 'application/json'
}
});
return response.json();
}
// Check payment status
async function checkPaymentStatus(reference) {
const response = await fetch(`https://paynexus.co.ke/api/payments/${reference}`, {
headers: {
'X-API-Key': PUBLIC_KEY,
'Content-Type': 'application/json'
}
});
return response.json();
}
cURL Examples
Read Operations with Public Key
# Get merchant info with public key
curl -X GET "https://paynexus.co.ke/api/merchant" \
-H "X-API-Key: pk_ce590814cd5e56a107e0b2b82def52b06306bd213a9fc3f89d097"
# Get payment accounts
curl -X GET "https://paynexus.co.ke/api/merchant/payment-accounts" \
-H "X-API-Key: pk_ce590814cd5e56a107e0b2b82def52b06306bd213a9fc3f89d097"
# Check payment status
curl -X GET "https://paynexus.co.ke/api/payments/REF123" \
-H "X-API-Key: pk_ce590814cd5e56a107e0b2b82def52b06306bd213a9fc3f89d097"
Important: Use secret keys (
sk_...) for payment initiation and write operations. Keep secret keys server-side only.
Public Key Allowed Operations
Public keys (pk_) can perform the following read-only operations:
Merchant Information
GET /api/merchant- Get merchant informationGET /api/merchant/businesses- List merchant businessesGET /api/merchant/payment-accounts- List payment accounts
Payment Read Operations
GET /api/payments/{reference}- Get payment statusGET /api/payments/{id}/status-by-id- Get payment status by IDPOST /api/payments/status-by-checkout-id- Get payment status by checkout IDGET /api/payments- List all payments
M-Pesa Operations
GET /api/mpesa/health- M-Pesa service health checkPOST /api/mpesa/validate-phone- Validate phone numberPOST /api/mpesa/payment/status- Check M-Pesa transaction status
Webhook Read Operations
GET /api/webhooks- List registered webhooks
API Key Read Operations
GET /api/api-keys- List API keys
Invoice Read Operations
GET /api/invoices- List invoicesGET /api/invoices/{invoice}- View invoice details
Receipt Read Operations
GET /api/receipts- List receiptsGET /api/receipts/{receipt}- View receipt details
Public Key Blocked Operations
Public keys (pk_) are blocked from these write operations at the middleware level:
Payment Write Operations
POST /api/payments/initiate- Initiate paymentsPOST /api/mpesa/payment/initiate- Initiate STK push payment
Webhook Management
POST /api/webhooks/register- Register webhooksPUT /api/webhooks/{id}- Update webhooksDELETE /api/webhooks/{id}- Delete webhooks
API Key Management
POST /api/api-keys- Create API keysPUT /api/api-keys/{id}- Update API keysDELETE /api/api-keys/{id}- Delete API keys
Invoice Management
POST /api/invoices- Create invoicesPUT /api/invoices/{invoice}- Update invoicesDELETE /api/invoices/{invoice}- Delete invoicesPOST /api/invoices/{invoice}/send- Send invoices
Receipt Management
POST /api/receipts/{receipt}/resend- Resend receipts
š 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.co.ke/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, $description = null) {
$data = [
'payment_account_id' => $paymentAccountId,
'amount' => $amount,
'phone' => $phone,
'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('0746990866');
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.co.ke/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 (system handles account reference automatically)
$data = [
'payment_account_id' => $mpesaAccount['id'],
'amount' => $request->amount,
'phone' => $phoneValidation['data']['normalized'],
'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'],
'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.co.ke/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, description = null) {
const data = {
payment_account_id: paymentAccountId,
amount: amount,
phone: phone,
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.co.ke',
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('0746990866');
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.co.ke/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,
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.co.ke/api'
self.secret_key = secret_key
self.headers = {
'Content-Type': 'application/json',
'X-API-Key': self.secret_key
}
def initiate_payment(self, phone, amount):
data = {
'payment_account_id': 1,
'amount': amount,
'phone': phone,
'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)
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.co.ke/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'],
'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.co.ke/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 (system handles account reference automatically)"
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\",
\"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"
š§ 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');
}