Liquidar pagos de marketplace (comisión + payout)
Cobra al comprador en la cuenta comercio del tenant, retiene una comisión de plataforma en tu app y luego paga destinos Pago Móvil de vendedores verificados con payouts simples o en batch.
Qué es este flujo
Usa este patrón cuando un vendedor registra un destino Pago Móvil en VES, un comprador paga con su propio Pago Móvil (monto de subasta o pedido) y tu tenant debe conservar una comisión antes de pagar al vendedor — opcionalmente repartiendo el crédito entre más de un comerciante verificado.
Pasos de punta a punta
- Registra y verifica al vendedor
POST /v1/merchants con bankCode + teléfono del vendedor (y tu id de vendedor como externalRef) y luego verifica el método con microdepósitos. Detalles: Comerciantes y payouts (/es/merchants-payouts).
- Resuelve el comerciante antes del payout
Al liquidar, busca al vendedor bajo tu tenant con GET /v1/merchants?externalRef={tuIdDeVendedor} — la misma auth que el resto (`x-api-key`). Prefiere esto a guardar solo el merchantId del gateway. Ver /es/merchants-payouts#lookup-by-ref.
- Cobra al comprador
Cobra a la cuenta comercio del tenant con C2P (recomendado: POST /v1/payments/c2p/request y luego POST /v1/payments/c2p) o débito inmediato (POST /v1/payments/debit/otp y luego POST /v1/payments/debit). Envía un externalRef de pago estable para tu pedido/subasta.
- Espera payment.completed
Suscríbete a payment.completed / payment.failed. Si la respuesta es incierta, recupera con GET /v1/payments/by-ref/:externalRef antes de cumplir o pagar.
- Calcula comisión y neto en VES
En tu app: comisión = cobrado × tasa (o fijo), neto = cobrado − comisión. No hay campo de platform-fee en la API: retienes la comisión pagando al vendedor menos de lo cobrado.
- Paga al vendedor (o reparte)
POST /v1/payouts con el merchantId del vendedor, el monto neto exacto y el paymentId del cobro completado — o POST /v1/payouts/batch con paymentId y un ítem por destino. Cada ítem necesita su propio externalRef idempotente. Se permiten varios payouts parciales hasta que el neto restante sea 0.
Reglas de comisión y split
| Regla | Detalle |
|---|---|
| Dueño de la comisión | Prefiere applicationFeeVes / applicationFeePercent en el cobro (la plataforma retiene; el neto va a pending del vendedor). Retener pagando menos sigue válido sin merchantId. |
| Ledger del vendedor | GET /v1/merchants/:id/balance devuelve pendingVes + availableVes. POST /v1/merchants/:id/transfers mueve float del tenant → pending del vendedor. Payouts con fromMerchantBalance debitan available del vendedor. |
| Balance de plataforma | GET /v1/balance devuelve platformAvailableVes, sellerObligationsVes, sellerPendingVes, sellerAvailableVes. |
| Montos de payout | Strings VES con exactamente dos decimales (p. ej. "1523.40"). El gateway nunca convierte FX en payouts. |
| Vínculo paymentId | Opcional en POST /v1/payouts y POST /v1/payouts/batch. Vincula piernas al cobro para ops y el tope de neto restante. |
| Neto restante | remaining = vesAmount − feeVes − suma(payouts PENDING|COMPLETED de ese paymentId). Los FAILED liberan remaining. Exceder → 422 payment_split_exceeded. |
| Multi-parcial | Varios payouts/batches pueden usar el mismo paymentId hasta remaining = 0. El mismo externalRef sigue siendo replay idempotente. |
| Split en batch | POST /v1/payouts/batch paga a varios comerciantes verificados en una solicitud. El resultado es por ítem — no es todo o nada. El paymentId del batch aplica a cada ítem. |
| Float de comercio | Los payouts clásicos debitan la cuenta comercio del tenant. Los payouts de saldo del vendedor debitan available (el tenant ya reservó vía SELLER_TRANSFER). |
| Idempotencia | externalRef en pagos es solo correlación. En comerciantes y payouts es la clave de idempotencia; paymentId entra en el hash del request de payout. |
Ejemplos mínimos
Cuando el vendedor esté verificado y el cobro al comprador haya completado, liquida el neto (o un split multiparte) con la API de payouts.
curl --request POST "${API_URL}/v1/merchants" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{
"externalRef": "seller_laganga_7f3a",
"name": "Maria Perez",
"identification": "V12345678",
"contactEmail": "maria@mail.com",
"contactPhone": "04141234567",
"bankCode": "0134",
"phone": "04145555555"
}'curl --request POST "${API_URL}/v1/payments/c2p" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{
"intentId": "00000000-0000-4000-8000-000000000000",
"usdAmount": 12.50,
"debtorId": "V87654321",
"debtorCellPhone": "584121234567",
"debtorBankCode": 102,
"token": "123456",
"externalRef": "bid_1042"
}'curl --request POST "${API_URL}/v1/payouts" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{
"merchantId": "00000000-0000-4000-8000-000000000000",
"paymentId": "00000000-0000-4000-8000-000000000010",
"monto": "1371.06",
"concepto": "Bid 1042 neto",
"externalRef": "po_bid_1042_seller"
}'curl --request POST "${API_URL}/v1/payouts/batch" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{
"externalRef": "settle_bid_1042",
"paymentId": "00000000-0000-4000-8000-000000000010",
"items": [
{
"merchantId": "00000000-0000-4000-8000-000000000000",
"monto": "1200.00",
"concepto": "Neto vendedor",
"externalRef": "po_bid_1042_seller"
},
{
"merchantId": "00000000-0000-4000-8000-000000000002",
"monto": "171.06",
"concepto": "Parte socio",
"externalRef": "po_bid_1042_partner"
}
]
}'Webhooks y recuperación
- Usa paymentId en los payouts para vincular la liquidación al cobro; usa externalRef del pago para tu id de pedido y externalRef del payout / ítem de batch para reintentos seguros.
- Suscríbete a payment.completed / payment.failed antes de cumplir la subasta o el pedido.
- Suscríbete a payout.completed / payout.failed después de disparar la liquidación.
- Verificación de comerciantes y reglas de payout: /es/merchants-payouts. Firma y entrega: /es/webhooks.