Quand un marchand envoie une demande de paiement à 0fee.dev, quelque chose doit décider quel fournisseur la traite. Pas « Stripe ou PayPal » -- c'est un simple choix binaire. La vraie question est : pour un paiement Orange Money en Côte d'Ivoire, le système doit-il utiliser PaiementPro (priorité 1), PawaPay (priorité 2) ou Hub2 (priorité 3) ? Et si PaiementPro est en panne, doit-il automatiquement basculer vers PawaPay ? C'est le moteur de routage.
Le problème central
0fee.dev couvre 53+ fournisseurs dans 200+ pays. Pour de nombreuses méthodes de paiement, plusieurs fournisseurs peuvent traiter la même transaction. En Afrique francophone, un paiement Orange Money en Côte d'Ivoire peut être traité par PaiementPro, PawaPay, Hub2 ou BUI. Chacun a des frais différents, des historiques de fiabilité différents et des lacunes de couverture différentes.
Le moteur de routage doit :
- Découvrir quelles méthodes de paiement sont disponibles pour un pays donné.
- Sélectionner le meilleur fournisseur pour une méthode de paiement donnée.
- Basculer vers le fournisseur suivant si le premier échoue.
- Respecter les fournisseurs configurés et les identifiants du marchand.
Découverte des méthodes de paiement par pays
La première étape de tout paiement est de découvrir les méthodes de paiement disponibles. Cela est piloté par la table payin_methods :
sqlCREATE TABLE payin_methods (
id TEXT PRIMARY KEY,
code TEXT UNIQUE NOT NULL, -- ex. "PAYIN_ORANGE_CI"
name TEXT NOT NULL, -- ex. "Orange Money Ivory Coast"
country_code TEXT NOT NULL, -- ex. "CI"
currency TEXT NOT NULL, -- ex. "XOF"
type TEXT NOT NULL, -- ex. "mobile_money"
operator TEXT, -- ex. "Orange"
is_active INTEGER DEFAULT 1,
min_amount INTEGER,
max_amount INTEGER,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);Pour la Côte d'Ivoire (CI), cela renvoie :
| Code | Nom | Type | Opérateur | Devise |
|---|---|---|---|---|
PAYIN_ORANGE_CI | Orange Money Ivory Coast | mobile_money | Orange | XOF |
PAYIN_MTN_CI | MTN Mobile Money Ivory Coast | mobile_money | MTN | XOF |
PAYIN_WAVE_CI | Wave Ivory Coast | mobile_money | Wave | XOF |
PAYIN_MOOV_CI | Moov Money Ivory Coast | mobile_money | Moov | XOF |
PAYIN_CARD_GLOBAL | Card Payment | card | - | Multiple |
Sélection de fournisseur par priorité
Une fois la méthode de paiement choisie, le moteur de routage détermine quel fournisseur la traite. Le système de priorité à trois niveaux fonctionne ainsi :
| Priorité | Signification | Exemple |
|---|---|---|
| 1 | Fournisseur principal -- utilisé en premier | PaiementPro pour PAYIN_ORANGE_CI |
| 2 | Fournisseur secondaire -- utilisé si la priorité 1 échoue | PawaPay pour PAYIN_ORANGE_CI |
| 3 | Fournisseur tertiaire -- dernier recours | Hub2 pour PAYIN_ORANGE_CI |
La colonne provider_method_code est essentielle. Chaque fournisseur utilise ses propres codes internes pour la même méthode de paiement. PaiementPro appelle Orange Money en Côte d'Ivoire « OMCIV2 ». PawaPay l'appelle « ORANGE_CIV ». Hub2 l'appelle simplement « Orange » avec un paramètre pays. La table de routage mappe le code unifié (PAYIN_ORANGE_CI) vers le code interne de chaque fournisseur.
L'algorithme de routage
La fonction de routage principale interroge la table provider_routing, ordonne par priorité et renvoie le meilleur fournisseur disponible :
pythonasync def get_provider_for_unified_method(
payment_method: str,
app_id: str,
environment: str = "production",
skip_providers: list[str] = None
) -> dict | None:
skip_providers = skip_providers or []
with get_db() as conn:
routes = conn.execute(
"""
SELECT pr.provider_id, pr.provider_method_code, pr.priority
FROM provider_routing pr
WHERE pr.payin_method_code = ?
AND pr.environment = ?
AND pr.is_active = 1
AND pr.provider_id NOT IN ({})
ORDER BY pr.priority ASC
""".format(",".join("?" * len(skip_providers))),
(payment_method, environment, *skip_providers)
).fetchall()
if not routes:
return None
for route in routes:
has_creds = conn.execute(
"""
SELECT 1 FROM provider_credentials
WHERE app_id = ? AND provider_id = ?
AND environment = ? AND is_active = 1
""",
(app_id, route["provider_id"], environment)
).fetchone()
if has_creds:
return {
"provider_id": route["provider_id"],
"provider_method_code": route["provider_method_code"],
"priority": route["priority"],
}
return NoneLogique de repli
Quand un paiement échoue au niveau du fournisseur, le système peut réessayer avec le fournisseur suivant dans la chaîne :
pythonasync def initiate_payment_with_fallback(
payment_method: str,
payment_data: dict,
app_id: str,
environment: str,
max_attempts: int = 3
) -> tuple[InitPaymentResult, str]:
skip_providers = []
for attempt in range(max_attempts):
route = await get_provider_for_unified_method(
payment_method, app_id, environment,
skip_providers=skip_providers
)
if not route:
break
provider_id = route["provider_id"]
credentials = await get_decrypted_credentials(
app_id, provider_id, environment
)
provider = provider_registry.get_instance(
provider_id, credentials, app_id
)
payment_data["provider_method_code"] = route["provider_method_code"]
result = await provider.initiate_payment(payment_data)
if result.status != "failed":
return result, provider_id
skip_providers.append(provider_id)
return InitPaymentResult(
provider_ref="",
status="failed",
instructions="Aucun fournisseur disponible n'a pu traiter ce paiement"
), ""Considérez le repli en pratique pour un paiement PAYIN_ORANGE_CI :
- Tentative 1 : Essayer PaiementPro (priorité 1). PaiementPro renvoie une erreur de timeout.
- Tentative 2 : Ignorer PaiementPro, essayer PawaPay (priorité 2). PawaPay initie le paiement avec succès.
- Le marchand et le client ne savent jamais que PaiementPro a été tenté en premier.
La table de routage : 117 méthodes de paiement
La table de routage complète mappe 117 méthodes de paiement dans 30+ pays vers leurs configurations de fournisseurs. Le pattern est cohérent : pour l'Afrique de l'Ouest francophone (zone UEMOA), PaiementPro est généralement en priorité 1 car il offre les meilleurs tarifs pour le mobile money local. PawaPay couvre le plus large éventail de pays comme option secondaire. Hub2 sert de repli tertiaire.
Le moteur de routage comme levier commercial
Au-delà du routage technique, le système de priorité est un outil commercial. Si 0fee.dev négocie un meilleur tarif avec un nouveau fournisseur, ajuster la table de routage change quel fournisseur gère le trafic -- sans modification de code, sans intervention du marchand et sans temps d'arrêt. Une seule instruction UPDATE déplace le volume de paiements d'un fournisseur à un autre.
C'est la puissance d'un moteur de routage dans un orchestrateur de paiement. Il transforme la sélection de fournisseur d'une décision codée en dur en un processus configurable et piloté par les données, optimisable en continu.
Cet article fait partie de la série « Comment nous avons construit 0fee.dev ». 0fee.dev est un orchestrateur de paiement couvrant 53+ fournisseurs dans 200+ pays, construit par Juste A. GNIMAVO et Claude depuis Abidjan sans aucun ingénieur humain. Suivez la série pour l'histoire complète de la construction.