MercadoPago split

Cobros con split de pagos (marketplace): cada comercio (negocio) conecta su cuenta de Mercado Pago por OAuth y la plataforma retiene un marketplace_fee del 1,5% sobre cada cobro. El comercio recibe el resto. La app de Mercado Pago es de Sabado — el consumer nunca ve client_secret ni los tokens del comercio.

Orden del reparto (según doc oficial de MP): primero Mercado Pago descuenta su comisión, después se descuenta el marketplace_fee del saldo restante. El comercio recibe lo que queda.

Flujo típico

  1. El comercio conecta su MP: GET /v1/mercadopago/connect → redirigís su navegador al connect_url.
  2. Verificás estado con GET /v1/mercadopago/status.
  3. Cobrás: POST /v1/mercadopago/preferences → mandás el init_point al comprador.
  4. El comprador paga → recibís mp.payment.approved por webhook (por el de por defecto, o por el propio de Mercado Pago si tus ventas las atiende otro sistema) (ver Eventos de MercadoPago). Si necesitás saber el estado sin esperar el webhook: GET /v1/mercadopago/payments.
  5. Para cancelar/revertir un cobro: POST /v1/mercadopago/refunds → recibís mp.payment.refunded.

Conectar el comercio

GET /v1/mercadopago/connect

Devuelve la URL de autorización OAuth para que el comercio conecte su cuenta de Mercado Pago. Redirigí el navegador del dueño del comercio al connect_url.

Scope: mercadopago.write.

GET /v1/mercadopago/connect?tenant_ref=bruno-traslados
Authorization: Bearer TU_API_KEY

Respuesta:

{
  "tenant_ref": "bruno-traslados",
  "connect_url": "https://auth.mercadopago.com.ar/authorization?client_id=...&response_type=code&platform_id=mp&redirect_uri=...&state=..."
}

El state es one-time (CSRF + liga el callback con el comercio). El callback lo maneja Sabado; cuando el comercio autoriza, queda conectado y status lo refleja.

Errores:

Estado de conexión

GET /v1/mercadopago/status

Estado de conexión MercadoPago del comercio. Usalo para mostrar "Conectar" vs "Conectado ✓" en tu UI.

Scope: mercadopago.read.

GET /v1/mercadopago/status?tenant_ref=bruno-traslados
Authorization: Bearer TU_API_KEY

Respuesta:

{
  "tenant_ref": "bruno-traslados",
  "connected": true,
  "mp_user_id": "123456789",
  "connected_at": "2026-05-28T19:04:58+00:00"
}

connected: false (con mp_user_id y connected_at en null) cuando el comercio no conectó, o cuando su token fue revocado — en ese caso la plataforma marca la credencial como desconectada automáticamente, así que el status no miente.

Errores:

Crear preferencia (cobrar)

POST /v1/mercadopago/preferences

Crea una preferencia de Checkout Pro con split. La plataforma calcula el marketplace_fee (1,5% del total) y crea la preferencia con el token del comercio. Mandá el init_point al comprador (sandbox_init_point en pruebas).

Scope: mercadopago.write.

POST /v1/mercadopago/preferences
{
  "tenant_ref": "bruno-traslados",
  "external_reference": "ticket-42",
  "items": [
    {
      "title": "Viaje a Ricky Martin (2 lugares)",
      "quantity": 1,
      "unit_price": 46000,
      "currency_id": "ARS"
    }
  ],
  "payer_email": "ana@gmail.com",
  "back_urls": {
    "success": "https://tudominio.com/pago-ok",
    "pending": "https://tudominio.com/pago-pendiente",
    "failure": "https://tudominio.com/pago-error"
  }
}

Controlar el cobro

Opcionales, por preferencia — cada venta decide. Sirven sobre todo cuando lo que vendés tiene tiempo límite (una reserva, un cupo, un turno):

CampoQué hace
expiration_date_toFecha ISO 8601. Después de esa hora el link deja de aceptar pagos. Ponele el fin de la reserva.
binary_modetrue: el pago sale aprobado o rechazado en el momento, nunca queda pendiente ni en revisión. Según Mercado Pago baja un poco la tasa de aprobación: lo que iba a revisión se rechaza.
excluded_payment_typesTipos de medio de pago a sacar, ej. ["ticket"] para el efectivo (Rapipago, Pago Fácil). El dinero en cuenta de Mercado Pago no se puede excluir.
date_of_expirationFecha ISO 8601: vencimiento de los pagos en efectivo. Mercado Pago acepta entre 1 y 30 días.
POST /v1/mercadopago/preferences
{
  "tenant_ref": "bruno-traslados",
  "external_reference": "compra-77",
  "items": [{ "title": "2 lugares", "quantity": 1, "unit_price": 46000 }],
  "expiration_date_to": "2026-09-21T19:00:00-03:00",
  "binary_mode": true,
  "excluded_payment_types": ["ticket"]
}

