Bonnes pratiques
Ce guide présente les meilleures pratiques pour utiliser la bibliothèque Faso Arzeka Payment de manière sûre, performante et maintenable.
Sécurité
1. Protéger les credentials
❌ Ne jamais hard-coder les credentials
# ❌ MAUVAIS
client = ArzekaPayment()
client.authenticate("username", "password")
✅ Utiliser des variables d’environnement
# ✅ BON
import os
client = ArzekaPayment()
client.authenticate(
os.getenv('ARZEKA_USERNAME'),
os.getenv('ARZEKA_PASSWORD')
)
2. Ne jamais logger les secrets
# ❌ MAUVAIS
logger.info(f"Authenticating with password: {password}")
# ✅ BON
logger.info("Authenticating...")
# ❌ MAUVAIS
logger.debug(f"Hash secret: {hash_secret}")
# ✅ BON
logger.debug("Hash secret: ***")
3. Utiliser HTTPS en production
# ✅ BON - Production
client = ArzekaPayment(
base_url="https://pwg.fasoarzeka.com/",
verify_ssl=True
)
# ⚠️ Acceptable seulement en test
client = ArzekaPayment(
base_url="https://pwg-test.fasoarzeka.com/",
verify_ssl=False # Uniquement en test !
)
4. Valider les webhooks
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.json
order_id = data.get('mappedOrderId')
# ✅ Toujours vérifier auprès d'Arzeka
verified_status = check_payment(order_id)
if verified_status['status'] == 'COMPLETED':
# Traiter le paiement
process_order(order_id)
return jsonify({'status': 'received'}), 200
Performance
1. Réutiliser les instances client
❌ Créer un nouveau client à chaque requête
# ❌ MAUVAIS - Crée une nouvelle session à chaque fois
def make_payment():
client = ArzekaPayment()
client.authenticate("username", "password")
response = client.initiate_payment(...)
client.close()
return response
✅ Réutiliser le même client
# ✅ BON - Réutilise la même session
client = ArzekaPayment()
client.authenticate("username", "password")
def make_payment():
return client.initiate_payment(...)
# Faire plusieurs paiements
payment1 = make_payment()
payment2 = make_payment()
# Fermer à la fin
client.close()
2. Utiliser le Context Manager
# ✅ OPTIMAL
with ArzekaPayment() as client:
client.authenticate("username", "password")
# Faire plusieurs opérations
for order in orders:
response = client.initiate_payment(...)
3. Configurer un timeout approprié
# ⚠️ Trop court - risque de timeout
client = ArzekaPayment(timeout=5)
# ✅ BON - Équilibré
client = ArzekaPayment(timeout=30)
# ⚠️ Trop long - bloque l'application
client = ArzekaPayment(timeout=300)
4. Activer le retry automatique
# ✅ BON
client = ArzekaPayment(
max_retries=3,
retry_delay=2
)
Fiabilité
1. Toujours gérer les erreurs
from fasoarzeka import (
ArzekaPayment,
ArzekaValidationError,
ArzekaAPIError,
ArzekaConnectionError
)
# ✅ BON
try:
response = client.initiate_payment(...)
except ArzekaValidationError as e:
logger.error(f"Validation error: {e}")
return None
except ArzekaAPIError as e:
logger.error(f"API error: {e}")
return None
except ArzekaConnectionError as e:
logger.error(f"Connection error: {e}")
# Réessayer plus tard
queue_for_retry(payment_data)
return None
2. Utiliser la réauthentification automatique
# ✅ BON - Pas besoin de gérer l'expiration du token
client = ArzekaPayment()
client.authenticate("username", "password")
# Le client se réauthentifie automatiquement si nécessaire
response = client.initiate_payment(...)
3. Valider les données avant envoi
from fasoarzeka.utils import validate_phone_number, format_msisdn
def process_payment(amount, phone, ...):
# ✅ Validation en amont
if amount < 100:
raise ValueError("Montant minimum : 100 FCFA")
if not validate_phone_number(phone):
raise ValueError("Numéro de téléphone invalide")
# Formatage
phone = format_msisdn(phone)
# Envoyer à l'API
response = client.initiate_payment(
amount=amount,
additional_info={
"mobile": phone,
# ...
},
# ...
)
4. Implémenter l’idempotence
# ✅ BON - Vérifier si déjà traité
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.json
order_id = data.get('mappedOrderId')
# Vérifier si déjà traité
if is_already_processed(order_id):
logger.info(f"Order {order_id} already processed")
return jsonify({'status': 'already_processed'}), 200
# Traiter le paiement
process_payment(order_id)
mark_as_processed(order_id)
return jsonify({'status': 'success'}), 200
Structure du code
1. Organiser les configurations
# config.py
import os
from dataclasses import dataclass
@dataclass
class ArzekaConfig:
username: str = os.getenv('ARZEKA_USERNAME', '')
password: str = os.getenv('ARZEKA_PASSWORD', '')
merchant_id: str = os.getenv('ARZEKA_MERCHANT_ID', '')
hash_secret: str = os.getenv('ARZEKA_HASH_SECRET', '')
base_url: str = os.getenv('ARZEKA_BASE_URL', 'https://pwg-test.fasoarzeka.com/')
webhook_url: str = os.getenv('ARZEKA_WEBHOOK_URL', '')
return_url: str = os.getenv('ARZEKA_RETURN_URL', '')
# Utilisation
from config import ArzekaConfig
config = ArzekaConfig()
client = ArzekaPayment(base_url=config.base_url)
client.authenticate(config.username, config.password)
2. Créer des wrappers métier
# payment_service.py
from fasoarzeka import ArzekaPayment
from config import ArzekaConfig
import logging
logger = logging.getLogger(__name__)
class PaymentService:
def __init__(self):
self.config = ArzekaConfig()
self.client = ArzekaPayment(base_url=self.config.base_url)
self.client.authenticate(
self.config.username,
self.config.password
)
def create_payment(self, amount: int, customer_phone: str, order_id: str):
"""Créer un paiement avec validation et logging"""
try:
logger.info(f"Creating payment for order {order_id}")
response = self.client.initiate_payment(
amount=amount,
merchant_id=self.config.merchant_id,
additional_info={
"mobile": customer_phone,
# ...
},
mapped_order_id=order_id,
hash_secret=self.config.hash_secret,
link_for_update_status=self.config.webhook_url,
link_back_to_calling_website=self.config.return_url
)
logger.info(f"Payment created: {response['mappedOrderId']}")
return response
except Exception as e:
logger.error(f"Payment creation failed: {e}")
raise
def get_payment_status(self, order_id: str):
"""Récupérer le statut d'un paiement"""
try:
return self.client.check_payment(order_id)
except Exception as e:
logger.error(f"Status check failed for {order_id}: {e}")
raise
def close(self):
"""Fermer le client"""
self.client.close()
3. Séparer les responsabilités
# validators.py
def validate_payment_data(data):
"""Valider les données de paiement"""
if data['amount'] < 100:
raise ValueError("Montant minimum : 100 FCFA")
# ...
# formatters.py
def format_payment_response(response):
"""Formater la réponse pour l'API publique"""
return {
'order_id': response['mappedOrderId'],
'payment_url': response['url'],
'status': response['status']
}
# services.py
def process_payment(payment_data):
"""Traiter un paiement"""
validate_payment_data(payment_data)
response = payment_service.create_payment(
amount=payment_data['amount'],
customer_phone=payment_data['phone'],
order_id=payment_data['order_id']
)
return format_payment_response(response)
Logging
1. Configurer le logging correctement
import logging
# Configuration de base
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('arzeka.log'),
logging.StreamHandler()
]
)
2. Logger les événements importants
logger = logging.getLogger(__name__)
# ✅ BON
logger.info(f"Payment initiated for order {order_id}")
logger.info(f"Payment completed: {order_id}")
logger.warning(f"Token will expire in 5 minutes")
logger.error(f"Payment failed: {order_id}", exc_info=True)
3. Éviter le logging excessif
# ❌ MAUVAIS - Trop verbeux
logger.debug("Entering function")
logger.debug("Validating data")
logger.debug("Data validated")
logger.debug("Calling API")
logger.debug("API called")
# ✅ BON - Pertinent
logger.info(f"Processing payment for order {order_id}")
logger.info(f"Payment successful: {order_id}")
Tests
1. Tester avec mocks
import unittest
from unittest.mock import patch, MagicMock
from fasoarzeka import ArzekaPayment
class TestPaymentService(unittest.TestCase):
@patch('fasoarzeka.ArzekaPayment')
def test_create_payment(self, mock_client):
# Configurer le mock
mock_client.return_value.initiate_payment.return_value = {
'mappedOrderId': 'ORDER-001',
'url': 'https://...',
'status': 'PENDING'
}
# Tester
service = PaymentService()
response = service.create_payment(1000, "22670123456", "ORDER-001")
# Vérifier
self.assertEqual(response['mappedOrderId'], 'ORDER-001')
2. Utiliser l’environnement de test
# tests/conftest.py
import os
os.environ['ARZEKA_BASE_URL'] = 'https://pwg-test.fasoarzeka.com/'
os.environ['ARZEKA_USERNAME'] = 'test_user'
os.environ['ARZEKA_PASSWORD'] = 'test_pass'
Production
Checklist de déploiement
✅ Configuration
[ ] Variables d’environnement configurées
[ ] URL de production utilisée
[ ] Credentials de production configurés
[ ] SSL activé
✅ Logging
[ ] Niveau de log approprié (INFO ou WARNING)
[ ] Rotation des logs configurée
[ ] Logs centralisés (Sentry, CloudWatch, etc.)
✅ Monitoring
[ ] Métriques collectées
[ ] Alertes configurées
[ ] Dashboard créé
✅ Sécurité
[ ] Secrets jamais loggés
[ ] HTTPS utilisé
[ ] Webhooks validés
[ ] Rate limiting implémenté
✅ Performance
[ ] Connection pooling activé
[ ] Timeout configuré
[ ] Retry activé
[ ] Cache implémenté si applicable
✅ Tests
[ ] Tests unitaires passent
[ ] Tests d’intégration passent
[ ] Tests en environnement de pré-production
Voir aussi
Gestion des erreurs : Gestion des erreurs
Logging : Configuration du logging
Référence API : Référence API complète