Cobros
Cobra una factura con el usuario delante, con su tarjeta guardada o con una nueva, completa la autenticación del banco y consulta el historial de intentos.
Actualizado el . 4 min de lectura
Incluido en los planes Profesional y Avanzado. Ver precios
El servicio de cobros cobra una factura ya emitida mientras el usuario está en tu aplicación: con una tarjeta que guardó antes o con una nueva que introduce en ese momento. Es la pieza entre Facturas, que emite, y Métodos de pago, que guarda tarjetas. Para cobrar sin el usuario delante se usa otra vía: invoices.charge, contra una autorización de cobro. Funciona con la clave de tu aplicación externa, siempre desde tu servidor.
Antes de empezar
| JavaScript | Python | Para qué | Scope |
|---|---|---|---|
payments.payInvoice(facturaId, userId, pmId?, { saveCard? }) | payments.pay_invoice(invoice_id, user_id, payment_method_id, save_card=...) | Iniciar el cobro | billing:charge |
payments.getPaymentIntents(facturaId, userId) | payments.get_payment_intents(invoice_id, user_id) | Historial de intentos de cobro | crm:invoices:read |
El cobro exige los aplicativos Finanzas y Pagos con tarjeta (Stripe) en el plan: sin ellos, la respuesta es un 403 MODULE_NOT_ENABLED, que no arregla ningún permiso. Si la pasarela está apagada en UTILIA OS, la respuesta es un 503 con errorCode STRIPE_NOT_CONFIGURED. Para no llegar a ese punto, pregunta antes con paymentMethods.getReadiness() al abrir la pantalla de pago, como se explica en Comprobar si se puede cobrar.
Cobrar una factura
Hay dos modos, según pases o no una tarjeta guardada:
- Con tarjeta guardada (
paymentMethodId): UTILIA OS confirma el cobro en la misma petición. Si el banco no pide autenticación, la respuesta llega conalreadySucceededen verdadero. - Con tarjeta nueva (sin
paymentMethodId): UTILIA OS prepara el cobro y te devuelve los datos para que tu web monte Stripe Elements y el usuario introduzca la tarjeta. ConsaveCardquedará guardada después del cobro; guardarla no autoriza cobros futuros, y el usuario debe saber que se guarda.
La respuesta trae clientSecret (para confirmar en el navegador; no lo guardes), paymentIntentId (identificador de UTILIA OS, no el pi_ de Stripe), status, alreadySucceeded, publishableKey y stripeAccountId. Los estados posibles son REQUIRES_PAYMENT_METHOD, REQUIRES_CONFIRMATION, REQUIRES_ACTION, PROCESSING, SUCCEEDED, CANCELED y FAILED.
// Con la tarjeta predeterminada
const cards = await sdk.paymentMethods.list('user_01HXYZ');
const card = cards.find((c) => c.isDefault);
const result = await sdk.payments.payInvoice(invoiceId, 'user_01HXYZ', card?.id);
if (result.alreadySucceeded) {
// Cobrada. Espera el aviso INVOICE_PAID para darla por pagada.
} else if (result.status === 'REQUIRES_ACTION') {
// El banco pide autenticación: tu web la completa con clientSecret.
enviarALaWeb(result.clientSecret, result.publishableKey, result.stripeAccountId);
}
// Con una tarjeta nueva, que se guardará tras el cobro
const nueva = await sdk.payments.payInvoice(invoiceId, 'user_01HXYZ', undefined, { saveCard: true });Cuando el banco pide autenticación (3D Secure), el cobro termina en tu web y la confirmación te llega por aviso:
Cobro con autenticación del banco
Participan: Tu servidor, Tu web, UTILIA OS, Stripe.
De Tu servidor a UTILIA OS:
POST /api/external/v1/invoices/:id/pay
payInvoice con la factura, el usuario y su tarjeta guardada.De UTILIA OS a Tu servidor:
status REQUIRES_ACTION y clientSecret (respuesta)
De Tu servidor a Tu web:
clientSecret, publishableKey y stripeAccountId
De Tu web a Stripe:
confirmCardPayment(clientSecret)
El banco autentica al usuario.De Stripe a UTILIA OS:
Aviso de cobro confirmado
UTILIA OS marca la factura como pagada.De UTILIA OS a Tu servidor:
Aviso INVOICE_PAID
Aquí das la factura por pagada.
En tu web, la confirmación con Stripe es igual sea cual sea el lenguaje de tu servidor:
import { loadStripe } from '@stripe/stripe-js';
const stripe = await loadStripe(publishableKey, { stripeAccount: stripeAccountId });
const { error } = await stripe!.confirmCardPayment(clientSecret);
if (error) console.error(error.message);
// Sin error, el cobro está hecho: espera el aviso INVOICE_PAID.La respuesta de payInvoice puede quedarse en PROCESSING o completarse después en el navegador: la fuente de verdad del cobro es el aviso INVOICE_PAID en tu servidor, también cuando no hay autenticación del banco.
Cuando la tarjeta se rechaza
Hay que distinguir dos situaciones:
El banco rechaza la tarjeta. No es una excepción: payInvoice devuelve el resultado con alreadySucceeded en falso y estado FAILED o REQUIRES_PAYMENT_METHOD. El motivo está en el historial, en el último intento: errorCode trae el código de Stripe (por ejemplo, card_declined) y errorMessage, el mensaje ya traducido al castellano. Enséñalo y pide otra tarjeta.
El cobro no se puede ni iniciar. Entonces sí se lanza un UtiliaSDKError, y el motivo viaja en errorCode (error_code en Python), no en code:
errorCode | Qué pasa |
|---|---|
INVOICE_ALREADY_PAID, INVOICE_ALREADY_CANCELLED, INVOICE_STATE_INVALID | La factura no se puede cobrar en su estado actual. |
PAYMENT_METHOD_NOT_FOUND, PAYMENT_METHOD_INACTIVE | La tarjeta no existe o ya no está activa. |
PAYMENT_METHOD_ACTIVE_INTENT | Ya hay un cobro en curso con esa tarjeta. |
BILLING_NOT_ENABLED, STRIPE_ACCOUNT_NOT_READY | La cuenta de cobro de la organización no está lista. |
VALIDATION_ERROR | Otros fallos al iniciar el cobro; el mensaje de Stripe viene en message. |
No existen códigos del tipo STRIPE_CARD_DECLINED: un rechazo nunca llega como excepción. La lista completa de códigos de facturación está en Códigos de error de facturación.
import { UtiliaSDKError } from '@utilia-os/sdk-js';
try {
const result = await sdk.payments.payInvoice(invoiceId, 'user_01HXYZ', pmId);
if (!result.alreadySucceeded && result.status !== 'REQUIRES_ACTION') {
const [last] = await sdk.payments.getPaymentIntents(invoiceId, 'user_01HXYZ');
mostrarRechazo(last?.errorMessage); // por ejemplo, «Tu banco rechazó el cobro.»
}
} catch (error) {
if (error instanceof UtiliaSDKError && error.errorCode === 'INVOICE_ALREADY_PAID') {
// Nada que cobrar
} else {
throw error;
}
}Historial de intentos
getPaymentIntents devuelve los intentos de cobro de una factura, del más reciente al más antiguo. Cada uno trae id, amount, currency, status, initiatedBy (CUSTOMER, STAFF o SYSTEM_RECURRING), las fechas createdAt, succeededAt y failedAt (cadenas ISO 8601), y, si falló, errorCode y errorMessage.