Split Payments de Mercado Pago: Construye un Marketplace en LATAM que Paga a Vendedores (el Stripe Connect que Casi Nadie Puede Usar) — Cesar Ayala
← Todos los artículos

Split Payments de Mercado Pago: Construye un Marketplace en LATAM que Paga a Vendedores (el Stripe Connect que Casi Nadie Puede Usar)

Split Payments de Mercado Pago permite que tu marketplace cobre una vez al comprador, liquide en la cuenta del vendedor y tome tu comisión con application_fee. Conecta a cada vendedor con OAuth, guarda y rota sus tokens, y crea el pago con el token del vendedor. La comisión de MP se descuenta primero; la tuya, del resto.

Por qué Stripe Connect deja varados a los marketplaces de LATAM

Voy a empezar por la pared con la que te vas a estrellar, porque es la razón de existir de todo este post: Stripe Connect no puede pagarle a tus vendedores en gran parte de LATAM. La lista de países soportados para payouts de Connect excluye a buena parte de la región — un vendedor en México o Argentina simplemente no puede recibir un payout de Connect (confirma la lista vigente de países soportados de Stripe, a 2026, porque cambia).

Esto importa más de lo que parece. Si tus compradores y tus vendedores están en LATAM, la API elegante de Connect te da exactamente igual: puedes cobrarle al comprador todo el día, pero no tienes forma de liquidarle al vendedor. Y un marketplace que no le paga a sus vendedores no es un marketplace, es un formulario bonito.

La respuesta nativa de la región es Mercado Pago Split Payments (también llamado “marketplace” en su documentación). El modelo es el mismo que Connect en espíritu: una sola plataforma le cobra al comprador y reparte el dinero entre el vendedor y la comisión de la plataforma. La diferencia es que este sí funciona con vendedores mexicanos y argentinos.

Yo me topé con esta pared construyendo Mercanto, el marketplace B2B. La documentación oficial existe, pero está dispersa y se queda en lo feliz: te enseña el OAuth y te suelta. Las trampas operativas — la rotación del refresh_token, el orden de las comisiones, la conciliación por API — las descubres en producción con dinero real moviéndose. Esta es la guía que me hubiera gustado encontrar. La fuente formal es la documentación de Split Payments de Mercado Pago; aquí va la versión operativa.

Si vienes del mundo Stripe, te recomiendo leer primero la contraparte de Stripe Connect que esto reemplaza en LATAM para que el contraste te quede claro.

¿Cómo funciona realmente un pago dividido (split)?

El modelo es engañosamente simple, así que vale la pena fijarlo bien antes de tocar código.

El comprador paga una sola vez. Pero — y esto es lo que cuesta interiorizar — el pago se liquida en la cuenta del vendedor, no en la tuya. Tú no eres el que cobra el bruto y luego reparte. Mercado Pago enruta la parte del vendedor a su cuenta y tu application_fee (tu comisión) a la cuenta de tu marketplace, de forma nativa.

Esto es fundamentalmente distinto de cobrar en tu propia cuenta y luego transferir manualmente. Aquí el dinero aterriza ya dividido. No hay un momento en que tengas la totalidad del fondo en tu cuenta — y eso tiene implicaciones legales y de tesorería que conviene entender desde el día uno.

La mecánica concreta: tú creas el pago usando el access_token OAuth del vendedor, y especificas application_fee con tu corte en la moneda del pago. Mercado Pago hace el reparto. Los reembolsos también se dividen proporcionalmente: regresan desde la cuenta del vendedor y desde la del marketplace, según la proporción original.

Flujo del dinero en un split payment

El comprador paga el totalUn solo cargo; se liquida en la cuenta del vendedor, no en la tuya.
MP descuenta su comisión de procesamientoSe deduce PRIMERO, sobre el monto bruto.
Se descuenta tu application_feeSale del REMANENTE, no del bruto.
El vendedor recibe su netoneto = total menos comisión MP menos application_fee.
Tu comisión aterriza en tu cuenta de marketplaceDe forma nativa, sin transferencia manual.
El comprador paga una vez; MP descuenta su comisión primero, tu application_fee sale del remanente, y cada parte aterriza en su cuenta.

