SDK & exemples de code

Production
SDK & exemples de code
Code prêt à copier dans 6 langages : PHP, Node.js, Python, Java, Go, cURL.

Client PHP minimal

PHP<?php
class NekaPayClient {
    public function __construct(
        private string $apiKey,
        private string $apiSecret,
        private string $baseUrl = 'https://nekapaie.com/api/v1'
    ) {}

    private function sign(string $body): array {
        $ts = (string) time();
        return [
            'ts' => $ts,
            'sig' => hash_hmac('sha256', $ts . $body, $this->apiSecret),
        ];
    }

    public function request(string $method, string $path, array $body = null): array {
        $bodyStr = $body ? json_encode($body) : '';
        $auth = $this->sign($bodyStr);

        $ch = curl_init($this->baseUrl . $path);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_POSTFIELDS => $bodyStr,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'X-NekaPay-Key: ' . $this->apiKey,
                'X-NekaPay-Timestamp: ' . $auth['ts'],
                'X-NekaPay-Signature: ' . $auth['sig'],
            ],
        ]);
        $response = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return ['status' => $code, 'data' => json_decode($response, true)];
    }

    public function cashIn(array $payload): array {
        return $this->request('POST', '/payments/cashin', $payload);
    }

    public function cashOut(array $payload): array {
        return $this->request('POST', '/payments/cashout', $payload);
    }

    public function getStatus(string $txId): array {
        return $this->request('GET', '/payments/' . $txId);
    }

    public function getBalances(): array {
        return $this->request('GET', '/balances');
    }
}

// Utilisation
$client = new NekaPayClient(
    getenv('NEKAPAY_API_KEY'),
    getenv('NEKAPAY_API_SECRET')
);

$result = $client->cashIn([
    'merchant_order_id' => 'order_' . uniqid(),
    'amount' => 50000,
    'currency' => 'XOF',
    'customer_msisdn' => '+22376123456',
    'country_code' => 'ML',
]);

if ($result['status'] === 202) {
    echo "Payment URL: " . $result['data']['data']['payment_url'];
}

Vérification de webhook (PHP)

PHP<?php
$secret = getenv('NEKAPAY_API_SECRET');
$ts = $_SERVER['HTTP_X_NEKAPAY_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_NEKAPAY_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');

if (!$ts || abs(time() - (int)$ts) > 300) { http_response_code(401); exit('replay'); }
if (!hash_equals(hash_hmac('sha256', $ts . $body, $secret), $sig)) {
    http_response_code(401); exit('signature');
}

$event = json_decode($body, true);
match ($event['status']) {
    'SUCCESS' => markPaid($event['merchant_order_id']),
    'FAILED', 'EXPIRED' => markFailed($event['merchant_order_id']),
    'REVERSED' => markRefunded($event['merchant_order_id']),
    default => null,
};
http_response_code(200);

Client Node.js minimal

JSconst crypto = require('crypto');
const axios = require('axios');

class NekaPayClient {
    constructor(apiKey, apiSecret, baseUrl = 'https://nekapaie.com/api/v1') {
        this.apiKey = apiKey;
        this.apiSecret = apiSecret;
        this.baseUrl = baseUrl;
    }

    sign(body) {
        const ts = Math.floor(Date.now() / 1000).toString();
        const sig = crypto.createHmac('sha256', this.apiSecret)
            .update(ts + body).digest('hex');
        return { ts, sig };
    }

    async request(method, path, body = null) {
        const bodyStr = body ? JSON.stringify(body) : '';
        const { ts, sig } = this.sign(bodyStr);

        const res = await axios({
            method, url: this.baseUrl + path,
            data: bodyStr,
            headers: {
                'Content-Type': 'application/json',
                'X-NekaPay-Key': this.apiKey,
                'X-NekaPay-Timestamp': ts,
                'X-NekaPay-Signature': sig,
            },
            validateStatus: () => true,
        });

        return { status: res.status, data: res.data };
    }

    cashIn(payload) { return this.request('POST', '/payments/cashin', payload); }
    cashOut(payload) { return this.request('POST', '/payments/cashout', payload); }
    getStatus(txId) { return this.request('GET', `/payments/${txId}`); }
    getBalances() { return this.request('GET', '/balances'); }
}

// Utilisation
const client = new NekaPayClient(
    process.env.NEKAPAY_API_KEY,
    process.env.NEKAPAY_API_SECRET
);

(async () => {
    const result = await client.cashIn({
        merchant_order_id: 'order_' + Date.now(),
        amount: 50000,
        currency: 'XOF',
        customer_msisdn: '+22376123456',
        country_code: 'ML',
    });

    if (result.status === 202) {
        console.log('Payment URL:', result.data.data.payment_url);
    }
})();

