Skip to main content

SDK Python pour l'API CinetPay v1 — paiements et transferts mobile money en Afrique. Compatible Django, FastAPI, Flask et tout projet Python.

Project description

cinetpay-python

SDK Python pour l'API CinetPay v1 — paiements et transferts mobile money en Afrique.

Compatible Django, FastAPI, Flask et tout projet Python 3.10+.

Caractéristiques

  • Sync + Async : CinetPayClient et AsyncCinetPayClient
  • Multi-pays : credentials api_key / api_password par pays
  • Auto-détection : sandbox (sk_test_) vs production (sk_live_)
  • Token cache : JWT mis en cache 23h, thread-safe (stampede guard)
  • Validation : données validées avant envoi (montants, emails, URLs)
  • Webhook : vérification timing-safe (hmac.compare_digest)
  • Typé : type hints complets, py.typed (PEP 561), compatible mypy
  • Sécurisé : HTTPS obligatoire, credentials masqués dans repr(), SSRF protection

Installation

pip install cinetpay-python

Environnements

Préfixe clé API URL API Environnement
sk_test_... https://api.cinetpay.net Sandbox
sk_live_... https://api.cinetpay.co Production

Le SDK détecte automatiquement l'environnement à partir du préfixe de la clé.

Démarrage rapide

Synchrone

from cinetpay import CinetPayClient, ClientConfig, CountryCredentials, PaymentRequest
import os

client = CinetPayClient(ClientConfig(
    credentials={
        "CI": CountryCredentials(
            api_key=os.environ["CINETPAY_API_KEY_CI"],
            api_password=os.environ["CINETPAY_API_PASSWORD_CI"],
        ),
    },
    debug=True,
))

# Initialiser un paiement
payment = client.payment.initialize(
    PaymentRequest(
        currency="XOF",
        merchant_transaction_id="ORDER-001",
        amount=5000,
        lang="fr",
        designation="Achat en ligne",
        client_email="client@email.com",
        client_first_name="Jean",
        client_last_name="Dupont",
        success_url="https://monsite.com/success",
        failed_url="https://monsite.com/failed",
        notify_url="https://monsite.com/webhook",
        channel="PUSH",
    ),
    "CI",
)

print(payment.payment_url)  # Rediriger le client
print(payment.payment_token)  # Pour le Seamless frontend

Asynchrone

import asyncio
from cinetpay import AsyncCinetPayClient, ClientConfig, CountryCredentials

async def main():
    async with AsyncCinetPayClient(ClientConfig(
        credentials={
            "CI": CountryCredentials(
                api_key="sk_test_...",
                api_password="your_password",
            ),
        },
    )) as client:
        balance = await client.balance.get("CI")
        print(f"Solde: {balance.available_balance} {balance.currency}")

asyncio.run(main())

API

Paiement

# Initialiser
payment = client.payment.initialize(PaymentRequest(...), "CI")
print(payment.payment_url)
print(payment.payment_token)

# Vérifier le statut
status = client.payment.get_status("ORDER-001", "CI")
print(status.status)  # SUCCESS, FAILED, PENDING, ...
print(status.user.name)

Transfert

from cinetpay import TransferRequest

transfer = client.transfer.create(
    TransferRequest(
        currency="XOF",
        merchant_transaction_id="TR-001",
        phone_number="+2250707000001",
        amount=500,
        payment_method="OM_CI",
        reason="Remboursement",
        notify_url="https://monsite.com/webhook",
    ),
    "CI",
)
print(transfer.status)

# Vérifier le statut
status = client.transfer.get_status(transfer.transaction_id, "CI")

Solde

balance = client.balance.get("CI")
print(f"{balance.available_balance} {balance.currency}")

Webhook

from cinetpay import verify_notification, parse_notification

# Flask
@app.route("/webhook", methods=["POST"])
def webhook():
    payload = parse_notification(request.json)

    # Vérifier le token (timing-safe)
    expected = get_stored_notify_token(payload.merchant_transaction_id)
    if not verify_notification(expected, payload.notify_token):
        return "Invalid token", 401

    # Confirmer le statut
    status = client.payment.get_status(payload.transaction_id, "CI")
    if status.status == "SUCCESS":
        # Livrer la commande
        pass

    return "OK", 200
# FastAPI
@app.post("/webhook")
async def webhook(request: Request):
    body = await request.json()
    payload = parse_notification(body)

    if not verify_notification(stored_token, payload.notify_token):
        raise HTTPException(401, "Invalid token")

    status = await client.payment.get_status(payload.transaction_id, "CI")
    return {"status": status.status}
# Django
def webhook(request):
    import json
    payload = parse_notification(json.loads(request.body))

    if not verify_notification(stored_token, payload.notify_token):
        return HttpResponse(status=401)

    status = client.payment.get_status(payload.transaction_id, "CI")
    return HttpResponse("OK")

Configuration

from cinetpay import ClientConfig, CountryCredentials

