Skip to content

SDK Python ​

Client Python officiel de l'API SOAMTAR : encaissez et envoyez des fonds avec Orange Money Sierra Leone, en leones (SLE).

  • Paquet : soamtar (version 1.1.0). Python 3.8+, bibliothèque standard uniquement.
  • Valeurs par défaut : devise SLE (USD et EUR aussi acceptés), fournisseur orange_money_sl.
  • Montants en chaînes à 2 décimales ("150.00"), en int ou en Decimal. Les float sont refusés.
  • Idempotency-Key ajouté automatiquement à chaque POST.
  • Vérification de la signature des webhooks (X-Soamtar-Signature).

Pas encore sur PyPI

Tant que le paquet n'est pas publié, installez-le depuis le dépôt du SDK (pip install <chemin-ou-url-git>).

Installation ​

bash
pip install soamtar

Configuration ​

python
from soamtar import SoamtarClient

client = SoamtarClient()                     # lit SOAMTAR_SECRET_KEY
client = SoamtarClient("sk_test_...")        # ou passez la clé secrète
client = SoamtarClient(timeout=30)           # secondes (15 par défaut)
client = SoamtarClient(sign_requests=True)   # signature facultative des requêtes
ArgumentDéfautRemarques
secret_keySOAMTAR_SECRET_KEYDoit commencer par sk_test_ ou sk_live_.
base_urlhttps://app.so-amtar.com/v1Lu dans SOAMTAR_API_URL, puis SOAMTAR_BASE_URL (ancien nom, déprécié).
timeout15En secondes.
sign_requestsFalseAjoute Soamtar-Timestamp / Soamtar-Signature à chaque POST (voir Signature des requêtes).

Utilisez une clé sk_test_… pendant l'intégration (simulation sur le même hôte, sans argent réel) et sk_live_… en production. Une clé secrète ne doit jamais se retrouver dans un navigateur ou une application mobile. client.is_test_mode() indique quelle clé est chargée.

Signature des requêtes ​

SoamtarClient(sign_requests=True) ajoute Soamtar-Timestamp: <unix> et Soamtar-Signature: v1=hex(HMAC_SHA256(secret_key, "<timestamp>.<METHODE>.<chemin>")) à chaque POST (chemin sans la barre oblique initiale, par exemple v1/payments ; horodatage à ±300 s). C'est un durcissement facultatif : la clé Bearer sur HTTPS vous authentifie déjà. Voir Authentification.

Encaisser un paiement ​

Le client reçoit une demande de paiement Orange Money sur son téléphone et la valide avec son code PIN.

python
payment = client.payments.create({
    "merchant_ref": "ORDER-1042",
    "amount": "150.00",              # Le 150.00
    "phone": "+23276000001",         # E.164 : +232 suivi de 8 chiffres
    "description": "Commande n° 1042",
    "callback_url": "https://shop.example.sl/webhooks/soamtar",  # facultatif
}, idempotency_key="order-1042")

print(payment.data["id"], payment.data["status"])   # ex. "pending"
print(payment.payment_url())                         # page de paiement hébergée

Page de paiement hébergée (SOAMTAR demande lui-même le numéro) : omettez phone, passez success_url / failed_url et redirigez vers payment.payment_url(). Sous Django, payment.to_django_redirect() renvoie directement la redirection.

callback_url (facultatif, HTTPS, paiements et payouts) est une URL de webhook propre à la transaction : SOAMTAR y envoie tous ses événements avec le même payload et la même X-Soamtar-Signature que pour vos endpoints enregistrés (signée avec le secret de votre plus ancien endpoint actif : enregistrez-en au moins un).

Suivez le résultat par webhook, ou interrogez l'API :

python
client.payments.retrieve(payment.data["id"], wait=True)  # attend un statut final (long polling)
client.payments.verify(payment.data["id"])                # revérifie auprès d'Orange Money
client.payments.list(status="success", per_page=50)
client.payments.status("TXN-…")                          # par identifiant ou référence SOAMTAR (pas votre merchant_ref)

Envoyer de l'argent (payouts) ​

python
payout = client.payouts.create({
    "merchant_ref": "PAYOUT-77",
    "amount": "50.00",
    "recipient": {"phone": "+23276000001", "name": "Aminata Kamara"},
    "description": "Paiement fournisseur",
}, idempotency_key="payout-PAYOUT-77")

client.payouts.retrieve(payout.data["id"])
client.payouts.list(status="success")

Remboursements ​

Remboursez un paiement réussi, en totalité ou en partie : SOAMTAR recrédite le montant sur le numéro Orange Money du payeur et débite votre solde. La clé doit disposer du scope can_refund.

python
refund = client.refunds.create({
    "transaction_id": payment.data["id"],   # identifiant (ou référence) SOAMTAR du paiement
    "amount": "50.00",                      # omis : remboursement total du restant
    "reason": "customer_request",
    "description": "Article en rupture de stock",
}, idempotency_key="refund-order-1042-1")

print(refund.data["reference"], refund.data["status"])   # "REF-…", "processing"

client.refunds.retrieve(refund.data["reference"])        # identifiant ou référence
client.refunds.list(transaction_id=payment.data["id"], status="success")

Le remboursement est soumis dans la même requête : success avec une clé de test, processing avec Orange Money jusqu'à la confirmation d'Orange, failed (avec failure_reason) si le crédit est refusé. Le résultat arrive par refund.success / refund.failed, plus payment.refunded / payment.partially_refunded en cas de succès. Voir Remboursements pour l'objet et les codes d'erreur.

Solde ​