Si todavía no tienes lo básico de Mercado Pago resuelto (Checkout Pro, Bricks, webhooks), arma primero esa base con cómo integrar Mercado Pago. Split Payments asume que ya sabes crear pagos.

Conectar vendedores con OAuth (flujo de authorization code)

Cada vendedor tiene que autorizar tu aplicación de marketplace antes de que puedas crear pagos en su nombre. Esto se hace con el flujo OAuth Authorization Code, igual que cualquier “Conecta tu cuenta” que hayas visto.

El paso uno es redirigir al vendedor a la URL de autorización de Mercado Pago con tu client_id, tu redirect_uri y un state (úsalo siempre para protegerte de CSRF y para correlacionar de qué vendedor se trata cuando vuelva). Un detalle que importa y que el draft inicial me costó caro: el host canónico es https://auth.mercadopago.com/authorization, sin subdominio de país. Y tienes que pedir el scope offline_access — es lo único que habilita que MP te devuelva un refresh_token; sin él, no puedes renovar nada y toda la mecánica de rotación que viene más abajo ni siquiera aplica.

Mercado Pago lo regresa a tu redirect_uri con un authorization code que dura ~10 minutos. No lo guardes para después: cámbialo de inmediato. Haces un POST a https://api.mercadopago.com/oauth/token con grant_type=authorization_code y el resto de los parámetros.

// 1) Construir la URL de autorización a la que rediriges al vendedor
//    Host canónico, SIN subdominio de país (.com.mx, etc.)
const authUrl = new URL("https://auth.mercadopago.com/authorization");
authUrl.searchParams.set("client_id", process.env.MP_CLIENT_ID);
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("platform_id", "mp");
authUrl.searchParams.set("redirect_uri", process.env.MP_REDIRECT_URI);
authUrl.searchParams.set("state", sellerLinkState); // correlaciona el vendedor + anti-CSRF
// offline_access es REQUERIDO para recibir refresh_token; valores válidos: offline_access, read, write
authUrl.searchParams.set("scope", "offline_access read write");
// redirige al vendedor a authUrl.toString()

// 2) Intercambiar el authorization code por el set de tokens del vendedor
async function exchangeCode(code) {
  const res = await fetch("https://api.mercadopago.com/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/json", "Accept": "application/json" },
    body: JSON.stringify({
      client_id: process.env.MP_CLIENT_ID,
      client_secret: process.env.MP_CLIENT_SECRET,
      grant_type: "authorization_code",
      code,                                   // válido ~10 minutos
      redirect_uri: process.env.MP_REDIRECT_URI,
    }),
  });
  const t = await res.json();
  // t.access_token, t.refresh_token, t.public_key,
  // t.user_id (= collector_id del vendedor), t.expires_in, t.scope, t.token_type
  return t;
}

La respuesta te da access_token, refresh_token, public_key, user_id (que es el collector_id del vendedor), token_type, expires_in y scope. Persiste el user_id como identificador del vendedor en MP, y guarda el set completo de tokens por vendedor. A escala vas a administrar un set de tokens por cada cuenta conectada — eso se vuelve infraestructura real, no un detalle.

El modelo de onboarding y payouts aquí es conceptualmente el mismo que ya describí para Stripe Connect: onboarding y payouts; si ya lo tienes mapeado mentalmente, este flujo te va a sonar familiar.

El ciclo de vida del token y la trampa de la rotación del refresh token

Aquí está la trampa número uno, la que tumba marketplaces en silencio meses después de lanzar.

El access_token del vendedor vive ~180 días (6 meses) — el expires_in que te devuelve MP es de 15552000 segundos. Calcula tu expires_at con el expires_in real de la respuesta, no con un número hardcodeado. Antes de que expire tienes que renovarlo con el refresh_token. Si lo dejas vencer, no hay recuperación elegante: tienes que volver a hacer toda la autorización con el vendedor, mandarlo otra vez al flujo OAuth. Eso es fricción que tus vendedores van a odiar.

