Skip to main content

SDK Python moderne (Async/Sync) pour l'API de paiement Shwary.

Project description

Shwary Python SDK

PyPI version Python versions License: MIT

Shwary Python est une bibliothèque cliente moderne, asynchrone et performante pour l'intégration de l'API Shwary. Elle permet d'initier des paiements Mobile Money en RDC, au Kenya et en Ouganda avec une validation stricte des données avant l'envoi.

  • Retry automatique : Les erreurs réseau transitoires (timeout, connexion) sont automatiquement retentées avec backoff exponentiel
  • Types stricts : TypedDict pour les réponses (PaymentResponse, TransactionResponse, WebhookPayload)
  • Logging structuré : Les logs s'écrivent dans la racine du projet utilisateur (logs/shwary.log) avec rotation automatique
  • Base class partagée : Élimination de la duplication sync/async pour une maintenabilité meilleure
  • 429 Rate Limiting : Nouvelle exception RateLimitingError pour gérer les dépassements de débit
  • Docstrings améliorées : Documentation complète avec exemples d'utilisation
  • Tests étendus : Couverture complète des retries, erreurs et validations
  • Modèles de réponse : Schemas Pydantic pour les webhooks et transactions
  • Correction des bugs : Correction des imports et des bugs mineurs

Caractéristiques

  • Gestion d'erreurs native : Pas besoin de vérifier les status_code manuellement. Le SDK lève des exceptions explicites (AuthenticationError, ValidationError, etc.).
  • Async-first : Construit sur httpx pour des performances optimales (Pooling de connexions).
  • Dual-mode : Support complet des modes Synchrone et Asynchrone.
  • Validation Robuste : Vérification des numéros (E.164) et des montants minimums (ex: 2900 CDF pour la RDC).
  • Retry automatique : Retries intelligentes sur erreurs réseau transitoires avec backoff exponentiel.
  • Type-safe : Basé sur Pydantic V2 pour une autocomplétion parfaite dans votre IDE.
  • Ultra-rapide : Optimisé avec uv et __slots__ pour minimiser l'empreinte mémoire.
  • Logging structuré : Logs dans la racine du projet utilisateur sans données sensibles.

Installation

Avec uv (recommandé) :

uv add shwary-python

Ou avec pip

pip install shwary-python

Utilisation Rapide

Mode Synchrone (Flask, Django, scripts)

from shwary import Shwary, ValidationError, AuthenticationError

with Shwary(
    merchant_id="your-merchant-id",
    merchant_key="your-merchant-key",
    is_sandbox=True
) as client:
    try:
        payment = client.initiate_payment(
            country="DRC",
            amount=5000,
            phone_number="+243972345678",
            callback_url="https://yoursite.com/webhooks/shwary"
        )
        print(f"Transaction: {payment['id']} - {payment['status']}")
    except ValidationError as e:
        print(f"Erreur validation: {e}")
    except AuthenticationError:
        print("Credentials invalides")

Mode Asynchrone (FastAPI, Quart, aiohttp)

import asyncio
from shwary import ShwaryAsync

async def main():
    async with ShwaryAsync(
        merchant_id="your-merchant-id",
        merchant_key="your-merchant-key",
        is_sandbox=True
    ) as client:
        try:
            payment = await client.initiate_payment(
                country="DRC",
                amount=5000,
                phone_number="+243972345678"
            )
            print(f"Transaction: {payment['id']}")
        except Exception as e:
            print(f"Erreur: {e}")

asyncio.run(main())

Validation par pays

Le SDK applique les règles métiers de Shwary localement pour économiser des appels réseau :

Pays Code Devise Montant Min. Préfixe
RDC DRC CDF 2900 +243
Kenya KE KES > 0 +254
Ouganda UG UGX > 0 +256

Gestion des Erreurs

Le SDK transforme les erreurs HTTP en exceptions Python. Vous n'avez pas besoin de vérifier manuellement les codes de statut – gérez simplement les exceptions :

from shwary import (
    Shwary,
    ValidationError,         # Données invalides
    AuthenticationError,      # Credentials invalides
    InsufficientFundsError,  # Solde insuffisant
    RateLimitingError,       # Trop de requêtes
    ShwaryAPIError,          # Erreur serveur
)

try:
    payment = client.initiate_payment(...)
    
except ValidationError as e:
    # Format téléphone invalide, montant trop bas, etc.
    print(f"Erreur validation: {e}")
    
except AuthenticationError:
    # merchant_id / merchant_key incorrects
    print("Credentials invalides - vérifiez votre configuration")
    
except InsufficientFundsError:
    # Solde marchand insuffisant
    print("Solde insuffisant - rechargez votre compte")
    
except RateLimitingError:
    # Trop de requêtes (429) - implémentez un backoff
    print("Rate limited - réessayez dans quelques secondes")
    
