Reembolsos

Emite reembolsos totales o parciales sobre transacciones capturadas, haz seguimiento del estado del ciclo de vida del reembolso y concilia los importes reembolsados con la API de Tokeflow.

Un reembolso devuelve los fondos de una transacción que ya ha sido capturada. Con Tokeflow emites reembolsos contra una única API normalizada: la capa de orquestación transmite la solicitud al proveedor de pagos conectado, hace seguimiento del resultado y lo refleja tanto en el registro del reembolso como en la transacción principal.

Tokeflow estandariza los reembolsos entre proveedores: una sola forma de solicitud, un solo ciclo de vida de estado, un solo modelo de conciliación. El proveedor de pagos conectado realiza el movimiento efectivo de los fondos.

Tokeflow es una capa de orquestación de pagos. Registra, enruta y hace seguimiento de las solicitudes de reembolso y normaliza el resultado; el proveedor de pagos conectado ejecuta el reembolso y liquida los fondos.

Cómo funcionan los reembolsos

Todo reembolso es hijo de una transacción capturada. Puedes reembolsar el importe capturado restante completo, o emitir varios reembolsos parciales a lo largo del tiempo hasta que la transacción quede totalmente reembolsada.

  • Reembolso total — omite amount para reembolsar todo el importe capturado restante.
  • Reembolso parcial — pasa amount (en unidades menores) para reembolsar menos que el importe capturado restante. El saldo restante permanece capturado y puede reembolsarse más adelante.
  • Múltiples reembolsos parciales — repite los reembolsos parciales hasta que el importe capturado restante llegue a cero. Cada llamada crea un nuevo registro de reembolso.

Un reembolso solo puede iniciarse cuando la transacción principal está en estado authorized o refund_pending. Una vez que se inicia un reembolso, la transacción pasa a refund_pending mientras el proveedor confirma el resultado.

Los resultados de reembolso suelen ser asíncronos. La respuesta síncrona confirma que la solicitud fue aceptada (refund_pending); suscríbete a los webhooks para conocer el resultado final en lugar de hacer sondeo.

Ciclo de vida del reembolso

Un registro de reembolso tiene su propio estado, independiente de la transacción principal.

StatusSignificado
pendingReembolso iniciado, aún no confirmado por el proveedor.
succeededReembolso confirmado por el proveedor.
failedEl reembolso falló en el proveedor; consulta failure_reason.
cancelledEl reembolso se canceló antes de procesarse.

Cómo se asigna el estado del reembolso a la transacción

La transacción principal refleja el estado agregado de los reembolsos:

  • Mientras cualquier reembolso esté pending, la transacción está en refund_pending.
  • Cuando el importe capturado ha sido reembolsado en su totalidad, la transacción pasa a refunded.
  • Cuando solo se ha reembolsado parte del importe capturado, la transacción pasa a partially_refunded.

Reembolsos en duda y el saldo reembolsable

Un reembolso reserva su importe en cuanto se inicia: el saldo reembolsable que la API aplica cuenta los reembolsos pending además de los succeeded. Mientras un reembolso anterior aún se está confirmando, un segundo reembolso que llevaría el total por encima del importe capturado se rechaza con 400 (validation_error), de modo que una solicitud reintentada o enviada por duplicado no puede mover el mismo dinero dos veces.

Un reembolso solo sale de pending cuando el proveedor da una respuesta definitiva:

  • Si el proveedor rechaza el reembolso, pasa a failed (consulta failure_reason) y su importe reservado se libera — corrige la solicitud e inténtalo de nuevo.
  • Si el proveedor no devuelve ningún resultado definitivo — un timeout o un error del lado del proveedor — el reembolso permanece pending y la transacción permanece refund_pending. El importe no se libera, porque los fondos pueden ya estar moviéndose; la resolución no es automática.

Como el resultado puede resolverse de forma asíncrona, suscríbete a los webhooks para el resultado final en lugar de reenviar el reembolso.


Endpoints

POST/api/v1/transactions/:id/refund

Reembolsa una transacción capturada. Admite reembolsos totales y múltiples reembolsos parciales.

OrgMerchant

Auth: clave de Organization (con merchant_id) o clave de Merchant. Ámbito requerido: transactions:write.

Reembolsa una transacción capturada previamente. Omite amount para un reembolso total del importe capturado restante, o pasa amount para un reembolso parcial.

