Back to 0fee
0fee

El motor inteligente de enrutamiento de pagos

Cómo 0fee.dev enruta pagos a través de 117 métodos y 53+ proveedores usando selección basada en prioridad con failover automático. Por Juste A. Gnimavo y Claude.

Thales & Claude | March 30, 2026 7 min 0fee
EN/ FR/ ES
routingpaymentsafricamobile-moneyarchitecture

Cuando un comerciante envía una solicitud de pago a 0fee.dev, algo tiene que decidir qué proveedor lo maneja. No "Stripe o PayPal" -- esa es una elección binaria simple. La verdadera pregunta es: para un pago de Orange Money en Costa de Marfil, ¿debería el sistema usar PaiementPro (prioridad 1), PawaPay (prioridad 2) o Hub2 (prioridad 3)? Y si PaiementPro está caído, ¿debería recurrir automáticamente a PawaPay? Este es el motor de enrutamiento.

El problema central

0fee.dev cubre más de 53 proveedores en más de 200 países. Para muchos métodos de pago, múltiples proveedores pueden manejar la misma transacción. En el África francófona, un pago de Orange Money en Costa de Marfil puede ser procesado por PaiementPro, PawaPay, Hub2 o BUI. Cada uno tiene diferentes tarifas, diferentes registros de fiabilidad y diferentes brechas de cobertura.

El motor de enrutamiento debe:

  1. Descubrir qué métodos de pago están disponibles para un país dado.
  2. Seleccionar el mejor proveedor para un método de pago dado.
  3. Recurrir al siguiente proveedor si el primero falla.
  4. Respetar los proveedores y credenciales configurados del comerciante.

Descubrimiento de métodos de pago basado en país

El primer paso en cualquier pago es descubrir qué métodos de pago están disponibles. Esto es impulsado por la tabla payin_methods:

sqlCREATE TABLE payin_methods (
    id TEXT PRIMARY KEY,
    code TEXT UNIQUE NOT NULL,     -- ej., "PAYIN_ORANGE_CI"
    name TEXT NOT NULL,            -- ej., "Orange Money Ivory Coast"
    country_code TEXT NOT NULL,    -- ej., "CI"
    currency TEXT NOT NULL,        -- ej., "XOF"
    type TEXT NOT NULL,            -- ej., "mobile_money"
    operator TEXT,                 -- ej., "Orange"
    is_active INTEGER DEFAULT 1,
    min_amount INTEGER,
    max_amount INTEGER,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
);

Cuando comienza una sesión de checkout, el frontend envía el país del cliente. El motor de enrutamiento consulta todos los métodos de pago activos para ese país:

pythonasync def get_payment_methods_for_country(
    country_code: str
) -> list[dict]:
    """
    Obtener todos los métodos de pago disponibles para un país.
    Retorna métodos ordenados por tipo (dinero móvil primero, luego tarjeta).
    """
    with get_db() as conn:
        methods = conn.execute(
            """
            SELECT code, name, type, operator, currency,
                   min_amount, max_amount
            FROM payin_methods
            WHERE country_code = ? AND is_active = 1
            ORDER BY
                CASE type
                    WHEN 'mobile_money' THEN 1
                    WHEN 'card' THEN 2
                    ELSE 3
                END,
                name
            """,
            (country_code.upper(),)
        ).fetchall()

    return [dict(m) for m in methods]

Para Costa de Marfil (CI), esto retorna:

CódigoNombreTipoOperadorMoneda
PAYIN_ORANGE_CIOrange Money Ivory Coastmobile_moneyOrangeXOF
PAYIN_MTN_CIMTN Mobile Money Ivory Coastmobile_moneyMTNXOF
PAYIN_WAVE_CIWave Ivory Coastmobile_moneyWaveXOF
PAYIN_MOOV_CIMoov Money Ivory Coastmobile_moneyMoovXOF
PAYIN_CARD_GLOBALCard Paymentcard-Múltiple

Selección de proveedor basada en prioridad

Una vez elegido un método de pago, el motor de enrutamiento determina qué proveedor lo maneja. Esta es la tabla provider_routing:

sqlCREATE TABLE provider_routing (
    id TEXT PRIMARY KEY,
    payin_method_code TEXT NOT NULL,   -- ej., "PAYIN_ORANGE_CI"
    provider_id TEXT NOT NULL,          -- ej., "paiementpro"
    provider_method_code TEXT,          -- ej., "OMCIV2"
    priority INTEGER NOT NULL DEFAULT 1,
    environment TEXT DEFAULT 'production',
    is_active INTEGER DEFAULT 1,
    UNIQUE(payin_method_code, provider_id, environment)
);

