Verifica comerciantes y envía payouts
Registra comerciantes con uno o más métodos Pago Móvil, confirma microdepósitos por método y paga destinos verificados con montos VES exactos e idempotencia por ítem.
Tres reglas de oro
- Los montos de payout siempre son strings explícitos del tenant con exactamente dos decimales — el gateway nunca calcula ni convierte FX para payouts.
- La idempotencia se aplica por externalRef a nivel de ítem ANTES de tocar el banco.
- Todo CreditoInmediato AC00 se reconcilia con el mismo poll de ConsultarOperaciones que ya usa débito inmediato.
Reglas de externalRef
Tú siempre eliges externalRef. La API nunca genera uno. Envía tu propio id estable (id de usuario, liquidación, número de pedido) para poder consultar los registros después.
| Regla | Detalle |
|---|---|
| Obligatorio | Requerido en POST /v1/merchants, POST /v1/payouts y en cada ítem de POST /v1/payouts/batch. |
| Longitud | 1–64 caracteres tras trim. |
| Alcance | Único por tenant. El mismo string puede existir bajo otro tenant. |
| Propiedad | Solo lo envía el cliente. Generate en el portal es una ayuda de UI para pruebas manuales; las integraciones de producción deben enviar su propio valor en el JSON. |
| Replay (mismo payload) | Devuelve el comerciante o payout existente (200) y no vuelve a tocar el banco. |
| Conflicto (payload distinto) | Devuelve 409 external_ref_conflict — salvo que el comerciante no tenga métodos de payout (p. ej. tras limpieza por BE01 o un alta sin método); entonces POST /v1/merchants con bankCode+phone adjunta un método nuevo (200), o sin ellos actualiza la identidad del shell (200). |
| Recuperación | GET /v1/merchants?externalRef=…, GET /v1/merchants/:id, GET /v1/payouts/by-ref/:externalRef, GET /v1/payouts/:id |
Verificación de comerciante
| Paso | Endpoint |
|---|---|
| 1. Alta | POST /v1/merchants (bankCode + phone opcionales crean el primer método; omite ambos para un comerciante sin método) |
| 2. Agregar otro método (opcional) | POST /v1/merchants/:id/payout-methods |
| 3. Disparar microabonos | POST /v1/merchants/:id/payout-methods/:methodId/verify/start |
| 4. Confirmar montos | POST /v1/merchants/:id/payout-methods/:methodId/verify |
| 5. Listar métodos | GET /v1/merchants/:id/payout-methods |
| 6. Marcar método por defecto | POST /v1/merchants/:id/payout-methods/:methodId/default |
| 7. Eliminar un método | DELETE /v1/merchants/:id/payout-methods/:methodId |
| 8. Estado | GET /v1/merchants/:id o GET /v1/merchants?externalRef=… |
| 9. Listar listos para pagar | GET /v1/merchants?status=verified&isActive=true&limit=25 |
| 10. Desactivar / reactivar | PATCH /v1/merchants/:id `{ "isActive": false|true }` |
| 11. Borrar (sin payouts) | DELETE /v1/merchants/:id — 409 merchant_has_payouts si hay historial |
curl --request POST "${API_URL}/v1/merchants" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{
"externalRef": "usr_7f3a",
"name": "Maria Perez",
"identification": "V12345678",
"contactEmail": "maria@mail.com",
"contactPhone": "04141234567",
"bankCode": "0134",
"phone": "04145555555"
}'curl --request POST "${API_URL}/v1/merchants" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{
"externalRef": "usr_7f3a",
"name": "Maria Perez",
"identification": "V12345678",
"contactEmail": "maria@mail.com",
"contactPhone": "04141234567"
}'
# → payoutMethods: [], message: "No payout method exists…"curl "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods" \
--header "x-api-key: ${VEXPAY_API_KEY}"
curl --request POST "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{
"bankCode": "0105",
"phone": "04141234567"
}'curl --request POST "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods/METHOD_ID/verify/start" \
--header "x-api-key: ${VEXPAY_API_KEY}"
curl --request POST "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods/METHOD_ID/verify" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{ "amount1": "0.37", "amount2": "0.82" }'curl --request POST "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods/METHOD_ID/default" \
--header "x-api-key: ${VEXPAY_API_KEY}"
curl --request DELETE "${API_URL}/v1/merchants/MERCHANT_ID/payout-methods/METHOD_ID" \
--header "x-api-key: ${VEXPAY_API_KEY}"curl "${API_URL}/v1/merchants?status=verified&isActive=true&limit=25" \
--header "x-api-key: ${VEXPAY_API_KEY}"
curl "${API_URL}/v1/merchants?externalRef=usr_7f3a" \
--header "x-api-key: ${VEXPAY_API_KEY}"
curl "${API_URL}/v1/merchants/MERCHANT_ID" \
--header "x-api-key: ${VEXPAY_API_KEY}"curl --request PATCH "${API_URL}/v1/merchants/MERCHANT_ID" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{ "isActive": false }'curl --request DELETE "${API_URL}/v1/merchants/MERCHANT_ID" \
--header "x-api-key: ${VEXPAY_API_KEY}"
# 200 → { "deleted": true, "merchantId": "…", "externalRef": "…" }
# 409 → { "error": "merchant_has_payouts", "payoutCount": N }Borrar un comerciante
Borra de forma permanente un comerciante (y sus métodos de payout) cuando nunca ha recibido un payout. Libera el externalRef del tenant para volver a registrar el mismo id de vendedor. Prefiere PATCH isActive:false si ya hay historial de payouts.
| Resultado | Detalle |
|---|---|
| DELETE /v1/merchants/:id | Acotado al tenant por x-api-key. El id del path es el UUID del gateway. |
| 200 OK | `{ "deleted": true, "merchantId", "externalRef" }` — comerciante y métodos eliminados; externalRef queda libre. |
| 404 | Comerciante no encontrado para este tenant. |
| 409 merchant_has_payouts | Existe al menos un payout (`payoutCount`). Desactiva con PATCH `{ "isActive": false }` en su lugar. |
curl --request DELETE "${API_URL}/v1/merchants/00000000-0000-4000-8000-000000000000" \
--header "x-api-key: ${VEXPAY_API_KEY}"{
"deleted": true,
"merchantId": "00000000-0000-4000-8000-000000000000",
"externalRef": "usr_7f3a"
}{
"error": "merchant_has_payouts",
"payoutCount": 3
}Buscar un comerciante por externalRef
Dentro de un tenant, cada comerciante queda identificado de forma única por el externalRef que enviaste al registrarlo. Prefiere consultar por esa ref cuando tu sistema guarda el id de vendedor/usuario, no el merchantId del gateway. La auth es la API key del tenant (`x-api-key`) — la búsqueda siempre queda acotada a ese tenant.
| Endpoint | Devuelve |
|---|---|
| GET /v1/merchants?externalRef={ref} | Detalle de un solo comerciante (misma forma que GET /v1/merchants/:id), con payoutMethods[] |
| GET /v1/merchants/:id | El mismo detalle cuando ya tienes el UUID del gateway |
| GET /v1/merchants | Lista paginada (`items`, `nextCursor`) si omites externalRef |
curl "${API_URL}/v1/merchants?externalRef=usr_7f3a" \
--header "x-api-key: ${VEXPAY_API_KEY}"const merchant = await fetch(
`${process.env.API_URL}/v1/merchants?externalRef=${encodeURIComponent('usr_7f3a')}`,
{ headers: { 'x-api-key': process.env.VEXPAY_API_KEY } }
).then((r) => r.json());
// merchant.merchantId, merchant.status, merchant.payoutMethodsPayouts simple y batch
| Endpoint | Propósito |
|---|---|
| POST /v1/payouts | Pagar un comerciante verificado y activo (payoutMethodId opcional) |
| POST /v1/payouts/batch | Pagar varios; resultado por ítem |
| GET /v1/payouts | Listar payouts (filtro status opcional) |
| GET /v1/payouts/:id | Consultar por id |
| GET /v1/payouts/by-ref/:externalRef | Consultar por externalRef |
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",
"payoutMethodId": "00000000-0000-4000-8000-000000000001",
"monto": "1523.40",
"concepto": "Liquidacion semana 30",
"externalRef": "po_9c21"
}'POST /v1/payouts/batch paga a varios comerciantes verificados en una solicitud. Cada ítem es su propio payout (su externalRef, status y webhooks). Los fallos son por ítem — no revierten a los demás.
curl --request POST "${API_URL}/v1/payouts/batch" \
--header "content-type: application/json" \
--header "x-api-key: ${VEXPAY_API_KEY}" \
--data '{
"externalRef": "corte_semana_30",
"items": [
{
"merchantId": "00000000-0000-4000-8000-000000000000",
"payoutMethodId": "00000000-0000-4000-8000-000000000001",
"monto": "1523.40",
"concepto": "Liquidacion semana 30",
"externalRef": "po_9c21_seller"
},
{
"merchantId": "00000000-0000-4000-8000-000000000002",
"monto": "210.00",
"concepto": "Liquidacion semana 30",
"externalRef": "po_9c21_partner"
}
]
}'{
"batchId": "00000000-0000-4000-8000-000000000010",
"items": [
{
"payoutId": "00000000-0000-4000-8000-000000000011",
"status": "completed",
"reference": "12345678",
"failureCode": null,
"externalRef": "po_9c21_seller",
"merchantId": "00000000-0000-4000-8000-000000000000",
"payoutMethodId": "00000000-0000-4000-8000-000000000001",
"merchantName": "Maria Perez",
"monto": "1523.40",
"concepto": "Liquidacion semana 30",
"bankCode": "0134",
"destination": "…5555",
"operationId": null,
"createdAt": "2026-07-28T16:00:00.000Z",
"settledAt": "2026-07-28T16:00:01.000Z"
},
{
"payoutId": "00000000-0000-4000-8000-000000000012",
"status": "pending",
"reference": null,
"failureCode": null,
"externalRef": "po_9c21_partner",
"merchantId": "00000000-0000-4000-8000-000000000002",
"payoutMethodId": "00000000-0000-4000-8000-000000000003",
"merchantName": "Socio CA",
"monto": "210.00",
"concepto": "Liquidacion semana 30",
"bankCode": "0102",
"destination": "…1234",
"operationId": "AC00-OP-EXAMPLE",
"createdAt": "2026-07-28T16:00:00.000Z",
"settledAt": null
}
]
}curl "${API_URL}/v1/payouts?status=pending&limit=25" \
--header "x-api-key: ${VEXPAY_API_KEY}"
curl "${API_URL}/v1/payouts/PAYOUT_ID" \
--header "x-api-key: ${VEXPAY_API_KEY}"