# Guía de integración de un sistema de autorización personalizado

El código fuente actual admite dos proveedores de autorización: Licentivo y `custom_http`. El comerciante selecciona y habilita uno en **Configuración del sistema → Sistema de autorización**; todos los productos y los entornos sandbox/live usan el mismo proveedor. Las conexiones personalizadas y las asignaciones de planes se configuran por separado para cada producto y entorno. Esta función no incluye un sistema de autorización propio; los comerciantes siguen usando sus servicios de autorización y SDK.

El intercambio de cuentas de Licentivo, los SDK de nueve lenguajes y la gestión de dispositivos existentes mantienen el procedimiento original. Los productos personalizados sincronizan el estado de compra mediante el protocolo de este documento; los clientes consultan la entrega de la autorización en el centro de cuentas, sin pasar por el procedimiento del SDK de Licentivo.

## Inicio rápido

1. Implemente las cuatro interfaces HTTP siguientes en su servicio y configure identificadores y claves de firma HMAC independientes para cada conexión.
2. En «Sistema de autorización», seleccione «Servicio de autorización personalizado» y añada una conexión. Introduzca la dirección, el entorno, el identificador de conexión, el key ID y la clave en Base64 estándar, verifique y guarde, y haga clic en «Habilitar servicio de autorización personalizado».
3. Cree borradores de productos y planes de venta. El tipo de compra y el precio se gestionan en Launch; las reglas de autorización se gestionan en su propio sistema.
4. Seleccione el producto, el entorno y la conexión; introduzca las referencias externas del producto y del plan y guarde. El proveedor depende de la selección para todo el sitio; no se puede habilitar Licentivo simultáneamente para otros productos.
5. Publique una versión de prueba en la página de planes de venta, complete las pruebas de aceptación de pago, entrega, renovación, cambio a un plan superior, reembolso y recuperación ante fallos, y configure después el entorno de producción por separado.

La dirección, el entorno, el identificador de conexión y la política de acceso a la red no se pueden modificar después de crearlos; cree una conexión nueva cuando sea necesario. Las claves de firma se pueden rotar; deje el campo vacío para conservar la clave original. Si existen pedidos de otro sistema de autorización, no se puede cambiar directamente el proveedor de todo el sitio: primero debe completarse la migración. Tras el cambio, los planes originales quedan disponibles solo como vista previa y deben volver a publicarse; se conservan las credenciales de conexión y las asignaciones guardadas. Los productos con pedidos existentes no pueden cambiar directamente de conexión y las asignaciones de los planes en venta tampoco se pueden sustituir directamente; use una nueva versión del plan para precios o asignaciones nuevos.

Para configurar mediante API, llame primero a `PUT /api/v1/admin/licensing/provider` con el cuerpo de solicitud `{"provider":"custom_http"}` o `{"provider":"licentivo"}` y guarde después la asignación del producto. Los proveedores no habilitados no permiten guardar vinculaciones de autorización de productos ni asignaciones de políticas de planes de Licentivo.

La comprobación de la conexión verifica el protocolo, la autenticación, el entorno y los tipos de compra admitidos; no crea ni confirma planes externos en nombre del comerciante. Antes de cobrar pagos reales, debe completarse la aceptación de la integración en sandbox con planes externos reales.

## 3. Procedimiento de integración del comerciante

