Venflow API

Pago único

Pago único (one-time)

Un cobro de pago único le cobra al cliente una sola vez y no crea ninguna suscripción: no hay renovación, ni próximo período, ni ciclo de facturación.

Métodos de pago disponibles

Los métodos de pago dependen del gateway (el banco o procesador con el que cobra tu organización). No todos los gateways ofrecen todos los métodos: el checkout le muestra al cliente solo los que tu gateway sabe procesar y que tú tienes activados.

MétodoCómo funciona
Débito InmediatoEl cliente autoriza el débito desde su banco en el momento del checkout.
Pago MóvilEl cliente hace el Pago Móvil desde la app de su banco y Venflow lo verifica directamente con el banco antes de dar el pago por bueno.
TarjetaEl cliente paga con su tarjeta de débito o de crédito.
DomiciliaciónNo disponible en pago único. Enrola la cuenta del cliente para cobrarle de forma recurrente, así que necesita una suscripción detrás.

Qué métodos ofrece cada gateway

GatewayDébito InmediatoPago MóvilTarjeta
R4SíNoNo
SypagoSíNoNo
BNCSíSíSí
  • Débito Inmediato está siempre disponible, en cualquier gateway, y no se puede desactivar: es la garantía de que tu cliente siempre tenga una forma de pagarte. Si tu gateway es R4 o Sypago, es el único método, y viene preseleccionado en el checkout: el cliente no tiene que elegir nada.
  • Pago Móvil y Tarjeta son opcionales y solo existen en BNC. Se activan por organización y, cuando están activos, el cliente elige el método en el checkout.
  • Pago Móvil necesita que tu organización tenga configurados el número de cuenta BNC (20 dígitos), el teléfono de Pago Móvil y el RIF. Mientras falte alguno, el método no aparece.
  • Tarjeta solo se ofrece en cobros que el cliente inicia en el momento: pagos únicos y payment links. Nunca aparece en un checkout recurrente, porque Venflow no guarda los datos de la tarjeta y no podría volver a cobrarla.

Los métodos se leen cada vez que el cliente abre el checkout, no se congelan en la sesión ni en el link: si activas o desactivas uno, el cambio se ve de inmediato en los links que ya enviaste.

Si quieres ofrecer Pago Móvil o Tarjeta y tu organización cobra por BNC, pide que los activen. Con R4 o Sypago no es posible: esos gateways no los procesan.

Tienes dos formas de cobrarlo desde la API:

Sesión de checkoutPayment link
EndpointPOST /v1/checkout/sessionsPOST /v1/payment-links
UsosUn solo pago — el link se consumeReutilizable — cada visita crea una sesión nueva
ClienteOpcional (customer)Nunca se prellena: lo registra quien abra el link
VigenciaLa sesión expira (expires_after_hours)El link vive hasta que lo desactives
Items ad-hocSíNo (solo items de producto)
Se eliminaExpira soloNo se elimina: se desactiva

Usa una sesión para cobrarle a un cliente concreto (una factura, un pedido). Usa un payment link para cobrarle a muchos: lo publicas una vez y sirve para todos.

Requisito: la función debe estar habilitada

El pago único se habilita por organización. Mientras esté deshabilitada para la tuya:

  • Todos los endpoints de /v1/payment-links responden 404 con el cuerpo {"code":"not_found","message":"Feature not available"} — el recurso no está disponible para tu organización. Es el mismo 404 que usa el resto de la plataforma para una función no habilitada, así que puedes distinguirlo de una URL mal escrita por el mensaje.
  • POST /v1/checkout/sessions con un plan de pago único responde 400 con el mensaje one_time plans cannot be used for subscription checkout. Use a recurring plan instead. (el endpoint sigue existiendo porque también sirve planes recurrentes).

Si ves esos códigos con un plan one_time válido, pide que activen el pago único para tu organización.

1. Crea un plan de pago único

curl -X POST "https://api.venflow.app/v1/plans" \
  -H "Api-Key: pvk_live_abc123..." \
  -H "Organization: org_xyz789..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Curso intensivo",
    "type": "one_time",
    "price": 120.00,
    "product_id": "<product_id>"
  }'
  • type es lo que decide si el plan cobra una vez o se renueva. Solo acepta dos valores: "one_time" (un cobro y ya) y "recurring" (se renueva). Es un campo opcional y, si no lo mandas, el plan se crea como "recurring" — así que para un plan de pago único tienes que enviarlo explícitamente.
  • Un plan de pago único no lleva frequency — no se renueva, así que no hay cada-cuánto que definir.
  • El campo type viene en todos los planes que devuelve GET /v1/plans, así que puedes filtrar por él desde tu integración.

