Product Document & Integration Manual · v1.1

CironetPay

The unified payment layer for the Cironet ecosystem. One gateway, one merchant identity, powering every product and client platform.

Version 1.1
Mobile Money · Uganda
Production Ready
pay.cironetug.com

What is CironetPay?

CironetPay is a centrally hosted payment gateway developed and operated by Cironet Technologies Limited. It sits between the applications that need to accept payments (Nodes) and the payment service providers that process them. It is the single, unified layer through which all payment activity in the Cironet ecosystem flows.

Node
Messaging
Gateway
CironetPay
Provider
Mobile Money

Nodes know CironetPay. CironetPay knows the payment provider. The provider knows Cironet Technologies as the merchant. No node ever interacts with a payment provider directly.

In one sentence: CironetPay is the payment backbone of the Cironet ecosystem — one gateway, one merchant account, powering every product Cironet builds and every client platform Cironet delivers.

The Problem

Accepting digital payments in Uganda is complex. Every provider has its own API, authentication mechanism, and webhook format. For a company operating multiple products and building platforms for various clients, this creates significant overhead:

The Solution

The "Node" Principle

Any application connects to CironetPay as a node. Nodes POST a checkout request to CironetPay and receive a signed webhook when the payment completes. The node never interacts with a payment provider directly — it does not even know which provider processed the payment.

For Cironet Products

Powers payments inside Messaging, Smart ERP, and Marketplace through a single internal checkout — no provider logic in each product.

For Client Platforms

A drop-in payment layer for platforms Cironet builds for clients. Clients accept payments instantly without separate PSP onboarding.

How It Works

1

Node Initiates Checkout

The Node builds a signed POST form and redirects the user to pay.cironetug.com/checkout.php with amount, reference, and callback URL.

2

CironetPay Collects Payment Details

CironetPay verifies the signature, records a Pending transaction, and shows the user a phone number prompt.

3

Payment Processed

CironetPay sends a Mobile Money push prompt to the user's phone. The user enters their PIN to authorize.

4

Provider Callback

The payment provider notifies CironetPay of the outcome. CironetPay updates the transaction status to Completed or Failed.

5

Node Notification

CironetPay sends a signed webhook to the Node's callback_url so the Node can fulfill the order (credit wallet, activate service, etc.).

Merchant Identity Model

CironetPay operates on a single-merchant, multi-source model. Payment providers see only Cironet Technologies Limited as the merchant.

PerspectiveWhat they seeWhy
Payment Providers One merchant: Cironet Technologies Limited Simplifies compliance. Providers deal with one trusted entity; clients don't need their own PSP accounts.
Inside CironetPay Every transaction is tagged with its node_source and merchant_id Enables granular per-product and per-client reporting, billing, and reconciliation.

Who It Serves

🏢

Cironet Products

Internal platforms owned by Cironet Technologies.

  • CironetMessaging — Wallet top-ups & billing
  • CironetSmart ERP — Invoice & payroll payments
  • CironetMarketplace — Storefront checkout
  • cironetug.com — Domains & hosting fees
🔌

Client Platforms

Solutions Cironet develops for external businesses.

  • SACCO & Microfinance systems
  • School Fees platforms
  • NGO Donor management
  • E-commerce custom builds

Core Capabilities

📱
Mobile Money

STK push to the payer's phone. Supports all major Ugandan mobile money networks.

🔐
Node Isolation

Every node has its own API key and isolated transaction history. Keys rotate independently.

🔏
Signed Callbacks

HMAC-SHA256 signatures on every outbound webhook. Nodes verify before fulfilling.

🔁
Idempotency

Duplicate checkout requests for the same reference are detected and returned safely.

📊
Central Ledger

All payments across the ecosystem recorded in one place with full audit trail.

🏪
Merchant Portal

Dashboard at /portal for merchants to view their transaction history and stats.

Checkout Flow (Node Integration)