1. Implemente las cuatro interfaces de este documento delante del sistema de autorización existente; se conserva la lógica de licencias que ya tiene el sistema.
2. Cree una conexión de «Servicio de autorización personalizado» en Launch y configure la dirección HTTPS, el identificador de conexión, el entorno y la clave de firma.
3. Launch llama a la interfaz de comprobación de capacidades para confirmar la versión del protocolo, la compatibilidad con compras permanentes/de duración limitada y el método de entrega.
4. Seleccione esta conexión para el producto y asigne la versión del plan de venta a las referencias externas `product_ref` y `plan_ref`. Los identificadores externos pueden ser cadenas propias del comerciante; no es obligatorio usar UUID de Licentivo.
5. Complete en sandbox las pruebas de aceptación de pago, renovación, cambio a un plan superior, reembolso y recuperación ante fallos; publique después la configuración live por separado.
6. Tras verificar el pago, Launch guarda el pedido, la cobertura de compra y las tareas pendientes de envío en la misma transacción de base de datos.
7. El backend envía una instantánea completa de la compra al servicio de autorización personalizado; este conserva el registro recibido de forma persistente antes de confirmar la recepción.
8. El servicio de autorización procesa el estado objetivo. Launch consulta el acuse del evento original; solo `applied` confirma que esta sincronización ha terminado.
9. Launch obtiene los datos de entrega para los clientes autenticados que poseen la compra y muestra en el centro de clientes el código de autorización, el acceso a la activación o las instrucciones en texto sin formato. La aplicación se integra con el sistema de autorización del comerciante.
10. Las renovaciones, los cambios a planes superiores, los reembolsos, las reversiones de pago, las suspensiones y los vencimientos generan una nueva versión de la instantánea completa. El servicio de autorización procesa el estado objetivo más reciente.

Los entornos sandbox/live de un mismo producto se vinculan a conexiones independientes. Una vez que existen compras, cambiar de proveedor requiere una migración separada; los pedidos y las tareas antiguos no deben enviarse al servicio nuevo sin aviso por un cambio de selección en el backend.

## 4. API que debe implementar el servicio de autorización personalizado

Todas las interfaces siguientes son **recibidas por el servicio de autorización del comerciante y llamadas por el servidor de Launch**. `base_url` puede incluir un prefijo de ruta fijo; las rutas de la tabla se añaden después de ese prefijo.

| Métodos y rutas | Finalidad |
| --- | --- |
| `GET /launch/v1/capabilities` | Comprobación de la conexión y declaración del protocolo y de las capacidades |
| `POST /launch/v1/events` | Recibe el estado objetivo completo de la compra; los reintentos reutilizan el evento original |
| `GET /launch/v1/events/{event_id}` | Consulta si el evento se ha aplicado realmente y recupera las respuestas perdidas |
| `GET /launch/v1/entitlements/{entitlement_ref}/delivery` | Consulta los datos de entrega de una autorización existente, sin crear una segunda autorización |

La primera versión consulta activamente los acuses y no añade puntos de entrada de callbacks externos para modificar pedidos. Los servicios personalizados no tienen permiso para confirmar pagos, modificar precios ni reescribir los plazos de compra.

### 4.1 Convenciones generales y autenticación

- JSON usa UTF-8; las horas usan UTC RFC 3339; `version` usa números enteros positivos.
- Una respuesta correcta es `{"data": {...}, "request_id": "..."}`; una respuesta de error es `{"error": {"code": "...", "message": "..."}, "request_id": "..."}`.
- Cada entorno y conexión usa una clave independiente. La clave debe contener al menos 32 bytes aleatorios y referenciar el almacén de claves cifrado existente en Launch; no debe incluirse en la configuración del frontend, los ejemplos ni los paquetes de instalación de la aplicación.
- Cada solicitud incluye `X-Launch-Key-Id`, `X-Launch-Timestamp`, `X-Launch-Nonce` y `X-Launch-Signature`.
- La marca de tiempo se expresa en segundos Unix decimales. El nonce es la codificación base64url sin relleno de un valor aleatorio de al menos 128 bits; se genera un valor nuevo en cada intento de red.

La entrada de la firma se compone de los siete elementos siguientes codificados en bytes UTF-8, unidos con un único salto de línea y sin salto de línea final:

```text
v1
<key_id>
<timestamp>
<nonce>
<UPPERCASE HTTP method>
<encoded request path and raw query, including the base_url path prefix>
<lowercase hexadecimal SHA-256 digest of the exact request body>
```

