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étodo | Cómo funciona |
|---|---|
| Débito Inmediato | El cliente autoriza el débito desde su banco en el momento del checkout. |
| Pago Móvil | El 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. |
| Tarjeta | El cliente paga con su tarjeta de débito o de crédito. |
| Domiciliación | No 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
| Gateway | Débito Inmediato | Pago Móvil | Tarjeta |
|---|---|---|---|
| R4 | Sí | No | No |
| Sypago | Sí | No | No |
| BNC | Sí | 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 checkout | Payment link | |
|---|---|---|
| Endpoint | POST /v1/checkout/sessions | POST /v1/payment-links |
| Usos | Un solo pago — el link se consume | Reutilizable — cada visita crea una sesión nueva |
| Cliente | Opcional (customer) | Nunca se prellena: lo registra quien abra el link |
| Vigencia | La sesión expira (expires_after_hours) | El link vive hasta que lo desactives |
| Items ad-hoc | Sí | No (solo items de producto) |
| Se elimina | Expira solo | No 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-linksresponden 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/sessionscon un plan de pago único responde 400 con el mensajeone_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>"
}'typees 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
typeviene en todos los planes que devuelveGET /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.
customeres 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.
2b. Cobra con un payment link reutilizable
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.
Atributos al crear un payment link
| Campo | Tipo | Notas |
|---|---|---|
plan | string | Requerido. Plan de tu organización, recurrente o de pago único |
redirect_url | string | A dónde se envía al cliente después de pagar |
expiration_hours | número (1-24) | Vigencia de cada sesión que genere el link. Predeterminado: 1 |
discount_id | string | Descuento de tu organización que se aplica a cada sesión. Uno que no sea tuyo devuelve 404 |
apply_discount_to_additional_items | booleano | Predeterminado true. Si es true, el descuento también afecta a los items adicionales, no solo al plan |
items | lista (máx. 100) | Solo items de producto (type: "product", product_id, plan_id, quantity) |
allow_manual_payments | booleano | Predeterminado true. En pago único debe quedar en true |
trial_days | número (0-365) | Solo planes recurrentes |
active | booleano | Predeterminado 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ón | Mensaje |
|---|---|
| Mezclar un plan de pago único con items de planes recurrentes | One-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 0 | One-time checkout does not support trial days. Remove trialDays or use a recurring plan. |
Enviar allow_manual_payments: false | One-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 deshabilitada | one_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).
3. Consulta y administra tus payment links
# 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.