except ShwaryAPIError as e:
    # Autres erreurs API (500, timeout, etc.)
    print(f"Erreur API {e.status_code}: {e.message}")

Webhooks et Callbacks

Lorsqu'une transaction change d'état, Shwary envoie une notification JSON à votre callback_url. Voici comment la traiter :

from shwary import WebhookPayload

@app.post("/webhooks/shwary")
async def handle_webhook(payload: WebhookPayload):
    """Shwary envoie une notification de changement d'état."""
    
    if payload.status == "completed":
        # Transaction réussie
        print(f"Paiement {payload.id} reçu ({payload.amount})")
        # La livrez le service ici
    
    elif payload.status == "failed":
        # Transaction échouée
        print(f"Paiement {payload.id} échoué")
        # Notifiez le client
    
    return {"status": "ok"}

Pour plus d'exemples (FastAPI, Flask), consultez le dossier examples/.

Exemples Complets

Le SDK inclut des exemples d'intégration complets :

Scripts simples

Frameworks web

Consultez examples/README.md pour plus de détails et comment les exécuter.

Logging

Le SDK configure automatiquement le logging à la racine du projet utilisateur :

from shwary import configure_logging
import logging

# Mode debug pour voir toutes les requêtes/réponses
configure_logging(log_level=logging.DEBUG)

# Les logs s'écrivent dans :
# - Console (STDOUT)
# - Fichier: ./logs/shwary.log (rotation automatique à 10MB)

Sans données sensibles (clés API masquées).

Exemples d'intégration

FastAPI avec WebhooksShwary

from fastapi import FastAPI, Request, HTTPException
from shwary import ShwaryAsync, WebhookPayload
import logging

app = FastAPI()

# Configuration du SDK Shwary
shwary = ShwaryAsync(
    merchant_id="your-merchant-id",
    merchant_key="your-merchant-key",
    is_sandbox=True
)

@app.post("/api/payments/initiate")
async def initiate_payment(phone: str, amount: float, country: str = "DRC"):
    """
    Initialise un paiement Shwary.
    
    Query params:
    - phone: numéro au format E.164 (ex: +243972345678)
    - amount: montant de la transaction
    - country: DRC, KE, UG (défaut: DRC)
    """
    try:
        async with shwary as client:
            payment = await client.initiate_payment(
                country=country,
                amount=amount,
                phone_number=phone,
                callback_url="https://yourapi.com/api/webhooks/shwary"
            )
        
        return {
            "success": True,
            "transaction_id": payment["id"],
            "status": payment["status"]
        }
    
    except ValidationError as e:
        raise HTTPException(status_code=400, detail=str(e))
    except AuthenticationError:
        raise HTTPException(status_code=401, detail="Shwary credentials invalid")
    except InsufficientFundsError:
        raise HTTPException(status_code=402, detail="Insufficient balance")
    except Exception as e:
        logging.error(f"Payment init failed: {e}")
        raise HTTPException(status_code=500, detail="Payment initiation failed")


@app.post("/api/webhooks/shwary")
async def handle_shwary_webhook(payload: WebhookPayload):
    """
    Reçoit les notifications de changement d'état de transactions.
    
    Shwary envoie une notification JSON lorsqu'une transaction change d'état.
    """
    logging.info(f"Webhook received: {payload.id} -> {payload.status}")
    
    if payload.status == "completed":
        # Transaction réussie - livrez le service
        logging.info(f"Payment completed: {payload.id}")
        # await deliver_service(payload.id)
    
    elif payload.status == "failed":
        # Transaction échouée
        logging.warning(f"Payment failed: {payload.id}")
        # await notify_user_failure(payload.id)
    
    return {"status": "ok"}


@app.get("/api/transactions/{transaction_id}")
async def get_transaction_status(transaction_id: str):
    """
    Récupère le statut d'une transaction.
    """
    try:
        async with shwary as client:
            tx = await client.get_transaction(transaction_id)
        
        return {
            "id": tx["id"],
            "status": tx["status"],
            "amount": tx["amount"]
        }
    
    except ShwaryAPIError as e:
        if e.status_code == 404:
            raise HTTPException(status_code=404, detail="Transaction not found")
        raise HTTPException(status_code=500, detail="Error fetching transaction")

Flask avec Shwary

from flask import Flask, request, jsonify
from shwary import Shwary, ValidationError, AuthenticationError, ShwaryAPIError
import logging

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)

# Client Shwary (synchrone pour Flask)
shwary_client = Shwary(
    merchant_id="your-merchant-id",
    merchant_key="your-merchant-key",
    is_sandbox=True
)