config = ClientConfig(
    # Credentials par pays (obligatoire)
    credentials={
        "CI": CountryCredentials(api_key="sk_test_...", api_password="..."),
        "SN": CountryCredentials(api_key="sk_test_...", api_password="..."),
    },

    # URL de base (auto-détecté depuis le préfixe de la clé)
    # base_url="https://api.cinetpay.co",  # forcer la production

    # TTL du cache token en secondes (défaut: 82800 = 23h)
    token_ttl=82800,

    # Timeout des requêtes en secondes (défaut: 30.0)
    timeout=30.0,

    # Active les logs (défaut: False)
    debug=True,

    # Token store personnalisé (défaut: MemoryTokenStore)
    # token_store=RedisTokenStore(),
)

Token store Redis

import redis
from cinetpay import ClientConfig, CountryCredentials

class RedisTokenStore:
    def __init__(self):
        self.r = redis.Redis()

    def get(self, key: str) -> str | None:
        val = self.r.get(key)
        return val.decode() if val else None

    def set(self, key: str, value: str, ttl_seconds: int) -> None:
        self.r.setex(key, ttl_seconds, value)

    def delete(self, key: str) -> None:
        self.r.delete(key)

client = CinetPayClient(ClientConfig(
    credentials={"CI": CountryCredentials(...)},
    token_store=RedisTokenStore(),
))

Gestion des erreurs

from cinetpay import (
    CinetPayError,
    ApiError,
    AuthenticationError,
    NetworkError,
    ValidationError,
)

try:
    payment = client.payment.initialize(request, "CI")
except ValidationError as e:
    # Données invalides — avant tout appel réseau
    print(e)  # [amount] must be an integer between 100 and 2500000

except ApiError as e:
    # Erreur API CinetPay
    print(e.api_code)     # 1200
    print(e.api_status)   # TRANSACTION_EXIST
    print(e.description)  # La transaction existe déjà

except AuthenticationError:
    # Credentials invalides

except NetworkError as e:
    # Problème réseau
    print(e.cause)

except CinetPayError:
    # Catch-all pour toutes les erreurs du SDK

Utilitaires

from cinetpay import is_final_status, PAYMENT_METHODS_BY_COUNTRY, COUNTRY_CODES

# Vérifier si un statut est final
is_final_status("SUCCESS")   # True
is_final_status("PENDING")   # False

# Opérateurs par pays
PAYMENT_METHODS_BY_COUNTRY["CI"]  # ("OM_CI", "MOOV_CI", "MTN_CI", "WAVE_CI")

# Pays supportés
COUNTRY_CODES  # ("CI", "BF", "ML", "SN", "TG", "GN", "CM", "BJ", "CD", "NE")

# Révoquer un token
client.revoke_token("CI")
client.revoke_all_tokens()

Context Manager

# Sync
with CinetPayClient(config) as client:
    balance = client.balance.get("CI")

# Async
async with AsyncCinetPayClient(config) as client:
    balance = await client.balance.get("CI")

Sécurité

Protection des clés API

NE FAITES PAS                              FAITES
────────────────────────────────────────────────────────────────────────
api_key="clé-en-dur"                       api_key=os.environ["CINETPAY_API_KEY_CI"]
Mélanger sk_test_ et sk_live_              Utiliser le même env pour tous les pays
Commiter le .env dans git                  Ajouter .env dans .gitignore
print(credentials)                         Le repr() masque automatiquement les clés

Credentials masqués

creds = CountryCredentials(api_key="sk_test_abc", api_password="secret")
print(creds)  # CountryCredentials(api_key='***', api_password='***')
print(client)  # CinetPayClient(countries=['CI', 'SN'])

Autres protections

  • HTTPS obligatoire (sauf localhost)
  • SSRF : warning si le hostname n'est pas un domaine CinetPay connu
  • Erreurs sanitisées : les messages d'erreur d'authentification ne contiennent jamais les credentials
  • Token stampede guard : threading.Lock (sync) / asyncio.Lock (async) empêche les appels auth simultanés

Support

Pour toute question sur l'API CinetPay : support@cinetpay.com

Licence

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cinetpay_python-0.1.0.tar.gz (34.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cinetpay_python-0.1.0-py3-none-any.whl (44.6 kB view details)

Uploaded Python 3

File details

Details for the file cinetpay_python-0.1.0.tar.gz.

File metadata

  • Download URL: cinetpay_python-0.1.0.tar.gz
  • Upload date:
  • Size: 34.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for cinetpay_python-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ef83276277d81db633b17fce875d5cfbacfb183db12113c7851812313a3cacb5
MD5 4019bc25f38ecd9a13659c944699abd8
BLAKE2b-256 b79bdda5466cef39aae1f5d61feabf6eb4e4deb5604e89013c0dcb28a6a9117b

See more details on using hashes here.

File details

Details for the file cinetpay_python-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for cinetpay_python-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0ee57991befd4e5ca3e8dda7e984fc93f7f41b4939daac40a7436f0b3e369c1a
MD5 640191bb8bfa935d1394bba46331f464
BLAKE2b-256 242b7bf72d0f796a78fae27b74570dc68ce14721e251f323f42e4d68358f0e84

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page