2a. Cobra con una sesión de checkout

curl -X POST "https://api.venflow.app/v1/checkout/sessions" \
  -H "Api-Key: pvk_live_abc123..." \
  -H "Organization: org_xyz789..." \
  -H "Content-Type: application/json" \
  -d '{
    "plan": "<one_time_plan_id>",
    "redirect_url": "https://tu-sitio.com/gracias",
    "items": [
      { "type": "product", "product_id": "<product_id>", "plan_id": "<one_time_plan_id>", "quantity": 2 },
      { "type": "adhoc", "name": "Envío", "price": 5.00, "quantity": 1 }
    ]
  }'

La respuesta es un objeto checkout.session — el mismo que devuelve un checkout recurrente, documentado campo por campo en POST /v1/checkout/sessions:

{
  "id": "7d3f9a12-...",
  "object": "checkout.session",
  "url": "https://pay.venflow.app/clients?org=org_xyz789...&session=7d3f9a12-...",
  "status": "pending",
  "customer": null,
  "plan": { "id": "<one_time_plan_id>", "name": "Curso intensivo", "amount": 12000 },
  "amount_subtotal": 12000,
  "amount_discount": 0,
  "amount_total": 12000,
  "discount": null,
  "expires_at": 1754643600,
  "redirect_url": "https://tu-sitio.com/gracias",
  "metadata": { "checkoutMode": "one_time" },
  "trial_days": null,
  "scheduled_start_date": null
}

Los dos campos que necesitas: url es el link que le envías al cliente, y amount_total es lo que se le cobrará, una sola vez.

Los montos van en centavos. amount_total: 12000 son 120,00 dólares — no doce mil. El price que enviaste al crear el plan sí va en dólares, así que la conversión ocurre entre una llamada y la otra.

En un pago único, trial_days y scheduled_start_date siempre vienen en null: son conceptos de suscripción y aquí no hay ninguna.

Ojo con metadata: además de las claves que tú envíes, una sesión de pago único trae siempre "checkoutMode": "one_time". Si lees tu propia metadata de vuelta, vas a encontrar esa clave extra.

  • customer es opcional: si lo omites, el checkout le pide los datos al cliente.
  • Los items ad-hoc (nombre + precio sueltos) sí se aceptan en una sesión de pago único: no hay ningún plan detrás de ellos, así que no arrastran nada recurrente.
  • Cuando el cliente paga, se emite una factura pagada y no se crea ninguna suscripción.
curl -X POST "https://api.venflow.app/v1/payment-links" \
  -H "Api-Key: pvk_live_abc123..." \
  -H "Organization: org_xyz789..." \
  -H "Content-Type: application/json" \
  -d '{
    "plan": "<one_time_plan_id>",
    "redirect_url": "https://tu-sitio.com/gracias",
    "expiration_hours": 2,
    "discount_id": "<discount_id>",
    "apply_discount_to_additional_items": true,
    "items": [
      { "type": "product", "product_id": "<product_id>", "plan_id": "<one_time_plan_id>", "quantity": 1 }
    ]
  }'

Respuesta:

{
  "id": "9f0c1e2a-...",
  "object": "payment_link",
  "url": "https://checkout.venflow.app/ul/9f0c1e2a-...",
  "active": true,
  "plan": "<one_time_plan_id>",
  "redirect_url": "https://tu-sitio.com/gracias",
  "expiration_hours": 2,
  "discount_id": "<discount_id>",
  "apply_discount_to_additional_items": true,
  "allow_manual_payments": true,
  "trial_days": null,
  "items": [
    {
      "id": "3b7d...",
      "product_id": "<product_id>",
      "plan_id": "<one_time_plan_id>",
      "name": "Manual del curso",
      "price": 15.00,
      "quantity": 1
    }
  ],
  "created": 1754640000,
  "livemode": true
}

url es el link que publicas. Cada visita genera una sesión de checkout nueva, con la vigencia que definiste en expiration_hours; el link en sí no se consume.

Cuidado con las unidades. items[].price va en dólares (15.00 son quince dólares), igual que el price de un plan. En cambio la sesión de checkout que genera el link reporta sus montos (amount_subtotal, amount_total, plan.amount) en centavos: esos mismos quince dólares son 1500. Si sumas los items de un link para compararlos con los totales de la sesión, convierte antes.

Los payment links también funcionan con planes recurrentes: en ese caso cada visita crea una suscripción, exactamente como el link universal del dashboard.