El sistema de prioridad de tres niveles funciona así:

PrioridadSignificadoEjemplo
1Proveedor principal -- usado primeroPaiementPro para PAYIN_ORANGE_CI
2Proveedor secundario -- usado si la prioridad 1 fallaPawaPay para PAYIN_ORANGE_CI
3Proveedor terciario -- último recursoHub2 para PAYIN_ORANGE_CI

La columna provider_method_code es esencial. Cada proveedor usa sus propios códigos internos para el mismo método de pago. PaiementPro llama a Orange Money en Costa de Marfil "OMCIV2". PawaPay lo llama "ORANGE_CIV". Hub2 simplemente lo llama "Orange" con un parámetro de país. La tabla de enrutamiento mapea el código unificado (PAYIN_ORANGE_CI) al código interno de cada proveedor.

El algoritmo de enrutamiento

La función central de enrutamiento consulta la tabla provider_routing, ordena por prioridad y devuelve el mejor proveedor disponible:

pythonasync def get_provider_for_unified_method(
    payment_method: str,
    app_id: str,
    environment: str = "production",
    skip_providers: list[str] = None
) -> dict | None:
    """
    Encontrar el mejor proveedor para un método de pago unificado.
    """
    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 None

La lógica del algoritmo:

  1. Consultar todas las entradas de enrutamiento para el método de pago, ordenadas por prioridad (ascendente).
  2. Excluir cualquier proveedor en skip_providers (usado para failover después de fallo).
  3. Para cada ruta, verificar si el comerciante tiene credenciales activas para ese proveedor.
  4. Devolver la primera coincidencia -- el proveedor de mayor prioridad que el comerciante puede usar.

Este diseño maneja una situación común: un comerciante podría no tener credenciales de PaiementPro. Aunque PaiementPro es prioridad 1 para PAYIN_ORANGE_CI, si el comerciante solo tiene credenciales de Hub2, el motor salta PaiementPro y devuelve Hub2.

Lógica de failover

Cuando un pago falla a nivel del proveedor, el sistema puede reintentar con el siguiente proveedor en la cadena:

pythonasync def initiate_payment_with_fallback(
    payment_method: str,
    payment_data: dict,
    app_id: str,
    environment: str,
    max_attempts: int = 3
) -> tuple[InitPaymentResult, str]:
    """
    Intentar iniciar un pago, recurriendo a proveedores de menor
    prioridad si el principal falla.
    """
    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="No available provider could process this payment"
    ), ""

Considera el failover en la práctica para un pago PAYIN_ORANGE_CI:

  1. Intento 1: Probar PaiementPro (prioridad 1). PaiementPro devuelve un error de timeout.
  2. Intento 2: Saltar PaiementPro, probar PawaPay (prioridad 2). PawaPay inicia exitosamente el pago.
  3. El comerciante y el cliente nunca saben que se intentó PaiementPro primero.

La tabla de enrutamiento: 117 métodos de pago

La tabla de enrutamiento completa mapea 117 métodos de pago en más de 30 países a sus configuraciones de proveedor. El patrón es consistente: para el África Occidental francófona (zona UEMOA), PaiementPro es típicamente prioridad 1 porque ofrece las mejores tarifas para dinero móvil local. PawaPay cubre la gama más amplia de países como opción secundaria. Hub2 sirve como fallback terciario.

Para África Oriental y Austral, PawaPay es típicamente el único proveedor configurado. Para pagos globales con tarjeta, Stripe es la única ruta.

El motor de enrutamiento como palanca de negocio

Más allá del enrutamiento técnico, el sistema de prioridades es una herramienta de negocio. Si 0fee.dev negocia una mejor tarifa con un nuevo proveedor, ajustar la tabla de enrutamiento cambia qué proveedor maneja el tráfico -- sin cambio de código, sin intervención del comerciante y sin tiempo de inactividad. Una sola sentencia UPDATE desplaza el volumen de pagos de un proveedor a otro.

Este es el poder de un motor de enrutamiento en un orquestador de pagos. Transforma la selección de proveedor de una decisión codificada en un proceso configurable e impulsado por datos que puede optimizarse continuamente.


Este artículo es parte de la serie "Cómo construimos 0fee.dev". 0fee.dev es un orquestador de pagos que cubre más de 53 proveedores en más de 200 países, construido por Juste A. GNIMAVO y Claude desde Abiyán sin ningún ingeniero humano. Sigue la serie para conocer la historia completa de construcción.

Share this article:

Responses

Write a response
0/2000
Loading responses...

Related Articles