Documentación

API REST

El contrato entre tu instancia y el plano de control. Todo el dinero que mueve Payezz pasa por aquí.

Parcial

La ruta de cargos existe y compila; le faltan sus tres consultas a base de datos, así que hoy responde con error.

1. Autenticación

Todas las rutas cuelgan de https://api.payezz.app/v1/. Cada instancia lleva un par instance_id más instance_secret, inyectado como secreto en el contenedor durante la provisión y rotable desde el plano de control. Tú no tienes que gestionarlo: tu instancia ya lo tiene.

Cada petición firma HMAC-SHA256(secret, timestamp + "." + cuerpo_crudo). El cuerpo que se firma es el crudo, byte a byte, no el resultado de volver a serializar el objeto: dos serializaciones distintas del mismo JSON producen firmas distintas.

POST /v1/payments/charges HTTP/1.1
Host: api.payezz.app
Content-Type: application/json
Payezz-Instance: inst_7f3a91c0
Payezz-Timestamp: 1789459200
Payezz-Signature: v1=6b1f...c39a
Idempotency-Key: renewal:svc_4821:2026-10
Payezz-Api-Version: 2026-09-01

Reglas que no se negocian en la verificación:

  • Comparación en tiempo constante. Comparar secretos con el operador de desigualdad filtra información por el tiempo de respuesta.
  • Ventana de reloj de más o menos 300 segundos. Fuera de ventana, 401.
  • Caché de las firmas ya vistas dentro de la ventana, para bloquear repeticiones.
  • Payezz-Api-Version obligatoria en todas las peticiones.

2. Clientes

POST /v1/customers crea o recupera el cliente en tu cuenta conectada. El mapeo entre tu identificador y el de Stripe lo guarda el plano de control, y la respuesta siempre trae el identificador vigente, aunque el anterior se hubiera borrado.

// petición
{
  "external_id": "cli_918",
  "email": "ana@ejemplo.com",
  "name": "Ana Ruiz",
  "metadata": { "instance_customer_id": "918" }
}

// respuesta 200
{ "customer_id": "cus_QZ...", "created": true }

3. Cobro con el cliente delante

POST /v1/payments/intents es el cobro de un checkout: hay alguien en la pantalla que puede autenticarse si su banco lo pide. Devuelve el client_secret con el que tu instancia monta el formulario de pago, además del desglose de comisión y el identificador de la entrada del libro de comisiones.

// petición
{
  "customer_id": "cus_QZ...",
  "amount_minor": 1499,
  "currency": "eur",
  "purpose": "new_order",
  "reference": { "kind": "invoice", "id": "inv_2026_0417" },
  "description": "Plan Estándar, octubre 2026",
  "statement_descriptor_suffix": "ACME",
  "metadata": { "order_id": "4821" },
  "automatic_payment_methods": true,
  "return_url": "https://tienda.acme.com/checkout/volver"
}

// respuesta 200
{
  "payment_intent_id": "pi_3Q...",
  "status": "requires_payment_method",
  "client_secret": "pi_3Q..._secret_...",
  "publishable_key": "pk_live_...",
  "stripe_account": "acct_1Q...",
  "fee": { "pct_bps": 190, "fixed_minor": 20,
           "total_minor": 48, "currency": "eur" },
  "ledger_entry_id": "cle_01J..."
}

Los valores admitidos en purpose son new_order, renewal, invoice, proration, reactivation, addon y manual_admin. No es decoración: es lo que permite conciliar después qué parte de la facturación viene de cada sitio.

4. Cobro sin el cliente delante

POST /v1/payments/charges es la renovación: se cobra contra un método de pago guardado, sin nadie mirando. Tiene tres desenlaces y los tres hay que tratarlos.

// petición
{
  "customer_id": "cus_QZ...",
  "payment_method_id": "pm_1Q...",
  "amount_minor": 1499,
  "currency": "eur",
  "purpose": "renewal",
  "reference": { "kind": "service", "id": "svc_4821" },
  "description": "Renovación, Plan Estándar",
  "metadata": { "service_id": "4821", "period": "2026-10" }
}

// 200, cobrado
{ "payment_intent_id": "pi_3Q...", "status": "succeeded",
  "fee": { "total_minor": 48, "currency": "eur" },
  "ledger_entry_id": "cle_01J..." }

// 200, el banco pide autenticación fuera de sesión
{ "payment_intent_id": "pi_3Q...", "status": "requires_action",
  "client_secret": "pi_3Q..._secret_...",
  "recovery_url": null, "decline_code": null }

