C++ Integration Guide

Integrate SYSNAV M-Pesa Payment Gateway in your C++ applications

← Back to Platforms

Getting Started

This guide walks you through integrating the SYSNAV M-Pesa Payment Gateway into your C++ applications using libcurl and nlohmann/json.

Prerequisites

  • C++17 or higher
  • CMake 3.10+
  • libcurl development library
  • nlohmann/json library
  • OpenSSL for HTTPS

Installation with CMake

# Install dependencies (Ubuntu/Debian)
sudo apt-get install libcurl4-openssl-dev nlohmann-json3-dev

# Create project structure
mkdir mpesa-gateway-cpp
cd mpesa-gateway-cpp
mkdir -p src include build

# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MpesaGateway)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(CURL REQUIRED)
find_package(nlohmann_json 3.2.0 REQUIRED)

add_executable(mpesa_client src/main.cpp)
target_link_libraries(mpesa_client PRIVATE CURL::libcurl nlohmann_json::nlohmann_json)
target_include_directories(mpesa_client PRIVATE include)

1. Gateway Client Class

Create a reusable client for interacting with the gateway:

// include/gateway_client.hpp
#pragma once

#include <string>
#include <curl/curl.h>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

class GatewayClient {
private:
    std::string base_url;
    CURL* curl_handle;
    
    static size_t WriteCallback(void* contents, size_t size, 
                               size_t nmemb, std::string* userp);
    
public:
    GatewayClient(const std::string& url = "https://payments.sysnavtechnologies.com");
    ~GatewayClient();
    
    json registerClient(const std::string& clientName, 
                       const std::string& email);
    json storeCredentials(const std::string& clientId,
                         const std::string& consumerKey,
                         const std::string& consumerSecret,
                         const std::string& shortcode,
                         const std::string& passkey,
                         const std::string& apiKey);
    json initiateSTKPush(const std::string& clientId,
                        const std::string& apiKey,
                        const std::string& phoneNumber,
                        double amount,
                        const std::string& accountRef,
                        const std::string& transDesc);
    json getTransactionStatus(const std::string& clientId,
                             const std::string& transactionId,
                             const std::string& apiKey);
};

2. Implement Gateway Client

// src/gateway_client.cpp
#include "../include/gateway_client.hpp"
#include <iostream>
#include <stdexcept>

size_t GatewayClient::WriteCallback(void* contents, size_t size,
                                     size_t nmemb, std::string* userp) {
    userp->append((char*)contents, size * nmemb);
    return size * nmemb;
}

GatewayClient::GatewayClient(const std::string& url) 
    : base_url(url) {
    curl_handle = curl_easy_init();
    if (!curl_handle) {
        throw std::runtime_error("Failed to initialize CURL");
    }
}

GatewayClient::~GatewayClient() {
    if (curl_handle) {
        curl_easy_cleanup(curl_handle);
    }
}

json GatewayClient::registerClient(const std::string& clientName,
                                   const std::string& email) {
    std::string url = base_url + "/api/v1/clients";
    
    json payload = {
        {"client_name", clientName},
        {"email", email}
    };
    
    std::string data = payload.dump();
    std::string response;
    
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    
    curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, data.c_str());
    curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &response);
    
    CURLcode res = curl_easy_perform(curl_handle);
    curl_slist_free_all(headers);
    
    if (res != CURLE_OK) {
        throw std::runtime_error("CURL request failed");
    }
    
    return json::parse(response);
}

json GatewayClient::storeCredentials(const std::string& clientId,
                                     const std::string& consumerKey,
                                     const std::string& consumerSecret,
                                     const std::string& shortcode,
                                     const std::string& passkey,
                                     const std::string& apiKey) {
    std::string url = base_url + "/api/v1/clients/" + clientId + "/credentials";
    
    json payload = {
        {"consumer_key", consumerKey},
        {"consumer_secret", consumerSecret},
        {"shortcode", shortcode},
        {"passkey", passkey}
    };
    
    std::string data = payload.dump();
    std::string response;
    
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    std::string api_header = "X-API-Key: " + apiKey;
    headers = curl_slist_append(headers, api_header.c_str());
    
    curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, data.c_str());
    curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &response);
    
    CURLcode res = curl_easy_perform(curl_handle);
    curl_slist_free_all(headers);
    
    if (res != CURLE_OK) {
        throw std::runtime_error("CURL request failed");
    }
    
    return json::parse(response);
}