python
client.balance.retrieve()
# {"object": "balance", "currency": "SLE", "available": …, "pending": …, "reserved": …, "last_updated": …}

Webhooks ​

Les notifications partent vers les endpoints webhook que vous avez enregistrés (tableau de bord ou client.webhooks.create), signées avec le secret de chaque endpoint : X-Soamtar-Signature: t=<unix>,v1=<hex> (plus v2= pendant une rotation du secret), HMAC-SHA256(secret, "<t>.<corps brut>"), tolérance de 300 s.

Webhook.construct_event(payload, signature_header, secret) vérifie le corps brut et renvoie l'événement décodé, ou lève SignatureException.

Django ​

python
import os
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from soamtar import Webhook, SignatureException

@csrf_exempt
@require_POST
def soamtar_webhook(request):
    try:
        event = Webhook.construct_event(
            request.body,                                   # corps BRUT
            request.headers.get("X-Soamtar-Signature"),
            os.environ["SOAMTAR_WEBHOOK_SECRET"],
        )
    except SignatureException:
        return HttpResponse(status=400)

    if event["event"] == "payment.success":
        mark_order_paid(event["data"]["merchant_reference"])
    elif event["event"] in ("payment.failed", "payment.cancelled", "payment.expired"):
        mark_order_failed(event["data"]["merchant_reference"])
    elif event["event"] == "refund.success":
        mark_order_refunded(event["data"]["merchant_reference"], event["data"]["amount"])
    # webhook.test et le reste : répondez simplement 200
    return HttpResponse(status=200)

FastAPI ​

Le SDK est synchrone (HTTP de la bibliothèque standard) : déclarez avec def les routes qui appellent l'API, FastAPI les exécutera dans son pool de threads.

python
import os
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import RedirectResponse
from soamtar import SoamtarClient, Webhook, SignatureException

app = FastAPI()
client = SoamtarClient()

@app.post("/checkout")
def checkout(order_id: str, amount: str):
    payment = client.payments.create({
        "merchant_ref": order_id,
        "amount": amount,
        "success_url": "https://shop.example.sl/merci",
        "failed_url": "https://shop.example.sl/panier",
    }, idempotency_key=f"order-{order_id}")
    return RedirectResponse(payment.payment_url(), status_code=303)

@app.post("/webhooks/soamtar")
async def soamtar_webhook(request: Request):
    try:
        event = Webhook.construct_event(
            await request.body(),
            request.headers.get("X-Soamtar-Signature"),
            os.environ["SOAMTAR_WEBHOOK_SECRET"],
        )
    except SignatureException:
        raise HTTPException(status_code=400)
    # traitez event["event"] / event["data"]
    return {"received": True}

Flask ​

python
import os
from flask import Flask, abort, request
from soamtar import Webhook, SignatureException

app = Flask(__name__)

@app.post("/webhooks/soamtar")
def soamtar_webhook():
    try:
        event = Webhook.construct_event(
            request.get_data(),
            request.headers.get("X-Soamtar-Signature"),
            os.environ["SOAMTAR_WEBHOOK_SECRET"],
        )
    except SignatureException:
        abort(400)
    return "", 200

Dédoublonnez sur event + data.id : un même événement peut arriver plusieurs fois.

Événements auxquels s'abonner : payment.success, payment.failed, payment.cancelled, payment.expired, payment.refunded, payment.partially_refunded, payout.success, payout.failed, payout.cancelled, refund.success, refund.failed, payout_request.submitted, payout_request.approved, payout_request.rejected, payout_request.completed, payout_request.failed. webhook.test est envoyé par client.webhooks.send_test() et par le bouton de test du tableau de bord, quels que soient vos abonnements. Voir Webhooks.

Gérer les endpoints ​

Avec une clé disposant du scope can_manage_webhooks :

python
client.webhooks.create("https://shop.example.sl/webhooks/soamtar")   # par défaut : payment.success/failed, payout.success/failed
client.webhooks.create("https://shop.example.sl/webhooks/soamtar", ["payment.success", "refund.success", "refund.failed"])  # idempotent par URL, renvoie le secret
client.webhooks.list()
client.webhooks.lookup("https://shop.example.sl/webhooks/soamtar")
client.webhooks.secret(endpoint_id)
client.webhooks.send_test()   # envoie un événement webhook.test à vos endpoints actifs

Alias dépréciés, toujours fonctionnels : client.webhooks.test() (utilisez send_test()), client.status.retrieve() (utilisez client.payments.status()).

Erreurs ​

python
from soamtar import ApiException, SoamtarException

try:
    client.payments.create({...})
except ApiException as e:
    print(e.status_code, e.error_code, e.message, e.errors)   # errors : messages par champ en 422
except SoamtarException as e:
    print(e)   # configuration, réseau ou saisie invalide (ex. montant en float)

Réessayez une erreur 5xx ou réseau avec la même idempotency_key ; ne renvoyez jamais une 4xx telle quelle.

Tester votre webhook ​

python
import hashlib, hmac, json, time
from soamtar import Webhook

def sign(payload: str, secret: str) -> str:
    t = int(time.time())
    v1 = hmac.new(secret.encode(), f"{t}.{payload}".encode(), hashlib.sha256).hexdigest()
    return f"t={t},v1={v1}"

def test_signature_valide():
    body = json.dumps({"event": "payment.success", "data": {"id": "tx_1"}})
    event = Webhook.construct_event(body, sign(body, "whsec_test"), "whsec_test")
    assert event["event"] == "payment.success"

SOAMTAR Payment API Documentation.