La transacción debe estar en estado authorized o refund_pending, y el amount del reembolso no debe superar el importe capturado restante. Otros estados devuelven 422 business_rule_error; un importe superior al saldo restante devuelve 400 validation_error.

Parámetros de ruta

FieldTypeRequiredDescription
idstringSíID de transacción (tx_…) a reembolsar.

Parámetros de consulta

FieldTypeRequiredDescription
merchant_idstringCondicionalMerchant de destino (mrc_…). Requerido al usar una clave de Organization; se infiere de una clave de Merchant.

Cuerpo

FieldTypeRequiredDescription
amountintegerNoImporte a reembolsar en unidades menores. Omítelo para un reembolso total. Debe ser ≥ 1 y ≤ el importe capturado restante.
reasonenumNoMotivo por el que se emitió el reembolso. Uno de duplicate, fraudulent, requested_by_customer.

Envía un encabezado Idempotency-Key en las solicitudes de reembolso para que un reintento automático nunca emita un segundo reembolso. Reproducir la misma clave devuelve el resultado original. Consulta Idempotencia y límites de frecuencia.

curl -X POST "https://api.tokeflow.com/api/v1/transactions/tx_777/refund" \
  -H "Authorization: Bearer sk_live_mer_…" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f1c9a7e-2b4d-4e0a-9c6f-7d1e2a3b4c5d" \
  -d '{
    "amount": 5000,
    "reason": "requested_by_customer"
  }'

Para un reembolso total, omite amount:

curl -X POST "https://api.tokeflow.com/api/v1/transactions/tx_777/refund" \
  -H "Authorization: Bearer sk_live_mer_…" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "duplicate" }'

Respuesta 200 OK

La respuesta confirma que el reembolso fue aceptado y refleja los totales de conciliación en la transacción. Los campos monetarios están en unidades menores.

{
  "success": true,
  "data": {
    "id": "tx_777",
    "refund_id": "ref_abc123",
    "status": "refund_pending",
    "amount_captured": 15000,
    "amount_refunded": 5000,
    "total_refunded": 5000,
    "updated_at": "2026-01-15T12:30:00.000Z"
  },
  "request_id": "req_8f3c2a1b9d4e",
  "timestamp": "2026-01-15T12:30:00.000Z"
}
FieldTypeDescription
idstringID de la transacción principal (tx_…).
refund_idstringEl registro de reembolso creado por esta solicitud (ref_…).
statusenumEstado de la transacción después de la solicitud — refund_pending.
amount_capturedintegerImporte total capturado en la transacción, sin cambios por el reembolso.
amount_refundedintegerImporte reembolsado por esta solicitud.
total_refundedintegerImporte acumulado reembolsado en todos los reembolsos de la transacción.
updated_atstringMarca de tiempo ISO 8601 UTC de la actualización.

Al reproducir una Idempotency-Key, este endpoint devuelve el resultado original con HTTP 200 en lugar de crear un nuevo reembolso.


GET/api/v1/transactions/:id/refunds

Enumera todos los reembolsos de una transacción, del más reciente al más antiguo.

OrgMerchant

Auth: clave de Organization (con merchant_id) o clave de Merchant. Ámbito requerido: transactions:read.

Úsalo para ver el historial de reembolsos, comprobar el estado de los reembolsos parciales o correlacionar los eventos de reembolso con las notificaciones de webhook.

Parámetros de ruta

FieldTypeRequiredDescription
idstringSíID de transacción (tx_…).

Parámetros de consulta

FieldTypeRequiredDescription
pageintegerNoNúmero de página (indexado desde 1). Predeterminado 1, mínimo 1.
limitintegerNoElementos por página. Predeterminado 20, máximo 100.
merchant_idstringCondicionalMerchant de destino (mrc_…). Requerido con una clave de Organization.
curl "https://api.tokeflow.com/api/v1/transactions/tx_777/refunds?page=1&limit=20" \
  -H "Authorization: Bearer sk_live_mer_…" \
  -H "Accept: application/json"

Respuesta 200 OK

