# Autorizaciones de cobro

Recoger, consultar y retirar la autorización que permite cobrar a un usuario sin que esté delante: texto de consentimiento, prueba legal y alcances, en /external/v1/payment-authorizations.

Una autorización de cobro es el consentimiento firmado con el que tu usuario te permite cargar en su tarjeta sin que esté delante. Sin ella no hay renovación mensual ni cobro programado: un cargo sin autorización es un cargo no autorizado.

Estos endpoints están bajo `/external/v1/payment-authorizations`, exigen la cabecera `X-Api-Key` y comparten un límite de 60 peticiones por minuto. Cada aplicación solo ve las autorizaciones de sus propios usuarios.

La autorización la registra tu servidor porque el usuario acepta el texto dentro de tu aplicación: tu servidor es quien conoce la dirección IP y el navegador desde los que aceptó. Esos dos datos, junto con el texto íntegro, la versión y el momento exacto, son la prueba legal del consentimiento. UTILIA OS los guarda sin posibilidad de cambio y marca al firmante como `EXTERNAL`.

**Registrar una autorización de cobro**

Participantes: Tu servidor, Navegador y UTILIA OS.

1. Tu servidor → UTILIA OS: `GET /api/external/v1/payment-authorizations/consent-text`. Con scope RECURRING o SCHEDULED_INVOICE.
2. UTILIA OS → Tu servidor (respuesta): text y version. Con el nombre fiscal de tu organización.
3. Tu servidor → Navegador: Muestra el texto tal cual
4. Navegador → Tu servidor: El usuario acepta. Guarda el momento, la IP y el navegador.
5. Tu servidor → UTILIA OS: `POST /api/external/v1/payment-authorizations`. El mismo texto y versión, la aceptación y el setupIntentId o paymentMethodId.
6. UTILIA OS → Tu servidor (respuesta): 201 con la autorización
7. UTILIA OS → Tu servidor: Aviso PAYMENT_AUTHORIZATION_GRANTED

_La autorización es la prueba legal que permite cobrar más tarde sin que el usuario esté delante._

## Plan y permisos

| Concepto | Valor |
| - | - |
| Aplicativos del plan | Finanzas y Pagos con tarjeta (Stripe) |
| Capacidad del plan | Cobro automático (`finance.direct_debit`) |

Sin alguno de los tres, la respuesta llega antes de comprobar ningún permiso: `403` con `errorCode: 'MODULE_NOT_ENABLED'` y su `moduleKey` si falta un aplicativo, o `403` con `errorCode: 'CAPABILITY_NOT_ENABLED'` y su `capabilityKey` si falta la capacidad.

| Endpoint | Permiso de la API (scope) |
| - | - |
| `GET /payment-authorizations/consent-text` | `billing:payment-authorizations:read` |
| `GET /payment-authorizations` | `billing:payment-authorizations:read` |
| `GET /payment-authorizations/:id` | `billing:payment-authorizations:read` |
| `POST /payment-authorizations` | `billing:payment-authorizations:write` |
| `POST /payment-authorizations/:id/revoke` | `billing:payment-authorizations:revoke` |

`billing:payment-authorizations:write` permite a la aplicación crear la prueba que autoriza cobros futuros, así que es un permiso crítico financiero: solo lo concede un Super Administrador, con un motivo que queda en la auditoría. No hay periodo de gracia: sin el permiso, la llamada responde `403 INSUFFICIENT_SCOPE` desde el primer día.

## Pedir el texto de consentimiento

**`GET /api/external/v1/payment-authorizations/consent-text`**

Devuelve el texto oficial que el usuario tiene que aceptar y su versión vigente. Muéstraselo tal cual antes de que acepte.

### Parámetros de consulta

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `scope` | `enum` | No | RECURRING para las cuotas de una suscripción y SCHEDULED_INVOICE para cobros programados de facturas sueltas. Por defecto, RECURRING. |

### Respuesta (200)

```json
{
  "text": "Autorizo a Estudio Marea SL a cargar en la tarjeta indicada las cuotas de mi suscripción hasta que retire esta autorización.",
  "scope": "RECURRING",
  "companyName": "Estudio Marea SL",
  "version": "1.2.0"
}
```

El texto lleva el nombre fiscal de la organización, así que no lo reescribas ni lo reutilices entre organizaciones. Al registrar la autorización tendrás que devolver ese mismo texto y esa misma versión.

## Registrar la autorización

**`POST /api/external/v1/payment-authorizations`**