Renovar es un POST a /oauth/token con grant_type=refresh_token. Suena trivial. No lo es, por esto:

EL refresh_token ROTA. Cada renovación devuelve un refresh_token nuevo e invalida el anterior. Si no persistes el nuevo de forma atómica — por ejemplo, si tu proceso renueva, recibe el token nuevo, y se cae antes de guardarlo — quedas con un refresh_token muerto en la base de datos y te bloqueas permanentemente de ese vendedor. La única salida es re-autorizar.

// Renovar ANTES del día 180. Persistir el refresh_token rotado de forma atómica.
async function refreshSellerToken(sellerId, currentRefreshToken) {
  const res = await fetch("https://api.mercadopago.com/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/json", "Accept": "application/json" },
    body: JSON.stringify({
      client_id: process.env.MP_CLIENT_ID,
      client_secret: process.env.MP_CLIENT_SECRET,
      grant_type: "refresh_token",
      refresh_token: currentRefreshToken,
    }),
  });
  const t = await res.json();

  // CRÍTICO: el refresh_token ROTÓ. Guarda el nuevo o pierdes al vendedor.
  // Envuelve TODO el guardado en una transacción.
  await db.transaction(async (tx) => {
    await tx.sellers.update(sellerId, {
      access_token: t.access_token,
      refresh_token: t.refresh_token,            // el NUEVO, nunca reutilices el viejo
      expires_at: new Date(Date.now() + t.expires_in * 1000),
    });
  });
  return t;
}

El patrón práctico: corre un cron que renueve bien antes del día 180 (yo no espero al límite — renuevo con semanas de holgura), envuelve el guardado en una transacción, y nunca reutilices un refresh_token después de un intercambio exitoso. Además trata la re-autorización como un modo de falla real en tu UX: si un set de tokens se vence, el vendedor necesita re-vincular, y eso tiene que tener una pantalla decente, no un error 500.

Un extra que ayuda: revisa qué notificaciones de cambio de autorización puede mandarte Mercado Pago para mantener tu estado sincronizado cuando un vendedor desvincula tu aplicación — confirma los topics de webhook/IPN disponibles y los lifetimes de tokens vigentes en la documentación de gestión de OAuth (a 2026). No te apoyes en un topic que no puedas citar en los docs.

Las dos trampas que te van a costar dinero

Rotación del refresh_tokenCada refresh devuelve un refresh_token NUEVO e invalida el anterior. Persístelo atómicamente o quedas bloqueado de ese vendedor para siempre.
Orden de comisionesLa comisión de MP se descuenta PRIMERO sobre el bruto; tu application_fee sale del remanente. Neto del vendedor = total menos comisión MP menos application_fee.
Memorízalas antes de escribir una línea de código de producción.

Crear el pago dividido con application_fee

Ahora la transacción que importa. Haces un POST a https://api.mercadopago.com/v1/payments con Authorization: Bearer {access_token_del_vendedor}el token del vendedor, NO el de tu marketplace. Esta es la línea que la gente equivoca: si usas tu token, no es un split payment, es un cobro normal en tu cuenta.

Incluyes application_fee con tu comisión en la moneda del pago, junto a transaction_amount, payment_method_id, token, payer, etc. Y mandas un X-Idempotency-Key único para que los reintentos no dupliquen el cargo.

async function createSplitPayment(seller, payment) {
  const res = await fetch("https://api.mercadopago.com/v1/payments", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      // Token OAuth del VENDEDOR, no el de tu marketplace
      "Authorization": `Bearer ${seller.access_token}`,
      "X-Idempotency-Key": payment.idempotencyKey, // evita doble cargo en reintentos
    },
    body: JSON.stringify({
      transaction_amount: 1000,            // total que paga el comprador
      application_fee: 100,                // TU comisión, en la moneda del pago
      description: "Orden #4821 - Mercanto",
      payment_method_id: payment.methodId, // p.ej. resultante del Brick/Checkout
      token: payment.cardToken,            // ilustrativo: depende del método de cobro
      installments: 1,
      payer: { email: payment.payerEmail },
    }),
  });
  return res.json(); // contiene id y status del pago
}