{
  "success": true,
  "data": [
    {
      "id": "ref_def456",
      "payment_transaction_id": "tx_777",
      "amount": 4000,
      "currency": "BRL",
      "status": "succeeded",
      "reason": "requested_by_customer",
      "provider_refund_id": "rfnd_7Qx9KpL2",
      "failure_reason": null,
      "created_at": "2026-01-15T13:10:00.000Z",
      "updated_at": "2026-01-15T13:11:42.000Z"
    },
    {
      "id": "ref_abc123",
      "payment_transaction_id": "tx_777",
      "amount": 5000,
      "currency": "BRL",
      "status": "succeeded",
      "reason": "requested_by_customer",
      "provider_refund_id": "rfnd_3Mt0VbN8",
      "failure_reason": null,
      "created_at": "2026-01-15T12:30:00.000Z",
      "updated_at": "2026-01-15T12:31:05.000Z"
    }
  ],
  "meta": {
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 2,
      "total_pages": 1,
      "has_next": false,
      "has_prev": false
    }
  },
  "request_id": "req_5a2f7b1c8e0d",
  "timestamp": "2026-01-15T13:12:00.000Z"
}

GET/api/v1/transactions/:id/refunds/:refundId

Recupera un único reembolso.

OrgMerchant

Auth: clave de Organization (con merchant_id) o clave de Merchant. Ámbito requerido: transactions:read.

Úsalo en flujos de soporte — por ejemplo, para inspeccionar un reembolso tras una notificación de webhook, o cuando un reembolso falla o tarda en liquidarse.

Parámetros de ruta

FieldTypeRequiredDescription
idstringSíID de transacción (tx_…).
refundIdstringSíID de reembolso (ref_…).

Parámetros de consulta

FieldTypeRequiredDescription
merchant_idstringCondicionalMerchant de destino (mrc_…). Requerido con una clave de Organization.
curl "https://api.tokeflow.com/api/v1/transactions/tx_777/refunds/ref_abc123" \
  -H "Authorization: Bearer sk_live_mer_…" \
  -H "Accept: application/json"

Respuesta 200 OK

{
  "success": true,
  "data": {
    "id": "ref_abc123",
    "payment_transaction_id": "tx_777",
    "amount": 5000,
    "currency": "BRL",
    "status": "succeeded",
    "reason": "requested_by_customer",
    "provider_refund_id": "rfnd_3Mt0VbN8",
    "failure_reason": null,
    "created_at": "2026-01-15T12:30:00.000Z",
    "updated_at": "2026-01-15T12:31:05.000Z"
  },
  "request_id": "req_9c4e1d7a2b6f",
  "timestamp": "2026-01-15T12:32:00.000Z"
}

El objeto de reembolso

FieldTypeDescription
idstringID de reembolso (ref_…).
payment_transaction_idstringID de la transacción principal (tx_…).
amountintegerImporte del reembolso en unidades menores.
currencystringCódigo de moneda ISO 4217 (p. ej. BRL).
statusenumpending, succeeded, failed o cancelled.
reasonenum | nullduplicate, fraudulent o requested_by_customer. null si no se proporciona.
provider_refund_idstring | nullIdentificador opaco del reembolso en el proveedor conectado. Trátalo como una cadena opaca. null hasta que se asigne.
failure_reasonstring | nullDetalle legible del fallo cuando status es failed; de lo contrario, null.
created_atstringMarca de tiempo de creación ISO 8601 UTC.
updated_atstringMarca de tiempo de la última actualización ISO 8601 UTC.

Errores

Los endpoints de reembolso usan el sobre de error estándar.

HTTPTypeCuándo
400validation_erroramount supera el importe capturado restante, o tiene un formato incorrecto.
401authentication_errorClave de API ausente o inválida.
403authorization_errorLa clave carece del ámbito requerido, o la transacción está fuera del ámbito de la clave.
404not_found_errorTransacción o reembolso no encontrado, o no perteneciente al merchant.
409conflict_errorYa hay un reembolso en conflicto en curso.
422business_rule_errorLa transacción no está en estado authorized ni refund_pending.
429rate_limit_errorSe superó el límite de frecuencia por clave; espera y reintenta.
{
  "error": {
    "type": "business_rule_error",
    "code": "TRANSACTION_NOT_REFUNDABLE",
    "message": "Transaction must be in 'authorized' or 'refund_pending' status to be refunded.",
    "details": { "current_status": "failed" },
    "request_id": "req_2b8d4f6a1c3e",
    "timestamp": "2026-01-15T12:40:00.000Z"
  }
}

Relacionado

En esta página