Nodes integrate via a redirect checkout. The Node builds a form with the required fields, signs it with a HMAC hash, and POSTs the user to CironetPay.

Step 1 — Build and POST the form

// PHP example — Node side (e.g. messaging.cironetug.com)
$app_key    = 'cp_live_msg_...';   // Your node's API key
$app_secret = 'cp_sec_msg_...';   // Your node's secret (kept private)
$reference  = 'CTR' . bin2hex(random_bytes(6));  // Unique per transaction
$amount     = 10000;               // UGX

$hash = hash('sha256', $app_key . $reference . $amount . $app_secret);

// POST these fields to https://pay.cironetug.com/checkout.php
$payload = [
    'app_key'        => $app_key,
    'reference'      => $reference,
    'amount'         => $amount,
    'currency'       => 'UGX',
    'customer_email' => $user_email,
    'callback_url'   => 'https://yourapp.com/api/payment_callback.php',
    'return_url'     => 'https://yourapp.com/user/history.php',
    'hash'           => $hash,
];

Step 2 — CironetPay handles the rest

The user is shown a phone number input on the CironetPay checkout page, enters their Mobile Money number, and authorizes the payment on their phone. The Node does not handle any of this.

Authentication

Nodes are authenticated via two values that must both be correct for a checkout to proceed:

FieldWherePurpose
app_key POST body Identifies the node. Matched against config/config.php api_keys.
hash POST body Integrity check. sha256(app_key + reference + amount + app_secret). Prevents tampering with the amount.

Never expose app_secret in client-side code. Hash generation must happen server-side only.

Request Fields

FieldRequiredDescription
app_keyRequiredNode API key issued by Cironet.
referenceRequiredUnique transaction reference from the Node. Used to match the webhook callback.
amountRequiredPayment amount in UGX (integer, no decimals).
currencyRequiredMust be UGX.
customer_emailRequiredPayer's email address for records.
callback_urlRequiredHTTPS endpoint on the Node that receives the signed payment result webhook.
return_urlRequiredWhere to send the user after they authorize (or cancel) payment.
hashRequiredsha256(app_key + reference + amount + app_secret).
metadataOptionalJSON string. Passed through and logged. Useful for channel, units, order details.

Webhook Callback

When a payment completes, CironetPay sends a POST request to the Node's callback_url with a JSON body and a signature header.

Webhook Payload

{
  "reference":      "CTR3f8a92b1c4d5",
  "amount":         10000,
  "status":         "completed",
  "transaction_id": "a4b2c8d1e6f0..."
}

Webhook Headers

Content-Type: application/json
X-CironetPay-Signature: <hmac-sha256-hex>

Node-side handling

// PHP — verify the webhook before processing
$raw_body      = file_get_contents('php://input');
$provided_sig  = $_SERVER['HTTP_X_CIRONETPAY_SIGNATURE'] ?? '';
$webhook_secret = 'whs_...'; // your cironetpay_webhook_secret

$expected_sig = hash_hmac('sha256', $raw_body, $webhook_secret);

if (!hash_equals($expected_sig, $provided_sig)) {
    http_response_code(401);
    exit('Invalid signature');
}

$data = json_decode($raw_body, true);
if ($data['status'] === 'completed') {
    // Credit wallet, activate service, fulfill order...
}

http_response_code(200);
echo json_encode(['status' => 'success']);

Always return HTTP 200 after successful processing. CironetPay logs non-200 responses as delivery failures.

Signature Security

CironetPay uses two separate secrets — do not confuse them:

SecretUsed forHeld by
app_secret Signing the hash field on the outgoing checkout request Node (in system_settings.cironetpay_app_secret) and CironetPay (config.api_secrets)
webhook_secret Signing the X-CironetPay-Signature header on inbound webhook callbacks Node (in system_settings.cironetpay_webhook_secret) and CironetPay (WEBHOOK_OUTGOING_SECRET env var)

Technical Architecture

Built with PHP 8.1+ on a standard LAMP stack. Hosted at pay.cironetug.com.