Webhook handler (Express)

JSconst express = require('express');
const crypto = require('crypto');
const app = express();

app.post('/webhooks/nekapay', express.raw({type: 'application/json'}), (req, res) => {
    const secret = process.env.NEKAPAY_API_SECRET;
    const ts = req.header('X-NekaPay-Timestamp');
    const sig = req.header('X-NekaPay-Signature');
    const body = req.body.toString();

    if (!ts || Math.abs(Date.now()/1000 - parseInt(ts)) > 300)
        return res.status(401).send('replay');

    const expected = crypto.createHmac('sha256', secret)
        .update(ts + body).digest('hex');

    if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig)))
        return res.status(401).send('signature');

    const event = JSON.parse(body);
    switch (event.status) {
        case 'SUCCESS': markPaid(event.merchant_order_id); break;
        case 'FAILED': case 'EXPIRED': markFailed(event.merchant_order_id); break;
        case 'REVERSED': markRefunded(event.merchant_order_id); break;
    }
    res.status(200).json({received: true});
});

app.listen(3000);

Client Python minimal

PYTHONimport os
import hmac
import hashlib
import json
import time
import requests


class NekaPayClient:
    def __init__(self, api_key, api_secret, base_url='https://nekapaie.com/api/v1'):
        self.api_key = api_key
        self.api_secret = api_secret
        self.base_url = base_url

    def _sign(self, body):
        ts = str(int(time.time()))
        sig = hmac.new(
            self.api_secret.encode(),
            (ts + body).encode(),
            hashlib.sha256
        ).hexdigest()
        return ts, sig

    def request(self, method, path, body=None):
        body_str = json.dumps(body, separators=(',', ':')) if body else ''
        ts, sig = self._sign(body_str)

        r = requests.request(method, self.base_url + path,
            data=body_str,
            headers={
                'Content-Type': 'application/json',
                'X-NekaPay-Key': self.api_key,
                'X-NekaPay-Timestamp': ts,
                'X-NekaPay-Signature': sig,
            }
        )
        return r.status_code, r.json()

    def cash_in(self, **payload): return self.request('POST', '/payments/cashin', payload)
    def cash_out(self, **payload): return self.request('POST', '/payments/cashout', payload)
    def get_status(self, tx_id): return self.request('GET', f'/payments/{tx_id}')
    def get_balances(self): return self.request('GET', '/balances')


# Utilisation
client = NekaPayClient(
    os.environ['NEKAPAY_API_KEY'],
    os.environ['NEKAPAY_API_SECRET']
)

status, data = client.cash_in(
    merchant_order_id='order_' + str(int(time.time())),
    amount=50000,
    currency='XOF',
    customer_msisdn='+22376123456',
    country_code='ML',
)

if status == 202:
    print('Payment URL:', data['data']['payment_url'])

Webhook handler (Flask)

PYTHONfrom flask import Flask, request
import os, hmac, hashlib, time, json

app = Flask(__name__)
SECRET = os.environ['NEKAPAY_API_SECRET']

@app.route('/webhooks/nekapay', methods=['POST'])
def webhook():
    ts = request.headers.get('X-NekaPay-Timestamp', '')
    sig = request.headers.get('X-NekaPay-Signature', '')
    body = request.get_data(as_text=True)

    if not ts or abs(time.time() - int(ts)) > 300:
        return 'replay', 401

    expected = hmac.new(SECRET.encode(), (ts + body).encode(), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, sig):
        return 'signature', 401

    event = json.loads(body)
    if event['status'] == 'SUCCESS':
        mark_paid(event['merchant_order_id'])
    elif event['status'] in ('FAILED', 'EXPIRED'):
        mark_failed(event['merchant_order_id'])

    return {'received': True}, 200

Client Java (HttpClient)

JAVAimport java.net.URI;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.HexFormat;

public class NekaPayClient {
    private final String apiKey, apiSecret, baseUrl;
    private final HttpClient http = HttpClient.newHttpClient();

    public NekaPayClient(String apiKey, String apiSecret, String baseUrl) {
        this.apiKey = apiKey;
        this.apiSecret = apiSecret;
        this.baseUrl = baseUrl;
    }