`X-Launch-Signature` se compone de `v1=` seguido de la codificación base64url sin relleno del resultado de `HMAC-SHA256`. El cuerpo de las solicitudes GET está vacío. El receptor verifica primero la firma y la ventana temporal y después deserializa; usa una comparación de tiempo constante, admite por defecto una desviación de 300 segundos en ambos sentidos y conserva los nonces durante al menos 600 segundos para impedir ataques de repetición. Los nonces no se reutilizan entre reintentos, pero los ID de evento y el cuerpo de negocio de la solicitud deben permanecer iguales.

Las firmas no sustituyen HTTPS. Las solicitudes no incluyen contraseñas de clientes, claves de pago ni claves administrativas de Licentivo; por defecto solo se envían referencias anónimas de cuentas, sin direcciones de correo. Los clientes HTTP del servidor necesitan tiempos de espera, límites de tamaño de respuesta y la prohibición de reenviar credenciales al seguir redirecciones. Al configurar direcciones personalizadas debe validarse el destino; el acceso a la red interna debe ser una capacidad de despliegue configurada explícitamente por los administradores.

### 4.2 Comprobación de capacidades

`GET /launch/v1/capabilities` devuelve HTTP 200, por ejemplo:

```json
{
  "data": {
    "protocol": "launch-external-licensing-v1",
    "connection_ref": "merchant-01-sandbox",
    "environment": "sandbox",
    "purchase_types": ["perpetual", "fixed_term"],
    "delivery_types": ["license_key", "activation_url", "instructions"],
    "devices": false,
    "identity_exchange": false
  },
  "request_id": "check-001"
}
```

`protocol`, `connection_ref`, `environment`, `purchase_types` y `delivery_types` son obligatorios. Los dos últimos deben ser arrays no vacíos; los tipos de compra no admitidos no pueden publicarse para la venta. En la primera versión, `devices` y `identity_exchange` deben ser `false`; solo puede declararse la compatibilidad después de completar el contrato ampliado y su implementación.

### 4.3 Instantánea de compra

Ejemplo de solicitud para `POST /launch/v1/events`:

```json
{
  "schema": "launch-external-licensing-v1",
  "event_id": "7bf84b91-2749-4b01-a6a7-bd527c488ee8",
  "connection_ref": "merchant-01-sandbox",
  "environment": "sandbox",
  "type": "entitlement.snapshot",
  "change_reason": "purchase",
  "account_ref": "customer_opaque_001",
  "entitlement_ref": "purchase_opaque_001",
  "launch_product_id": "c6dd463e-50f7-4b76-8239-0a8ec6fc304b",
  "launch_plan_version_id": "5459a073-9362-4b46-949d-9b0c5d84421a",
  "product_ref": "desktop-editor",
  "plan_ref": "professional-yearly-v1",
  "version": 1,
  "desired_state": "active",
  "valid_from": "2026-10-25T00:00:00Z",
  "valid_until": "2027-10-25T00:00:00Z"
}
```

Todos los campos del ejemplo anterior son obligatorios. `valid_until` con valor `null` indica una compra permanente; los demás valores deben ser posteriores a `valid_from`.

| Campo | Restricciones y significado |
| --- | --- |
| `event_id` | UUID generado por Launch; no cambia al reintentar el mismo evento |
| `connection_ref` / `environment` | Debe coincidir con la conexión y el entorno a los que pertenece la clave de firma |
| `account_ref` | Referencia anónima estable de la cuenta; el receptor identifica al cliente mediante la combinación de conexión, entorno y esta referencia |
| `entitlement_ref` | Referencia estable de una relación de compra; después de una revocación completa, una nueva compra usa una referencia nueva |
| `launch_product_id` / `launch_plan_version_id` | UUID del producto de Launch y de la versión inmutable del plan |
| `product_ref` / `plan_ref` | Referencia externa configurada por quien realiza la integración; 1–128 caracteres ASCII, con letras, números, `_`, `-` y `.` permitidos; no crea planes externos automáticamente |
| `version` | Versión objetivo que aumenta de forma monótona para la misma conexión, entorno y referencia de compra |
| `desired_state` | `active`, `suspended`, `expired`, `revoked` |
| `change_reason` | `purchase`, `renewal`, `upgrade`, `refund`, `reversal`, `suspension`, `resumption`, `expiry`, `reconciliation` |