Registra la autorización que el usuario acaba de firmar dentro de tu aplicación, con la prueba de quién la aceptó y desde dónde.

### Cuerpo

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `userId` | `string` | Sí | Identificador en tu aplicación (externalId) del usuario que firma. |
| `setupIntentId` | `string` | No | SetupIntent confirmado en el navegador del usuario, con el formato seti\_…. Obligatorio salvo que envíes paymentMethodId. |
| `paymentMethodId` | `string` | No | Tarjeta ya guardada del usuario. Alternativa a setupIntentId cuando la tarjeta se registró antes. |
| `consentText` | `string` | Sí | Texto íntegro que el usuario aceptó (máximo 4.000 caracteres). Tiene que coincidir con el que devuelve consent-text para ese mismo alcance. |
| `consentVersion` | `string` | Sí | Versión del texto aceptado, en formato MAYOR.MENOR.PARCHE. Tiene que ser la vigente. |
| `acceptedAt` | `string` | Sí | Momento exacto de la aceptación, en formato ISO 8601. |
| `acceptedIp` | `string` | Sí | Dirección IP desde la que el usuario aceptó. Parte de la prueba legal. |
| `acceptedUserAgent` | `string` | No | Navegador desde el que aceptó, máximo 500 caracteres. Parte de la prueba legal. |
| `scope` | `enum` | Sí | RECURRING o SCHEDULED_INVOICE. Los alcances de un solo uso los crea UTILIA OS al cobrar y no se pueden pedir desde fuera. |

### Respuesta (201)

```json
{
  "id": "ckauth1234567890",
  "externalUserId": "user_01HXYZ",
  "scope": "RECURRING",
  "active": true,
  "paymentMethod": {
    "id": "ckpm1234567890",
    "brand": "visa",
    "last4": "4242",
    "expMonth": 11,
    "expYear": 2029
  },
  "consentVersion": "1.2.0",
  "acceptedAt": "2026-10-01T09:10:02.000Z",
  "consumedAt": null,
  "revokedAt": null,
  "revokedBy": null,
  "createdAt": "2026-10-01T09:10:02.000Z"
}
```

La respuesta no devuelve el número de la tarjeta ni el texto íntegro del consentimiento. Produce el aviso `PAYMENT_AUTHORIZATION_GRANTED`, dirigido a tu aplicación.

> **Atención: Cuotas y cobros sueltos necesitan dos autorizaciones**
>
> El alcance `RECURRING` cubre las cuotas de una suscripción. Un cobro programado de una factura suelta exige una autorización de alcance `SCHEDULED_INVOICE`: la primera no vale para lo segundo, y la respuesta es `409 PAYMENT_AUTHORIZATION_NOT_ACTIVE` con el alcance que hace falta.

### Errores

| Situación | HTTP | `errorCode` |
| - | - | - |
| El texto o la versión no son los vigentes | 422 | `PAYMENT_AUTHORIZATION_CONSENT_MISMATCH` |
| Ni `setupIntentId` ni `paymentMethodId` | 400 | `VALIDATION_ERROR` |
| El alcance no se puede autorizar desde fuera | 400 | `VALIDATION_ERROR` |
| El usuario indicado no existe en la aplicación | 404 | `EXTERNAL_USER_NOT_FOUND` |
| La tarjeta no existe o no es de ese usuario | 404 | `PAYMENT_METHOD_NOT_FOUND` |
| La cuenta de la pasarela no está lista para operar | 409 | `STRIPE_ACCOUNT_NOT_READY` |

El consentimiento se comprueba **antes** de tocar la pasarela: si el texto no es el vigente, no se consume el SetupIntent.

## Listar autorizaciones

**`GET /api/external/v1/payment-authorizations`**

Lista paginada de las autorizaciones de los usuarios de la aplicación.

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `userId` | `string` | No | Filtra por el identificador del usuario en tu aplicación. Sin él se listan las de todos tus usuarios. |
| `scope` | `enum` | No | Filtra por alcance. |
| `active` | `boolean` | No | Filtra por vigencia. |
| `page` | `integer` | No | Página, por defecto 1. |
| `limit` | `integer` | No | Elementos por página, por defecto 20 y como máximo 100. |

La respuesta tiene la forma `{ data, total, page, limit, totalPages }`.

## Detalle de una autorización

**`GET /api/external/v1/payment-authorizations/:id`**

Detalle de una autorización concreta.

Parámetro de ruta: `id`.