Los campos exactos del body varían según el método de cobro (tarjeta, OXXO, SPEI vía Checkout/Bricks), así que toma token, payment_method_id e installments como ilustrativos — ajústalos a tu método. Lo que no es negociable es: token del vendedor en el header, application_fee con tu corte, e idempotencia.

Y la disciplina que repito en cada post de pagos: no confíes en el redirect del front-end para dar por bueno el pago. Verifica el estado desde un webhook/IPN firmado y vuelve a consultar el pago en la API antes de cumplir la orden. El front-end miente, se cae, lo manipulan a tu antojo. El evento verificado es la fuente de verdad.

La trampa del orden de comisiones: quién cobra primero

Esta es la trampa de modelado de dinero que quema a todo marketplace que le cotiza un take rate a sus vendedores sin hacer la cuenta.

La intuición ingenua es: “cobro 10% de comisión, entonces de $1,000 me quedo $100 y el vendedor recibe $900”. Falso. El orden real es:

  1. Mercado Pago descuenta su comisión de procesamiento primero, sobre el bruto.
  2. Tu application_fee se descuenta después, del remanente — no del bruto.

Entonces:

neto del vendedor = total - comisión MP - application_fee

Un ejemplo con números redondos. Total $1,000, supongamos comisión de MP de $40 (4% — confirma la tasa vigente en tu dashboard, a 2026), y tu application_fee de $100:

total          = 1000 MXN
comisión MP    =   40 MXN   (se descuenta PRIMERO, sobre el bruto)
remanente      =  960 MXN
application_fee =  100 MXN   (se descuenta del REMANENTE)
-------------------------------------------------------------
vendedor recibe =  860 MXN
tú recibes      =  100 MXN

El vendedor no recibió $900 “menos comisiones de la pasarela”. Recibió $860. Si tú le prometiste “te quedas con el 90%” pensando en el bruto, le mentiste sin querer y vas a tener una conversación incómoda — o vas a comerte la diferencia.

Si quieres garantizarle al vendedor un take rate fijo, tienes que despejar tu application_fee hacia atrás contra la comisión de MP. No es álgebra difícil, pero hay que hacerla antes de cotizar, no después de que el dinero ya se movió. Las tasas de MP son sensibles al tiempo: confírmalas en el dashboard/docs (a 2026).

Realidades operativas: conciliación, restricciones y escala

Lo que la documentación no enfatiza es lo que separa un demo de un marketplace que sobrevive una auditoría.

La conciliación es por API. No hay un dashboard amigable que te muestre, cross-vendedor, quién cobró qué y cuándo se liberó. Trabajas con reportes de liquidación/releases (settlement/releases). En la práctica esto significa que tienes que construir tu propio ledger — tu propia contabilidad de cada split, cada comisión, cada liberación de fondos. No es opcional. En Mercanto, presupuestar tiempo de ingeniería para ese ledger fue una de las mejores decisiones; sin él, no puedes responder “¿cuánto le debemos a este vendedor?” con confianza.

Hay restricciones para mover fondos externamente. No asumas flexibilidad arbitraria de payout/transferencia. El dinero vive bajo las reglas de Mercado Pago, no las tuyas.

A escala administras un set de tokens por vendedor. Almacenamiento de tokens, job de refresh agendado, y recuperación por re-vinculación se vuelven infraestructura de verdad. El job de refresh no es opcional: es lo único entre tú y bloquearte de vendedores cuando sus tokens lleguen al día 180.

Los webhooks/IPN son tu fuente de verdad para el estado del pago. Verifica el evento firmado, vuelve a consultar el recurso, y solo entonces concede el cumplimiento. Nunca el redirect.