Component Overview

ComponentFileResponsibility
Checkoutcheckout.phpAuthenticates node, collects phone, initiates charge.
CironetPay Enginecore/CironetPay.phpIdempotency check, DB write, gateway delegation.
Payment Gatewaygateways/One class per payment provider, handling the provider-specific protocol. Internal to CironetPay — never exposed to nodes or merchants.
Webhook Receiverapi/webhook.phpReceives provider callbacks, updates transaction status.
Notifiercore/Notifier.phpSends signed webhook to the Node's callback_url.
Merchant Portalportal/Dashboard for merchants to view their transactions.

Database Schema

transactions

The central ledger. Every payment attempt is recorded here.

CREATE TABLE transactions (
    id               INT AUTO_INCREMENT PRIMARY KEY,
    transaction_ref  VARCHAR(100) UNIQUE NOT NULL,
    external_id      VARCHAR(100),       -- the Node's reference
    node_source      VARCHAR(50),        -- e.g. 'messaging_node'
    merchant_id      INT,
    provider         VARCHAR(50),        -- internal only
    amount           DECIMAL(20,2),
    currency         VARCHAR(10),
    payer_phone      VARCHAR(30),
    payer_email      VARCHAR(150),
    callback_url     VARCHAR(500),       -- Node's callback endpoint
    status           ENUM('Pending','Completed','Failed','Cancelled'),
    provider_reference VARCHAR(255),
    raw_log          JSON,
    created_at       TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

merchant_webhooks

Registered webhook URLs for merchant portal accounts.

CREATE TABLE merchant_webhooks (
    id          INT AUTO_INCREMENT PRIMARY KEY,
    merchant_id INT NOT NULL,
    url         VARCHAR(500) NOT NULL,
    secret      VARCHAR(255),
    status      ENUM('active','inactive') DEFAULT 'active'
);

Configuration

All credentials live in .env. Never commit this file to version control.

VariableDescription
DB_HOST / DB_NAME / DB_USER / DB_PASSMySQL connection
CIRONET_API_KEY_{NODE}Public API key for each connected node
CIRONET_API_SECRET_{NODE}Secret used to verify the checkout hash from that node
WEBHOOK_OUTGOING_SECRETSecret used to sign all outbound webhook callbacks to nodes

Security: If a node's API key is compromised, update CIRONET_API_KEY_{NODE} in .env and cironetpay_app_key in the node's database simultaneously.

Node Onboarding

1

Generate Keys

Create an API key and a separate secret: bin2hex(random_bytes(16)) for each.

2

Configure CironetPay

Add CIRONET_API_KEY_{NODE} and CIRONET_API_SECRET_{NODE} to the .env file, and register the node slug in config/config.php.

3

Configure the Node

Add cironetpay_app_key, cironetpay_app_secret, and cironetpay_webhook_secret to the node's system_settings table.

4

Implement Callback

Build a /api/payment_callback.php endpoint on the node that verifies the HMAC signature and fulfills the order.

Troubleshooting

SymptomCauseFix
401 Unauthorized Node app_key not found in config Verify CIRONET_API_KEY_{NODE} in .env matches what the node is sending.
400 Invalid Signature Hash hash mismatch on checkout Ensure node's cironetpay_app_secret matches CIRONET_API_SECRET_{NODE} in CironetPay's .env.
Transaction stuck Pending Provider webhook not received, or user didn't authorize Check logs/webhooks_raw.log for provider callback. Verify webhook URL registered with provider.
Callback returns 401 on Node Signature mismatch on webhook Confirm node's cironetpay_webhook_secret equals WEBHOOK_OUTGOING_SECRET in CironetPay's .env.
Callback returns 404 on Node Transaction reference not found or already completed Check if the transaction was previously fulfilled (idempotency). Review node's transaction log.
No webhook delivered callback_url was not stored on the transaction Ensure callback_url is included in the checkout POST payload. Check logs/notifier.log.

Roadmap