`account_ref` y `entitlement_ref` usan de 1 a 128 caracteres entre letras, números, guiones bajos o guiones. `schema` y `type` tienen los valores fijos del ejemplo. El receptor aplica el resultado según `desired_state` y el plazo absoluto completo, sin añadir días ni revocar toda la autorización por su cuenta según el motivo.

En particular, en los reembolsos: después de reembolsar un pedido de renovación o cambio a un plan superior, puede seguir existiendo una compra base válida. En ese caso, la instantánea puede seguir siendo `active`, pero con un plazo más corto o un plan distinto. Solo se envía el estado de terminación correspondiente cuando el recálculo del libro contable confirma que ya no queda cobertura de compra válida.

### 4.4 Recepción, acuses y recuperación

El receptor solo debe devolver HTTP 202 después de guardar de forma persistente el ID de evento, el resumen criptográfico del cuerpo original de la solicitud y la tarea pendiente de ejecución; las solicitudes duplicadas cuya sincronización ya ha terminado pueden devolver HTTP 200. Ambas respuestas incluyen la estructura de acuse siguiente.

```json
{
  "data": {
    "event_id": "7bf84b91-2749-4b01-a6a7-bd527c488ee8",
    "connection_ref": "merchant-01-sandbox",
    "environment": "sandbox",
    "entitlement_ref": "purchase_opaque_001",
    "version": 1,
    "payload_sha256": "<64-character lowercase hexadecimal SHA-256 digest of the exact event body>",
    "status": "pending",
    "error_code": null
  },
  "request_id": "receipt-001"
}
```

El marcador del resumen criptográfico debe sustituirse por el resumen real. Todos los campos del acuse indicados arriba son obligatorios; `error_code` es `null` cuando no hay error. La consulta `GET /launch/v1/events/{event_id}` devuelve la misma estructura con HTTP 200; el estado puede ser:

| `status` | Significado | Procesamiento de Launch |
| --- | --- | --- |
| `pending` | Guardado de forma persistente, pero el cambio real de autorización aún no ha terminado | Consultar más tarde; no se puede marcar la sincronización como completada |
| `applied` | El servicio de autorización confirma que el estado objetivo se ha aplicado realmente | Confirmar la sincronización después de comprobar todos los campos de correlación y el resumen criptográfico |
| `rejected` | Se ha determinado que no se puede aplicar, por ejemplo porque el plan externo no existe | Detener los reintentos automáticos y mostrar un error explícito |
| `manual_review` | Resultado incierto o conflicto de estado; se requiere comprobación manual | Conservar la tarea y el evento originales hasta que se realice una intervención auditable |

El estado de entrega es independiente del acuse de sincronización; `applied` no puede servir directamente como permiso de ejecución de la aplicación del cliente.

Requisitos de fiabilidad:

- La entrega se realiza al menos una vez y el receptor debe garantizar la idempotencia de negocio; no se puede prometer «una única llamada» en la capa de red.
- Para el mismo ID de evento y el mismo cuerpo original de solicitud, devolver el resultado existente; para el mismo ID con un cuerpo distinto, devolver HTTP 409 `IDEMPOTENCY_CONFLICT`.
- Las versiones antiguas no deben sobrescribir las nuevas. No se deben aceptar sin aviso eventos u objetivos distintos para la misma versión de compra; debe devolverse HTTP 409 `STATE_VERSION_CONFLICT` para su comprobación.
- Si se pierde una respuesta POST o se reinicia el proceso, Launch consulta primero el evento original mediante GET; solo vuelve a enviar el ID y el cuerpo originales después de recibir explícitamente `EVENT_NOT_FOUND`.
- Si el resultado real de la emisión remota no está claro, quien realiza la integración también debe comprobarlo con su propia referencia de idempotencia persistente; un reenvío no debe generar otra autorización.
- Para HTTP 429, respetar `Retry-After`; ante tiempos de espera agotados y errores 5xx, recuperar con reintentos y esperas progresivas. Si se supera el límite, pasar a comprobación manual. Los rechazos de autenticación, configuración o negocio no deben considerarse sincronizaciones correctas.

