Integrate SYSNAV M-Pesa Payment Gateway in your C++ applications
This guide walks you through integrating the SYSNAV M-Pesa Payment Gateway into your C++ applications using libcurl and nlohmann/json.
# 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)
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);
};
// 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);
}
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;
}
// 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;
}
}
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
#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;
}
https://payments.navipos.co.ke
Include your API key in the X-API-Key header for all protected endpoints.
- Initiate STK Push - List Transactions - Get Transaction Details - Webhook Callback (no auth)Start building secure payment solutions with SYSNAV M-Pesa Gateway
Back to Platforms