Venta con reserva: prevenir es mejor que devolver. El efectivo acredita en 1 a 2 días hábiles y no puede vencer en menos de un día, así que con una reserva de minutos es exactamente el pago que llega con la reserva ya caída. Para ventas con tiempo límite: excluí ticket, activá binary_mode y poné expiration_date_to al fin de la reserva. Al vencer, consultá por external_reference antes de liberar el lugar. La devolución queda para la excepción: no se puede garantizar (ver abajo).

Respuesta:

{
  "tenant_ref": "bruno-traslados",
  "preference_id": "123456789-abc...",
  "init_point": "https://www.mercadopago.com.ar/checkout/v1/redirect?pref_id=...",
  "sandbox_init_point": "https://sandbox.mercadopago.com.ar/checkout/v1/redirect?pref_id=...",
  "marketplace_fee": 690,
  "expires_at": "2026-09-21T19:00:00.000-03:00"
}

marketplace_fee es el monto absoluto en ARS que retiene la plataforma (round(total * 0.015, 2)). expires_at es la hora en que el link deja de cobrar (null si no mandaste expiration_date_to).

Errores:

Consultar pagos

El estado real del pago en Mercado Pago, en el momento, sin depender del webhook. Sirve para decidir qué hacer cuando vence una reserva, o para conciliar. Se consulta con la cuenta del comercio: solo ves sus pagos. No depende del plan (igual que las devoluciones).

Scope: mercadopago.read.

GET /v1/mercadopago/payments?external_reference=…

Los pagos de tu venta, más nuevos primero. Es la forma de preguntar "¿se pagó la compra 77?" cuando todavía no tenés payment_id (no te llegó ningún webhook). Puede venir más de uno: un intento rechazado y después uno aprobado, por ejemplo.

GET /v1/mercadopago/payments?tenant_ref=bruno-traslados&external_reference=compra-77
{
  "tenant_ref": "bruno-traslados",
  "data": [
    {
      "payment_id": "123456789",
      "external_reference": "compra-77",
      "status": "approved",
      "status_detail": "accredited",
      "payment_type_id": "credit_card",
      "transaction_amount": 46000,
      "amount_refunded": 0,
      "marketplace_fee": 690,
      "mp_fee": 1380,
      "net_received": 43930,
      "currency": "ARS",
      "date_created": "2026-09-21T18:00:00.000-03:00",
      "date_approved": "2026-09-21T18:00:02.000-03:00",
      "refunds": []
    }
  ]
}

GET /v1/mercadopago/payments/{payment_id}

Un pago puntual, con la misma forma (sin el data).

GET /v1/mercadopago/payments/123456789?tenant_ref=bruno-traslados

Errores:

Cancelar / reembolsar

POST /v1/mercadopago/refunds

Hace un reembolso total del pago con la cuenta del comercio. Antes de pedírselo a Mercado Pago revisamos el estado del pago, así que te contestamos con códigos estables en vez del texto de Mercado Pago. Los reembolsos no dependen del plan: devolver la plata de un pago siempre está disponible.

Scope: mercadopago.write.

POST /v1/mercadopago/refunds
{
  "tenant_ref": "bruno-traslados",
  "payment_id": "123456789"
}

payment_id: el id del pago de Mercado Pago (el que recibís en mp.payment.approved o en la consulta).

Respuesta:

{
  "tenant_ref": "bruno-traslados",
  "refund_id": "99001",
  "payment_id": "123456789",
  "amount": 46000,
  "status": "approved",
  "already_refunded": false
}

Pedirlo dos veces no devuelve dos veces ni falla. Si el pago ya estaba devuelto (por este endpoint o desde Mercado Pago), respondemos 200 con la devolución que ya existe y "already_refunded": true, sin tocar nada. Por eso no hace falta Idempotency-Key: la devolución es una sola por pago.

RespuestaCuándo
200, already_refunded: falseSe hizo la devolución.
200, already_refunded: trueYa estaba devuelto: te damos la devolución existente.
409 payment_not_refundable, reason: not_approvedEl pago no está aprobado (viene payment_status con el estado real). Solo se devuelven pagos aprobados.
409 payment_not_refundable, reason: refund_window_expiredPasaron más de 180 días desde la aprobación: Mercado Pago ya no lo permite.
409 payment_not_refundable, reason: partially_refundedEl pago ya tiene una devolución parcial (hecha desde Mercado Pago). Este endpoint no la completa.
409 payment_not_refundable, reason: charged_backEl comprador hizo un contracargo con su banco: la plata ya volvió por ese lado.
404 payment_not_foundEl pago no existe o no es de ese comercio.
409 mp_not_connectedEl comercio no tiene Mercado Pago conectado.
502 refund_failedMercado Pago rechazó la devolución — el caso típico es falta de saldo del comercio. El motivo va en message.

Cuando la devolución sale, te mandamos mp.payment.refunded en ese mismo momento. Si después llega el aviso de Mercado Pago por el mismo cambio, no se repite.

La plata y la comisión en una devolución

Un solo camino de invalidación. Si el comercio o el comprador hacen el refund directamente desde Mercado Pago en lugar de usar este endpoint, igual te llega el webhook mp.payment.refunded (o mp.payment.partial_refund si fue parcial). Procesalo del mismo modo — no importa quién lo disparó.