@app.route("/api/payments/initiate", methods=["POST"])
def initiate_payment():
    """
    Initialise un paiement Shwary.
    
    Body JSON:
    {
        "phone": "+243972345678",
        "amount": 5000,
        "country": "DRC"
    }
    """
    data = request.get_json()
    
    try:
        phone = data.get("phone")
        amount = data.get("amount")
        country = data.get("country", "DRC")
        
        if not all([phone, amount]):
            return jsonify({"error": "Missing phone or amount"}), 400
        
        payment = shwary_client.initiate_payment(
            country=country,
            amount=amount,
            phone_number=phone,
            callback_url="https://yourapi.com/api/webhooks/shwary"
        )
        
        return jsonify({
            "success": True,
            "transaction_id": payment["id"],
            "status": payment["status"]
        }), 200
    
    except ValidationError as e:
        app.logger.warning(f"Validation error: {e}")
        return jsonify({"error": str(e)}), 400
    
    except AuthenticationError as e:
        app.logger.error(f"Auth error: {e}")
        return jsonify({"error": "Shwary authentication failed"}), 401
    
    except ShwaryAPIError as e:
        app.logger.error(f"API error: {e}")
        return jsonify({"error": f"Shwary error: {e.message}"}), e.status_code
    
    except Exception as e:
        app.logger.error(f"Unexpected error: {e}")
        return jsonify({"error": "Internal server error"}), 500


@app.route("/api/webhooks/shwary", methods=["POST"])
def handle_shwary_webhook():
    """
    Reçoit les notifications de Shwary.
    """
    data = request.get_json()
    
    transaction_id = data.get("id")
    status = data.get("status")
    
    app.logger.info(f"Shwary webhook: {transaction_id} -> {status}")
    
    if status == "completed":
        # Transaction réussie
        app.logger.info(f"Payment completed: {transaction_id}")
        # deliver_service(transaction_id)
    
    elif status == "failed":
        # Transaction échouée
        app.logger.warning(f"Payment failed: {transaction_id}")
        # notify_user_failure(transaction_id)
    
    return jsonify({"status": "ok"}), 200


@app.route("/api/transactions/<transaction_id>", methods=["GET"])
def get_transaction_status(transaction_id):
    """Récupère le statut d'une transaction."""
    try:
        tx = shwary_client.get_transaction(transaction_id)
        
        return jsonify({
            "id": tx["id"],
            "status": tx["status"],
            "amount": tx["amount"]
        }), 200
    
    except ShwaryAPIError as e:
        if e.status_code == 404:
            return jsonify({"error": "Transaction not found"}), 404
        
        app.logger.error(f"Error fetching transaction: {e}")
        return jsonify({"error": "Server error"}), 500


@app.teardown_appcontext
def shutdown_shwary(exception=None):
    """Ferme le client Shwary à l'arrêt."""
    shwary_client.close()


if __name__ == "__main__":
    app.run(debug=False, host="0.0.0.0", port=5000)

Configuration du Logging

Le SDK configure automatiquement le logging à la racine du projet. Pour augmenter le verbosity :

import logging
from shwary import configure_logging

# Mode debug (affiche toutes les requêtes/réponses)
configure_logging(log_level=logging.DEBUG)

# Les logs sont écrits dans :
# - Console (STDOUT)
# - Fichier: ./shwary.log (rotation automatique à 10MB)

Les fichiers de log contiennent les détails des requêtes/réponses (sans données sensibles comme les clés API).

Développement

Pour contribuer au SDK, consultez le fichier CONTRIBUTING.md

Licence

Distribué sous la licence MIT. Voir License: MIT pour plus d'informations.

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

shwary_python-2.0.4.tar.gz (21.4 kB view details)

Uploaded Source

Built Distribution

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

shwary_python-2.0.4-py3-none-any.whl (30.9 kB view details)

Uploaded Python 3

File details

Details for the file shwary_python-2.0.4.tar.gz.

File metadata

  • Download URL: shwary_python-2.0.4.tar.gz
  • Upload date:
  • Size: 21.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for shwary_python-2.0.4.tar.gz
Algorithm Hash digest
SHA256 022e5db968703d319ae73deab6063d3e3ccb4fdc5d2a25a1960909147efdf187
MD5 f9f38fd626ce2fa56e4033644d769910
BLAKE2b-256 4a9176432e119cf6ee6522188ff406f5e392277dd5a027b49dd2e7dfdffc25bc

See more details on using hashes here.

File details

Details for the file shwary_python-2.0.4-py3-none-any.whl.

File metadata

  • Download URL: shwary_python-2.0.4-py3-none-any.whl
  • Upload date:
  • Size: 30.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for shwary_python-2.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 f0a196be8930effe63e2bf0a1a534d84e7b4a6ee67e3a7790190a5b311a4dd92
MD5 97761373b6ca7f8f3284523107cafcf4
BLAKE2b-256 fd84456418c3b8c23f312eef6299d6c471d8655f0a591166386a4ddc7b7dfd28

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