// 402, rechazo del banco
{ "error": { "code": "card_declined",
             "decline_code": "insufficient_funds",
             "retryable": true, "retry_after_hint_days": 3,
             "message_for_customer": "Tu banco ha rechazado el pago." } }

El caso requires_action es el que más sistemas de facturación tratan mal: marcan el servicio como impagado y lo suspenden sin dar salida. La instancia debe enviar al comprador un correo con un enlace a una página de confirmación que reanude ese PaymentIntent. No es un fallo de cobro, es un cobro a medias.

5. Métodos de pago guardados

POST /v1/payments/setup-intents para guardar una tarjeta, GET /v1/payments/methods para listarlas y POST /v1/payments/methods/{id}/detach para desvincularlas. Siempre sobre la cuenta conectada, nunca sobre la plataforma.

6. Reembolsos

POST /v1/refunds admite reembolso total o parcial. La política de qué parte de la comisión se devuelve y qué parte se retiene la aplica el plano de control, no la instancia, para que sea la misma para todo el mundo y quede registrada.

// petición
{ "payment_intent_id": "pi_3Q...", "amount_minor": 500,
  "reason": "requested_by_customer", "note": "Error de facturación" }

// respuesta
{ "refund_id": "re_3Q...", "status": "succeeded",
  "commission": { "returned_minor": 10, "retained_minor": 20,
                  "policy": "pct_proporcional_fijo_retenido" } }

7. Comisiones de pagos fuera de la pasarela

Cuando cobras por transferencia, en efectivo o por cualquier vía que no pasa por la pasarela, y marcas la factura como pagada, tu instancia llama antes a POST /v1/commissions/manual. La comisión se devenga, se acumula y se liquida al cierre del mes.

// petición
{
  "reference": { "kind": "invoice", "id": "inv_2026_0418" },
  "amount_minor": 2400,
  "currency": "eur",
  "method": "bank_transfer",
  "occurred_at": "2026-10-03T09:12:00Z",
  "external_ref": "Transferencia 4409/2026"
}

// respuesta
{ "ledger_entry_id": "cle_01J...", "status": "accrued",
  "fee": { "total_minor": 66, "currency": "eur" },
  "settles_in_period": "2026-10" }

Los valores de method son bank_transfer, cash, paypal_external, crypto, other y admin_marked_paid. Si la llamada falla, la factura se queda en pendiente con un aviso: no se pierde tu trabajo, pero tampoco se salta el registro.

8. Estado de la cuenta y comisiones

GET /v1/account devuelve el estado de tu cuenta conectada, y GET /v1/commissions/summary el resumen de comisión cobrada y devengada. Son las dos llamadas que alimentan la sección de comisión de tu propio panel.

9. Idempotencia

Cada petición que mueve dinero lleva Idempotency-Key. El formato recomendado es <propósito>:<referencia>:<periodo>, por ejemplo renewal:svc_4821:2026-10.

Nunca uses la fecha de hoy como parte de la clave. Una clave del estilo renewal-4821-2026-10-03 hace que un reintento al día siguiente cobre otra vez, porque la clave cambia. El periodo que factura sí puede formar parte de la clave; el día en que se intenta, no.

  • El plano de control guarda (instance_id, idempotency_key) con su respuesta durante siete días. Stripe solo garantiza veinticuatro horas; la ventana es mayor a propósito, porque un reintento humano tras un incidente llega tarde.
  • La clave que viaja a Stripe se deriva con hmac(instance_id, idempotency_key), para que dos instancias no puedan colisionar ni sondearse.
  • Repetir una clave con un cuerpo distinto devuelve 409 idempotency_key_reuse, nunca un cobro nuevo.

10. Errores y reintentos

SituaciónRespuestaQué hace tu instancia
Firma inválida o reloj fuera de ventana401No reintentar. Alertar: es configuración o reloj desajustado.
Cuenta conectada sin cobros habilitados409 account_not_readyNo reintentar. Avisar al proveedor.
Moneda no soportada por el plan422 currency_not_supportedNo reintentar.
Rechazo del banco402 con retryableProgramar según la política de dunning.
Error nuestro o de Stripe, o tiempo agotado503 o sin respuestaReintentar con la misma clave de idempotencia, con espera creciente, hasta seis veces en veinticuatro horas.
Límite de peticiones429 con Retry-AfterRespetar la cabecera.

La regla que resume todas las demás: un tiempo agotado no es un fallo de cobro. Puede haber cobrado. La instancia jamás trata un 503 como «no se cobró»: reintenta con la misma clave y espera al webhook.

¿Falta algo que necesitas para integrarte? Escribe a hola@payezz.app con el caso concreto.