Códigos de error recomendados: `SIGNATURE_INVALID` (401), `SIGNATURE_EXPIRED` (401), `NONCE_REPLAYED` (409), `SCOPE_MISMATCH` (403), `EVENT_NOT_FOUND` (404), `IDEMPOTENCY_CONFLICT` (409), `STATE_VERSION_CONFLICT` (409), `PLAN_UNKNOWN` (422), `PURCHASE_TYPE_UNSUPPORTED` (422), `RATE_LIMITED` (429), `SERVICE_UNAVAILABLE` (503). Los mensajes de error no deben incluir credenciales.

### 4.5 Entrega de la autorización

`GET /launch/v1/entitlements/{entitlement_ref}/delivery` solo puede consultar resultados de entrega existentes en esa conexión y entorno; la consulta no debe interpretarse como una orden de nueva emisión. Ejemplo de HTTP 200:

```json
{
  "data": {
    "connection_ref": "merchant-01-sandbox",
    "environment": "sandbox",
    "entitlement_ref": "purchase_opaque_001",
    "version": 1,
    "status": "ready",
    "delivery": {
      "type": "license_key",
      "value": "EXAMPLE-ONLY-NOT-A-REAL-LICENSE",
      "expires_at": null
    }
  },
  "request_id": "delivery-001"
}
```

`status` es `pending`, `ready` o `unavailable`; si no es ready, `delivery` es `null`. Cuando es ready, `type` debe ser uno de los valores declarados `license_key`, `activation_url` o `instructions`; `value` admite como máximo 4096 bytes UTF-8 y `expires_at` es una hora UTC o `null`. Todos los campos de correlación de la respuesta deben coincidir con la versión de compra actual de Launch; las entregas de versiones antiguas no se pueden considerar resultados actuales.

Los códigos de autorización son datos sensibles; las respuestas de entrega usan `Cache-Control: no-store` y no se registra su cuerpo. Si deben conservarse de forma persistente, se usa almacenamiento cifrado. Los URL de activación solo admiten orígenes HTTPS permitidos por la configuración de la conexión; instructions se muestra como texto sin formato, sin ejecutar HTML ni scripts. Las cuentas con autorización revocada, reembolso total o sin compras válidas no pueden seguir obteniendo datos de entrega mediante Launch; el sistema externo ejecuta la invalidación real de las credenciales ya entregadas.

## API administrativas y de clientes de Launch

Estas interfaces se encuentran bajo el origen de Launch desplegado por el comerciante y no usan la autenticación con clave HMAC del servicio externo. Los administradores usan la sesión existente del sitio y las solicitudes de escritura deben incluir `X-CSRF-Token`. Los clientes solo pueden consultar sus propias compras.

| Métodos y rutas | Finalidad |
| --- | --- |
| `GET /api/v1/admin/licensing/connections` | Devuelve `connections`, `bindings`, `mappings` y `default_provider`; la lista solo contiene conexiones personalizadas, mientras que Licentivo mantiene su interfaz administrativa original |
| `POST /api/v1/admin/licensing/connections` | Verifica y añade una conexión personalizada |
| `PUT /api/v1/admin/licensing/connections/{id}` | Edita el nombre, los orígenes de activación permitidos y la configuración de firma; la dirección histórica de recepción no cambia |
| `POST /api/v1/admin/licensing/connections/{id}/check` | Vuelve a comprobar; un valor distinto de null en `last_check_error` dentro de HTTP 200 indica que la comprobación ha fallado |
| `PUT /api/v1/admin/products/{id}/licensing` | Configura el proveedor, la conexión y las asignaciones de planes externos para el entorno seleccionado del producto |
| `GET /api/v1/me/products/{id}/delivery?environment=sandbox` | El cliente actual consulta la entrega de autorización de su compra personalizada vigente; autenticación mediante Cookie o Bearer de escritorio |
| `GET /api/v1/licensing/documentation` | Este documento, sin necesidad de iniciar sesión |
| `GET /api/v1/licensing/protocol` | OpenAPI que debe implementar el servicio personalizado, sin necesidad de iniciar sesión |