Levantar un marketplace de punta a punta

  1. Registra tu app OAuthObtén client_id, client_secret y configura tu redirect_uri en el panel de Mercado Pago.
  2. El vendedor autoriza (authorization code)Lo rediriges al flujo OAuth con scope offline_access; vuelve con un code válido ~10 min.
  3. Intercambia y guarda el set de tokensaccess_token, refresh_token, user_id (collector_id). Persiste por vendedor.
  4. Crea el split payment con application_feePOST a /v1/payments con el token del VENDEDOR y tu comisión.
  5. Verifica el webhook y conciliaConfirma el evento firmado, consulta el pago, y cuadra con los reportes de liquidación en tu ledger.
El camino de producción, no el del demo.

Stripe Connect vs Mercado Pago Split Payments en LATAM

Stripe Connect

  • Payouts excluyen a buena parte de LATAM (MX/AR no soportados para vendedores locales)
  • La plataforma puede retener fondos según el tipo de cuenta
  • Comisión vía application_fee, modelo bien documentado
  • Conciliación con dashboard amigable
  • En la práctica: NO lo puedes usar para pagar a vendedores mexicanos/argentinos

MP Split Payments

  • Paga a vendedores en LATAM, incluidos MX y AR — su razón de existir
  • Los fondos se liquidan en la cuenta del VENDEDOR, no en la tuya
  • Comisión vía application_fee; comisión de MP se descuenta ANTES que la tuya
  • Conciliación por API; construyes tu propio ledger
  • Es el que sí puedes usar para un marketplace LATAM real
La realidad de payout para vendedores mexicanos/argentinos (confirma países soportados de Stripe, a 2026).

Preguntas frecuentes

¿Puedo usar Stripe Connect en México/Argentina en vez de esto? No para pagarle a vendedores locales. Confirma la lista de países soportados de Stripe (a 2026), pero esa exclusión es precisamente la razón por la que MP Split Payments existe en la región.

¿Con el token de quién creo el pago, el mío o el del vendedor? El del vendedor — su access_token OAuth — con application_fee para tu corte. Si usas tu token, no hay split.

¿Qué pasa si el access_token de un vendedor expira? Tienes que rehacer toda la autorización con ese vendedor, a menos que lo hayas renovado a tiempo con el refresh_token.

¿El refresh_token se queda igual? No. Rota en cada renovación: cada refresh devuelve uno nuevo e invalida el anterior. Persiste el nuevo de forma atómica o quedas bloqueado. Y recuerda que solo recibes refresh_token si pediste el scope offline_access al autorizar.

¿Mi comisión se toma del bruto? No. La comisión de MP se descuenta primero, del bruto; tu application_fee sale del remanente. Neto del vendedor = total menos comisión MP menos application_fee.

¿Cómo sé que un pago tuvo éxito? Verifica un webhook/IPN firmado, vuelve a consultar el pago en la API, y entonces cumple. Nunca confíes en el redirect.

Cierre: constrúyelo sobre el evento verificado, no sobre el redirect

Mercado Pago Split Payments es la forma realista de operar un marketplace en LATAM que le paga a vendedores terceros. No es tan pulido como Stripe Connect, pero tiene la única virtud que importa aquí: sí funciona con vendedores mexicanos y argentinos.

Ganas o pierdes en dos cosas concretas. Una: nunca pierdas un refresh_token rotado — persístelo atómicamente, renueva con holgura, y trata la re-vinculación como un flujo de UX de primera clase. Dos: siempre modela la comisión de MP antes que la tuya — el vendedor recibe el total menos la comisión de MP menos tu application_fee, en ese orden, o le cotizaste mal.

Y la regla que no cambia en ningún post de pagos que escribo: concede acceso solo sobre un evento firmado y verificado. El webhook es la fuente de verdad, no el redirect.

Este es exactamente el stack detrás de Mercanto. Si lo estás cableando y te topas con el muro de la conciliación o el job de refresh, escríbeme — son las dos piezas que la gente subestima y las dos que te van a doler en producción.