    private String sign(String ts, String body) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
        byte[] hash = mac.doFinal((ts + body).getBytes(StandardCharsets.UTF_8));
        return HexFormat.of().formatHex(hash);
    }

    public HttpResponse<String> request(String method, String path, String body) throws Exception {
        String ts = String.valueOf(System.currentTimeMillis() / 1000);
        String sig = sign(ts, body == null ? "" : body);

        var builder = HttpRequest.newBuilder()
            .uri(URI.create(baseUrl + path))
            .header("Content-Type", "application/json")
            .header("X-NekaPay-Key", apiKey)
            .header("X-NekaPay-Timestamp", ts)
            .header("X-NekaPay-Signature", sig);

        if (body != null) {
            builder.method(method, HttpRequest.BodyPublishers.ofString(body));
        } else {
            builder.method(method, HttpRequest.BodyPublishers.noBody());
        }

        return http.send(builder.build(), HttpResponse.BodyHandlers.ofString());
    }

    public static void main(String[] args) throws Exception {
        var client = new NekaPayClient(
            System.getenv("NEKAPAY_API_KEY"),
            System.getenv("NEKAPAY_API_SECRET"),
            "https://nekapaie.com/api/v1"
        );

        String body = """
            {"merchant_order_id":"order_001","amount":50000,
             "currency":"XOF","customer_msisdn":"+22376123456","country_code":"ML"}""";

        var response = client.request("POST", "/payments/cashin", body);
        System.out.println("Status: " + response.statusCode());
        System.out.println("Body: " + response.body());
    }
}

Client Go minimal

GOpackage main

import (
    "bytes"
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

type NekaPay struct {
    APIKey    string
    APISecret string
    BaseURL   string
}

func (c *NekaPay) sign(ts, body string) string {
    h := hmac.New(sha256.New, []byte(c.APISecret))
    h.Write([]byte(ts + body))
    return hex.EncodeToString(h.Sum(nil))
}

func (c *NekaPay) Request(method, path string, payload interface{}) (int, map[string]interface{}, error) {
    var bodyBytes []byte
    if payload != nil {
        bodyBytes, _ = json.Marshal(payload)
    }
    bodyStr := string(bodyBytes)

    ts := strconv.FormatInt(time.Now().Unix(), 10)
    sig := c.sign(ts, bodyStr)

    req, _ := http.NewRequest(method, c.BaseURL+path, bytes.NewReader(bodyBytes))
    req.Header.Set("Content-Type", "application/json")
    req.Header.Set("X-NekaPay-Key", c.APIKey)
    req.Header.Set("X-NekaPay-Timestamp", ts)
    req.Header.Set("X-NekaPay-Signature", sig)

    resp, err := http.DefaultClient.Do(req)
    if err != nil { return 0, nil, err }
    defer resp.Body.Close()

    raw, _ := io.ReadAll(resp.Body)
    var data map[string]interface{}
    json.Unmarshal(raw, &data)

    return resp.StatusCode, data, nil
}

func main() {
    client := &NekaPay{
        APIKey:    os.Getenv("NEKAPAY_API_KEY"),
        APISecret: os.Getenv("NEKAPAY_API_SECRET"),
        BaseURL:   "https://nekapaie.com/api/v1",
    }

    status, data, err := client.Request("POST", "/payments/cashin", map[string]interface{}{
        "merchant_order_id": "order_001",
        "amount":            50000,
        "currency":          "XOF",
        "customer_msisdn":   "+22376123456",
        "country_code":      "ML",
    })

    if err != nil { panic(err) }
    fmt.Printf("Status: %d\nResponse: %+v\n", status, data)
}

Bash + cURL (script complet)

BASH#!/usr/bin/env bash
set -euo pipefail

API_KEY="${NEKAPAY_API_KEY:?Missing NEKAPAY_API_KEY}"
API_SECRET="${NEKAPAY_API_SECRET:?Missing NEKAPAY_API_SECRET}"
BASE_URL="${NEKAPAY_BASE_URL:-https://nekapaie.com/api/v1}"

call_api() {
  local method="$1" path="$2" body="${3:-}"
  local ts sig
  ts=$(date +%s)
  sig=$(printf '%s%s' "$ts" "$body" | openssl dgst -sha256 -hmac "$API_SECRET" | cut -d' ' -f2)

  curl -s -X "$method" "$BASE_URL$path" \
    -H "Content-Type: application/json" \
    -H "X-NekaPay-Key: $API_KEY" \
    -H "X-NekaPay-Timestamp: $ts" \
    -H "X-NekaPay-Signature: $sig" \
    --data "$body"
}

# 1. Cash-In
BODY='{"merchant_order_id":"order_001","amount":50000,"currency":"XOF","customer_msisdn":"+22376123456","country_code":"ML"}'
TX=$(call_api POST "/payments/cashin" "$BODY" | jq -r '.data.transaction_id')
echo "Transaction créée : $TX"

# 2. Statut
sleep 2
call_api GET "/payments/$TX" | jq

# 3. Soldes
call_api GET "/balances" | jq
SDK officiels : les clients ci-dessus sont des exemples self-contained. Des SDK officiels packagés (Composer, npm, PyPI, Maven, Go modules) sont prévus dans la prochaine release. Pour l'instant, copiez ces snippets dans votre projet.