CampoTipoNotas
planstringRequerido. Plan de tu organización, recurrente o de pago único
redirect_urlstringA dónde se envía al cliente después de pagar
expiration_hoursnúmero (1-24)Vigencia de cada sesión que genere el link. Predeterminado: 1
discount_idstringDescuento de tu organización que se aplica a cada sesión. Uno que no sea tuyo devuelve 404
apply_discount_to_additional_itemsbooleanoPredeterminado true. Si es true, el descuento también afecta a los items adicionales, no solo al plan
itemslista (máx. 100)Solo items de producto (type: "product", product_id, plan_id, quantity)
allow_manual_paymentsbooleanoPredeterminado true. En pago único debe quedar en true
trial_daysnúmero (0-365)Solo planes recurrentes
activebooleanoPredeterminado true. Con false el link nace apagado

Los campos que no estén en esta lista se ignoran: la API los descarta al leer el cuerpo y responde como si no los hubieras enviado. Ojo con esto — si mandas, por ejemplo, un price dentro de un item para pisar el precio del plan, el link se crea igual y cobra el precio del plan. Revisa siempre la respuesta: es el estado real del link.

product_id y plan_id de cada item deben ser UUIDs de tu organización; uno mal formado devuelve 400 indicando el item exacto (items[0].plan_id must be a UUID).

Los payment links no aceptan items ad-hoc. La plantilla que se guarda exige un producto y su plan, así que un item suelto (nombre + precio) devuelve 400 indicándote que uses una sesión de checkout. Tampoco tienen metadata.

Reglas del pago único (y sus errores)

Estas reglas se validan al crear la sesión o el link, nunca después, para que nunca exista un link imposible de pagar. Todas devuelven 400:

SituaciónMensaje
Mezclar un plan de pago único con items de planes recurrentesOne-time checkout accepts only one_time additional items. Remove the recurring items or use a recurring plan. seguido de Recurring items: <nombres>. con los items que sobran
Enviar trial_days mayor que 0One-time checkout does not support trial days. Remove trialDays or use a recurring plan.
Enviar allow_manual_payments: falseOne-time checkout requires manual payments (Débito Inmediato). Set allowManualPayments to true or use a recurring plan.
Enviar scheduled_start_date (solo sesiones)One-time checkout does not support a scheduled start date. Remove scheduledStartDate or use a recurring plan.
Usar un plan one_time con la función deshabilitadaone_time plans cannot be used for subscription checkout. Use a recurring plan instead.

trial_days: 0 sí se acepta: es un "sin prueba" explícito, que es justo lo que un pago único ya es.

Los mensajes de la tabla son prefijos: algunos añaden detalle al final (los nombres de los items que causaron el rechazo, por ejemplo). Si tu integración distingue errores por el texto, compara por prefijo — o mejor, por el code de la respuesta (invalid_argument).

# Obtener uno
curl "https://api.venflow.app/v1/payment-links/<payment_link_id>" \
  -H "Api-Key: pvk_live_abc123..." \
  -H "Organization: org_xyz789..."

# Listar (más recientes primero, incluye los desactivados)
curl "https://api.venflow.app/v1/payment-links?limit=20" \
  -H "Api-Key: pvk_live_abc123..." \
  -H "Organization: org_xyz789..."

# Página siguiente: starting_after = id del último elemento que recibiste
curl "https://api.venflow.app/v1/payment-links?limit=20&starting_after=<ultimo_id>" \
  -H "Api-Key: pvk_live_abc123..." \
  -H "Organization: org_xyz789..."

La lista sigue la convención del resto de la API: object: "list", data y has_more.

Cuando has_more es true hay más links: pide la página siguiente con starting_after. Como los payment links no se eliminan, la lista solo crece y ese cursor es la única forma de llegar a los más antiguos. Un starting_after que no corresponda a un link tuyo devuelve 400, para que un bucle "seguir pidiendo hasta que has_more sea false" no se quede dando vueltas.

Desactivar y reactivar

Los payment links no se eliminan: se desactivan. Es tu interruptor de apagado.

curl -X PATCH "https://api.venflow.app/v1/payment-links/<payment_link_id>" \
  -H "Api-Key: pvk_live_abc123..." \
  -H "Organization: org_xyz789..." \
  -H "Content-Type: application/json" \
  -d '{ "active": false }'
  • Un link desactivado rechaza cualquier visita nueva y deja de generar sesiones.
  • Los pagos, facturas y suscripciones que ya se generaron con él no se tocan.
  • Volver a enviar {"active": true} lo deja operativo otra vez, y repetir el mismo valor no produce error.

Permisos de la clave API

Los endpoints de /v1/payment-links requieren que tu clave tenga los permisos del recurso de payment links (crear, leer y actualizar). Una clave sin ellos recibe el error de permisos habitual, igual que en el resto de la API.