json GatewayClient::initiateSTKPush(const std::string& clientId,
                                    const std::string& apiKey,
                                    const std::string& phoneNumber,
                                    double amount,
                                    const std::string& accountRef,
                                    const std::string& transDesc) {
    std::string url = base_url + "/api/v1/stk-push";
    
    json payload = {
        {"phone_number", phoneNumber},
        {"amount", amount},
        {"account_reference", accountRef},
        {"transaction_description", transDesc}
    };
    
    std::string data = payload.dump();
    std::string response;
    
    struct curl_slist* headers = nullptr;
    headers = curl_slist_append(headers, "Content-Type: application/json");
    std::string api_header = "X-API-Key: " + apiKey;
    headers = curl_slist_append(headers, api_header.c_str());
    
    curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, data.c_str());
    curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &response);
    
    CURLcode res = curl_easy_perform(curl_handle);
    curl_slist_free_all(headers);
    
    if (res != CURLE_OK) {
        throw std::runtime_error("CURL request failed");
    }
    
    return json::parse(response);
}

json GatewayClient::getTransactionStatus(const std::string& clientId,
                                         const std::string& transactionId,
                                         const std::string& apiKey) {
    std::string url = base_url + "/api/v1/transactions/" + transactionId;
    
    std::string response;
    
    struct curl_slist* headers = nullptr;
    std::string api_header = "X-API-Key: " + apiKey;
    headers = curl_slist_append(headers, api_header.c_str());
    
    curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, headers);
    curl_easy_setopt(curl_handle, CURLOPT_HTTPGET, 1L);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, &response);
    
    CURLcode res = curl_easy_perform(curl_handle);
    curl_slist_free_all(headers);
    
    if (res != CURLE_OK) {
        throw std::runtime_error("CURL request failed");
    }
    
    return json::parse(response);
}

3. Webhook Server Implementation

Handle M-Pesa callbacks with a simple HTTP server:

// src/webhook_server.hpp
#pragma once

#include <string>
#include <functional>
#include <nlohmann/json.hpp>

using json = nlohmann::json;

class WebhookServer {
private:
    int port;
    std::function<void(const json&)gt; callback_handler;

public:
    WebhookServer(int p = 8080);
    
    void setCallbackHandler(std::function<void(const json&)> handler);
    void start();
    void stop();
};

// src/webhook_server.cpp
#include "webhook_server.hpp"
#include <iostream>

// Simple HTTP server implementation
// For production, consider using libraries like crow or pistache

WebhookServer::WebhookServer(int p) : port(p) {}

void WebhookServer::setCallbackHandler(std::function<void(const json&)> handler) {
    callback_handler = handler;
}

void WebhookServer::start() {
    std::cout << "Webhook server starting on port " << port << std::endl;
    
    // Implementation depends on your HTTP server library
    // Example with crow:
    // crow::SimpleApp app;
    // CROW_ROUTE(app, "/webhooks/mpesa").methods("POST"_method)
    // ([this](const crow::request& req) {
    //     auto payload = json::parse(req.body);
    //     if (callback_handler) {
    //         callback_handler(payload);
    //     }
    //     return crow::response(200, R"({"status":"success"})");
    // });
    // app.port(port).multithreaded().run();
}

void WebhookServer::stop() {
    std::cout << "Webhook server stopping" << std::endl;
}

4. Complete Example Usage

// src/main.cpp
#include "gateway_client.hpp"
#include <iostream>
#include <iomanip>

void printJson(const json& obj) {
    std::cout << obj.dump(4) << std::endl;
}