Exige `userId` en la cadena de consulta. Si la autorización no es de ese usuario o de esa aplicación, responde `404 PAYMENT_AUTHORIZATION_NOT_FOUND`.

## Retirar una autorización

**`POST /api/external/v1/payment-authorizations/:id/revoke`**

Retira la autorización. Es inmediato y arrastra todo lo que dependía de ella.

Parámetro de ruta: `id`.

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `userId` | `string` | Sí | Identificador del usuario en tu aplicación (externalId). |
| `reason` | `string` | No | Motivo de la retirada, para la auditoría. |

> **Atención: Retirar no se puede deshacer**
>
> La retirada cancela los cobros programados pendientes que dependían de la autorización y pasa a cobro manual las suscripciones que la usaban. Para volver a cobrar sin el usuario delante hay que recoger una autorización nueva.

Produce el aviso `PAYMENT_AUTHORIZATION_REVOKED`, dirigido a tu aplicación.

## Cómo se consume una autorización

| Aviso | Qué significa |
| - | - |
| `PAYMENT_AUTHORIZATION_GRANTED` | El usuario ha autorizado el cobro. |
| `PAYMENT_AUTHORIZATION_CONSUMED` | Una autorización de un solo uso se ha gastado al ejecutarse el cobro que respaldaba. Deja de servir. |
| `PAYMENT_AUTHORIZATION_REVOKED` | La autorización se ha retirado, por el usuario o por tu equipo. |

Las de alcance `RECURRING` no se consumen: sirven para todas las cuotas hasta que se retiren.

## Con el SDK

### JavaScript / TypeScript

```typescript
import { UtiliaSDK } from '@utilia-os/sdk-js';

const sdk = new UtiliaSDK({
  baseURL: 'https://os.utilia.ai/api',
  apiKey: process.env.UTILIA_API_KEY!,
});

// 1. El texto que hay que mostrar al usuario
const consentimiento = await sdk.paymentAuthorizations.getConsentText({
  scope: 'RECURRING',
});

// 2. Lo que se guarda cuando acepta
const autorizacion = await sdk.paymentAuthorizations.create({
  userId: 'user_01HXYZ',
  setupIntentId: 'seti_1OabcDEF',
  consentText: consentimiento.text,
  consentVersion: consentimiento.version,
  acceptedAt: new Date().toISOString(),
  acceptedIp: peticion.ip,
  acceptedUserAgent: peticion.headers['user-agent'],
  scope: 'RECURRING',
});

// 3. Retirarla más adelante
await sdk.paymentAuthorizations.revoke(autorizacion.id, {
  userId: 'user_01HXYZ',
  reason: 'El usuario la retiró desde su perfil.',
});
```

### Python

```python
import os
from datetime import datetime, timezone

from utilia_sdk import UtiliaSDK
from utilia_sdk.models import (
    CreateExternalPaymentAuthorizationInput,
    RevokeExternalPaymentAuthorizationInput,
)

sdk = UtiliaSDK(
    base_url="https://os.utilia.ai/api",
    api_key=os.environ["UTILIA_API_KEY"],
)

consentimiento = await sdk.payment_authorizations.get_consent_text(scope="RECURRING")

autorizacion = await sdk.payment_authorizations.create(
    CreateExternalPaymentAuthorizationInput(
        user_id="user_01HXYZ",
        setup_intent_id="seti_1OabcDEF",
        consent_text=consentimiento.text,
        consent_version=consentimiento.version,
        accepted_at=datetime.now(timezone.utc).isoformat(),
        accepted_ip=peticion.ip,
        accepted_user_agent=peticion.headers.get("user-agent"),
        scope="RECURRING",
    )
)

await sdk.payment_authorizations.revoke(
    autorizacion.id,
    RevokeExternalPaymentAuthorizationInput(
        user_id="user_01HXYZ",
        reason="El usuario la retiró desde su perfil.",
    ),
)
```

## Siguiente paso

- [Suscripciones](https://os.utilia.ai/docs/developers/api/subscriptions): Crear la cuota mensual que usará esta autorización.
- [Cobros programados](https://os.utilia.ai/docs/developers/api/scheduled-charges): Cobrar una factura suelta en una fecha futura.
- [Acciones sobre una factura](https://os.utilia.ai/docs/developers/api/invoice-actions): Cobrar ahora mismo sin el usuario delante.
- [Métodos de pago](https://os.utilia.ai/docs/developers/api/payment-methods): Guardar la tarjeta antes de recoger la autorización.