Cuerpo de solicitud para añadir una conexión personalizada:

```json
{
  "provider": "custom_http",
  "name": "My licensing service",
  "base_url": "https://licenses.example.com",
  "environment": "sandbox",
  "connection_ref": "merchant-01-sandbox",
  "key_id": "key-1",
  "signing_key": "<standard Base64 encoding of 32 to 128 random bytes>",
  "allow_private_network": false,
  "allow_local_http": false,
  "activation_origins": ["https://activate.example.com"]
}
```

`data` de la respuesta es el objeto de conexión sin datos sensibles; no devuelve la clave de firma ni el secret ID. allow_local_http solo permite direcciones localhost/de bucle local en sandbox; HTTPS en redes privadas requiere autorización explícita del administrador. Solo se puede dejar la clave vacía al editar una conexión existente. Licentivo se sigue configurando mediante `POST /api/v1/admin/connections/licentivo`.

Cuerpo de solicitud para configurar el producto:

```json
{
  "provider": "custom_http",
  "environment": "sandbox",
  "connection_id": "<id returned when creating the connection>",
  "mappings": [
    {
      "version_id": "<Launch plan version UUID>",
      "product_ref": "desktop-editor",
      "plan_ref": "professional-yearly-v1"
    }
  ]
}
```

Al seleccionar Licentivo, envíe `{"provider":"licentivo","environment":"sandbox","mappings":[]}`; las asignaciones de sus productos y políticas siguen usando la interfaz original. El primer cambio convierte las publicaciones existentes en vistas previas, que deben volver a comprobarse y publicarse para el nuevo proveedor. mappings puede contener únicamente las versiones que se van a añadir o conservar; omitir una versión no elimina la asignación antigua.

La entrega al cliente usa la envoltura data estándar con HTTP 200; si no hay compra, devuelve 404. Los productos Licentivo devuelven 409 `LICENSING_DELIVERY_UNSUPPORTED`; las compras sin sincronizar, suspendidas, vencidas o totalmente reembolsadas devuelven 409 `ENTITLEMENT_NOT_READY`; si el servicio externo no está disponible temporalmente o el acuse no coincide, devuelve 503. «Ver entrega de autorización» en el centro de clientes vuelve a comprobar la titularidad y el estado, sin escribir credenciales en los URL ni en los registros.

Las interfaces identity, device e integration-check de Licentivo para productos personalizados devuelven 409 `CUSTOM_LICENSING_SDK_REQUIRED`. El antiguo `/credential` sigue devolviendo 410. La aplicación debe usar el SDK del comerciante y no considerar el estado `ready` de Launch como autorización de ejecución.

## Ejemplos y pruebas de aceptación

El [ejemplo de adaptador ligero en Python](/es/launch-documents/custom-licensing-example/) ofrece una estructura receptora con firma, nonce, deduplicación de eventos y persistencia de acuses; el procesamiento real de la autorización requiere integrarse con el sistema del comerciante.

El [OpenAPI](/es/assets/launch/external-licensing-openapi.json) del protocolo personalizado y el [OpenAPI](/es/assets/launch/openapi.json) de Launch describen las dos direcciones por separado. El receptor no necesita implementar las interfaces de Licentivo de catálogo de productos, catálogo de políticas o confianza de identidades.

La verificación local usa MySQL aislado, un adaptador HTTP real y servicios simulados de autorización/pago; cubre pérdida de respuestas, reinicios, renovaciones, cambios a planes superiores, reembolsos, desactivación de cuentas, acuses que no coinciden y aislamiento de entregas. La aceptación en producción también requiere el servicio de autorización y la aplicación del comerciante. Launch no aporta las capacidades no declaradas de dispositivos, acceso con cuentas compartidas ni funcionamiento sin conexión.