int main() {
    try {
        // Initialize client
        GatewayClient client("https://payments.sysnavtechnologies.com");
        
        // Step 1: Register client
        std::cout << "Step 1: Registering client..." << std::endl;
        auto reg_result = client.registerClient(
            "My C++ App",
            "contact@mycppapp.com"
        );
        
        std::string client_id = reg_result["data"]["id"];
        std::string api_key = reg_result["data"]["api_key"];
        
        std::cout << "Client registered!" << std::endl;
        std::cout << "Client ID: " << client_id << std::endl;
        std::cout << "API Key: " << api_key << std::endl << std::endl;
        
        // Step 2: Store credentials
        std::cout << "Step 2: Storing Daraja credentials..." << std::endl;
        auto cred_result = client.storeCredentials(
            client_id,
            "your_consumer_key",
            "your_consumer_secret",
            "174379",
            "bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919",
            api_key
        );
        
        std::cout << "Credentials stored successfully!" << std::endl << std::endl;
        
        // Step 3: Initiate STK Push
        std::cout << "Step 3: Initiating STK Push..." << std::endl;
        auto payment_result = client.initiateSTKPush(
            client_id,
            api_key,
            "254712345678",
            100.00,
            "ACCOUNT001",
            "Payment for Order #123"
        );
        
        std::cout << "STK Push initiated!" << std::endl;
        std::cout << "Response:" << std::endl;
        printJson(payment_result);
        
        std::string checkout_id = payment_result["data"]["checkout_request_id"];
        std::cout << "\nCheckout Request ID: " << checkout_id << std::endl << std::endl;
        
        // Step 4: Check transaction status
        std::cout << "Step 4: Checking transaction status..." << std::endl;
        // Wait a moment before checking
        std::this_thread::sleep_for(std::chrono::seconds(5));
        
        auto txn_result = client.getTransactionStatus(
            client_id,
            checkout_id,
            api_key
        );
        
        std::cout << "Transaction status:" << std::endl;
        printJson(txn_result);
        
        return 0;
        
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
}

5. Asynchronous Request Handling

For non-blocking operations:

// include/async_gateway.hpp
#pragma once

#include "gateway_client.hpp"
#include <future>
#include <memory>

class AsyncGatewayClient {
private:
    std::shared_ptr<GatewayClient> client;

public:
    AsyncGatewayClient(const std::string& url = "https://payments.sysnavtechnologies.com");
    
    std::future<json> initiateSTKPushAsync(
        const std::string& clientId,
        const std::string& apiKey,
        const std::string& phoneNumber,
        double amount,
        const std::string& accountRef,
        const std::string& transDesc);
    
    std::future<json> getTransactionStatusAsync(
        const std::string& clientId,
        const std::string& transactionId,
        const std::string& apiKey);
};

// src/async_gateway.cpp
AsyncGatewayClient::AsyncGatewayClient(const std::string& url)
    : client(std::make_shared<GatewayClient>(url)) {}

std::future<json> AsyncGatewayClient::initiateSTKPushAsync(
    const std::string& clientId,
    const std::string& apiKey,
    const std::string& phoneNumber,
    double amount,
    const std::string& accountRef,
    const std::string& transDesc) {
    
    return std::async(std::launch::async, [this, clientId, apiKey, 
                                           phoneNumber, amount, accountRef, transDesc]() {
        return client->initiateSTKPush(clientId, apiKey, phoneNumber, 
                                       amount, accountRef, transDesc);
    });
}

// Usage example
AsyncGatewayClient async_client;

auto payment_future = async_client.initiateSTKPushAsync(
    client_id, api_key, "254712345678", 100.00, "ACCOUNT001", "Payment"
);

// Do other work...

auto result = payment_future.get(); // Wait for result

Error Handling

#include <stdexcept>
#include <optional>

class GatewayException : public std::runtime_error {
public:
    std::string error_code;
    json response_data;

    GatewayException(const std::string& code, 
                     const std::string& message,
                     const json& data = {})
        : std::runtime_error(message), 
          error_code(code), 
          response_data(data) {}
};

// Safe wrapper for API calls
std::optional<json> safeApiCall(
    std::function<json()> api_fn,
    int max_retries = 3) {
    
    for (int attempt = 0; attempt < max_retries; ++attempt) {
        try {
            auto result = api_fn();
            
            if (!result["success"].get<bool>()) {
                auto error = result["error"];
                throw GatewayException(
                    error["code"].get<std::string>(),
                    error["message"].get<std::string>(),
                    result
                );
            }
            
            return result;
            
        } catch (const GatewayException& e) {
            if (attempt == max_retries - 1) throw;
            std::this_thread::sleep_for(
                std::chrono::seconds(1 << attempt) // Exponential backoff
            );
        }
    }
    
    return std::nullopt;
}

// Usage
try {
    auto result = safeApiCall([&]() {
        return client.initiateSTKPush(...);
    });
    
    if (result) {
        std::cout << "Success: " << result->dump() << std::endl;
    }
} catch (const GatewayException& e) {
    std::cerr << "Error [" << e.error_code << "]: " << e.what() << std::endl;
}

API Reference

Base URL

https://payments.navipos.co.ke

Authentication

Include your API key in the X-API-Key header for all protected endpoints.

Key Endpoints

  • POST - Initiate STK Push
  • GET - List Transactions
  • GET - Get Transaction Details
  • POST - Webhook Callback (no auth)

Ready to Integrate?

Start building secure payment solutions with SYSNAV M-Pesa Gateway

Back to Platforms