# Guia de integração de um sistema de licenciamento personalizado

O código atual oferece dois provedores de licenciamento: Licentivo e `custom_http`. O comerciante seleciona e ativa um deles em **Configurações do sistema → Sistema de licenciamento**. Todos os produtos e ambientes sandbox/live usam o mesmo provedor; as conexões personalizadas e os mapeamentos de planos continuam separados por produto e ambiente. Este recurso não inclui um sistema próprio de licenciamento; o comerciante continua usando seu serviço e SDK existentes.

A troca de contas Licentivo, os SDKs para nove linguagens e a gestão de dispositivos mantêm o fluxo existente. Os produtos personalizados sincronizam o estado de compra por este protocolo; o cliente consulta a entrega da licença na central da conta, sem passar pelo fluxo do SDK Licentivo.

## Início rápido

1. Implemente as quatro interfaces HTTP abaixo no seu serviço, com chaves de assinatura HMAC e identificadores independentes para cada conexão.
2. Em “Sistema de licenciamento”, selecione “Serviço de licenciamento personalizado”, adicione uma conexão e informe endereço, ambiente, referência da conexão, ID da chave e chave em Base64 padrão. Verifique, salve e clique em “Ativar serviço de licenciamento personalizado”.
3. Crie rascunhos de produto e plano comercial. Launch gerencia o tipo e o preço da compra; seu sistema gerencia as regras de licenciamento.
4. Selecione produto, ambiente e conexão, preencha as referências externas de produto e plano e salve. O provedor é definido para todo o site; não é possível ativar Licentivo simultaneamente para outros produtos.
5. Publique uma versão de teste na página do plano, conclua os testes de pagamento, entrega, renovação, upgrade, reembolso e recuperação de falhas e depois configure a produção separadamente.

Endereço, ambiente, referência da conexão e política de acesso à rede não podem ser alterados após a criação; crie outra conexão quando necessário. A chave de assinatura pode ser rotacionada; deixe-a vazia para conservar a atual. Havendo pedidos de outro sistema de licenciamento, conclua a migração antes de trocar o provedor global. Após a troca, os planos anteriores ficam apenas para visualização e precisam ser republicados; credenciais e mapeamentos salvos são preservados. Produtos com pedidos existentes não podem trocar diretamente de conexão, e mapeamentos em venda não podem ser substituídos diretamente; novos preços ou mapeamentos exigem uma nova versão do plano.

Ao configurar pela API, primeiro chame `PUT /api/v1/admin/licensing/provider` com `{"provider":"custom_http"}` ou `{"provider":"licentivo"}` e depois salve o mapeamento do produto. Provedores inativos não podem salvar vínculos de licenciamento de produtos nem mapeamentos de políticas de planos Licentivo.

A verificação da conexão confirma protocolo, autenticação, ambiente e tipos de compra suportados; não cria nem confirma planos externos pelo comerciante. Antes de receber pagamentos reais, conclua a validação sandbox usando planos externos reais.

## 3. Fluxo de integração do comerciante

1. Implemente as quatro interfaces deste documento à frente do sistema de licenciamento existente, preservando sua lógica de licenças.
2. Crie uma conexão de “Serviço de licenciamento personalizado” no Launch e configure endereço HTTPS, referência da conexão, ambiente e chave de assinatura.
3. Launch chama a interface de capacidades para confirmar a versão do protocolo, o suporte a compras permanentes ou temporárias e a forma de entrega.
4. Selecione essa conexão para o produto e mapeie a versão do plano comercial para `product_ref` e `plan_ref` externos. Os códigos externos podem ser strings próprias do comerciante; não precisam ser UUIDs Licentivo.
5. Conclua a validação sandbox de pagamento, renovação, upgrade, reembolso e recuperação de falhas; depois publique a configuração live separadamente.
6. Após verificar o pagamento, Launch salva o pedido, a cobertura comprada e as tarefas pendentes na mesma transação de banco de dados.
7. O servidor envia um instantâneo completo da compra ao serviço personalizado; o serviço persiste o recebimento antes de confirmá-lo.
8. O serviço de licenciamento aplica o estado desejado. Launch consulta o recibo do evento original; somente `applied` confirma a conclusão dessa sincronização.
9. Launch consulta a entrega para clientes autenticados que possuem a compra e mostra o código de licença, o acesso de ativação ou as instruções em texto na central da conta. O aplicativo conecta-se ao sistema de licenciamento do comerciante.
10. Renovação, upgrade, reembolso, reversão, suspensão e expiração geram novas versões do instantâneo completo. O serviço de licenciamento processa o estado desejado mais recente.

As conexões sandbox/live do mesmo produto são vinculadas separadamente. Depois de uma compra, trocar o provedor exige migração independente; pedidos e tarefas antigos não podem acompanhar a seleção administrativa e ser enviados silenciosamente ao novo serviço.

## 4. APIs exigidas do serviço de licenciamento personalizado

As interfaces abaixo são **recebidas pelo serviço de licenciamento do comerciante e chamadas pelo servidor Launch**. `base_url` pode incluir um prefixo de caminho fixo; os caminhos da tabela são acrescentados após esse prefixo.

| Métodos e caminhos | Finalidade |
| --- | --- |
| `GET /launch/v1/capabilities` | Verificação da conexão e declaração de protocolo e capacidades |
| `POST /launch/v1/events` | Receber o estado completo desejado da compra; repetir usando o evento original |
| `GET /launch/v1/events/{event_id}` | Consultar se o evento foi realmente aplicado e recuperar respostas perdidas |
| `GET /launch/v1/entitlements/{entitlement_ref}/delivery` | Ler uma entrega de licença existente sem emitir uma segunda licença |

A primeira versão consulta ativamente os recibos e não adiciona callbacks externos para alterar pedidos. Serviços personalizados não podem confirmar pagamentos, alterar preços nem reescrever os períodos comprados.

### 4.1 Convenções gerais e autenticação

- JSON usa UTF-8; horários usam UTC RFC 3339; `version` usa inteiros positivos.
- A resposta de sucesso é `{"data": {...}, "request_id": "..."}`; a de erro é `{"error": {"code": "...", "message": "..."}, "request_id": "..."}`.
- Cada ambiente e conexão usa uma chave separada. A chave deve conter pelo menos 32 bytes aleatórios e ser referenciada pelo armazenamento criptografado de chaves já existente no Launch; nunca deve entrar na configuração do frontend, nos exemplos ou nos pacotes do aplicativo.
- Cada requisição inclui `X-Launch-Key-Id`, `X-Launch-Timestamp`, `X-Launch-Nonce` e `X-Launch-Signature`.
- O timestamp usa segundos Unix decimais. O nonce é uma codificação base64url sem padding de pelo menos 128 bits aleatórios, com um novo valor a cada tentativa de rede.

A entrada da assinatura contém os sete itens a seguir, codificados como bytes UTF-8 e unidos por uma única quebra de linha, sem quebra ao 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` é formado pelo prefixo `v1=` seguido do resultado de `HMAC-SHA256` codificado em base64url sem padding. O corpo das requisições GET é vazio. O destinatário verifica a assinatura e a janela de tempo antes de desserializar, usa comparação em tempo constante, aceita por padrão desvio de ±300 segundos e conserva os nonces por pelo menos 600 segundos para impedir replay. Não reutilize nonces entre tentativas; os IDs de evento e corpos das requisições de negócio devem permanecer iguais.

Assinaturas não substituem HTTPS. As requisições não levam senhas de clientes, chaves de pagamento nem chaves administrativas Licentivo; por padrão, enviam apenas referências anônimas de conta, sem e-mail. Os clientes HTTP do servidor precisam de timeout, limite de resposta e bloqueio de encaminhamento de credenciais em redirecionamentos. Valide os endereços personalizados; o acesso à intranet deve ser configurado explicitamente pelo administrador.

### 4.2 Verificação de capacidades

`GET /launch/v1/capabilities` retorna HTTP 200, por exemplo:

```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` e `delivery_types` são obrigatórios. Os dois últimos devem ser arrays não vazios; tipos de compra não suportados não podem ser vendidos. Na primeira versão, `devices` e `identity_exchange` devem ser `false`; só declare suporte após concluir o contrato estendido e sua implementação.

### 4.3 Instantâneo da compra

Exemplo de requisição `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 os campos do exemplo acima são obrigatórios. `valid_until` com valor `null` indica compra permanente; qualquer outro valor deve ser posterior a `valid_from`.

| Campo | Restrições e significado |
| --- | --- |
| `event_id` | UUID gerado pelo Launch; permanece igual ao repetir o mesmo evento |
| `connection_ref` / `environment` | Devem corresponder à conexão e ao ambiente da chave de assinatura |
| `account_ref` | Referência anônima estável da conta; o destinatário identifica o cliente pela combinação de conexão, ambiente e referência |
| `entitlement_ref` | Referência estável de uma relação de compra; após revogação integral, uma recompra usa nova referência |
| `launch_product_id` / `launch_plan_version_id` | UUIDs do produto Launch e da versão imutável do plano |
| `product_ref` / `plan_ref` | Referência externa configurada pelo integrador; 1–128 caracteres ASCII, aceitando letras, números, `_`, `-` e `.`; planos externos não são criados automaticamente |
| `version` | Versão desejada que aumenta monotonicamente na mesma conexão, ambiente e referência de compra |
| `desired_state` | `active`, `suspended`, `expired`, `revoked` |
| `change_reason` | `purchase`, `renewal`, `upgrade`, `refund`, `reversal`, `suspension`, `resumption`, `expiry`, `reconciliation` |

`account_ref` e `entitlement_ref` usam 1–128 letras, números, sublinhados ou hífens. `schema` e `type` são fixados nos valores do exemplo. O destinatário aplica `desired_state` e o período absoluto completo, sem acrescentar dias nem revogar toda a autorização por conta própria.

Nos reembolsos, em especial, devolver um pedido de renovação ou upgrade pode preservar uma compra-base válida. Nesse caso, o instantâneo pode continuar `active`, com prazo menor ou outro plano. O estado de encerramento só é enviado após recalcular o histórico e confirmar que nenhuma compra válida mantém a cobertura.

### 4.4 Recebimento, confirmação e recuperação

O destinatário só deve retornar HTTP 202 depois de persistir o ID do evento, o resumo do corpo original e a tarefa a executar. Repetições já sincronizadas podem retornar HTTP 200. Ambos retornam a estrutura de recibo a seguir.

```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"
}
```

Substitua o marcador do resumo pelo resumo real. Todos os campos do recibo acima são obrigatórios; `error_code` é `null` quando não há erro. A consulta `GET /launch/v1/events/{event_id}` retorna a mesma estrutura com HTTP 200, e o estado pode ser:

| `status` | Significado | Tratamento pelo Launch |
| --- | --- | --- |
| `pending` | Persistido, mas a alteração real de autorização ainda não terminou | Consultar mais tarde; não marcar como sincronizado |
| `applied` | O serviço de licenciamento confirma que o estado desejado foi realmente aplicado | Confirmar a sincronização após conferir todos os campos relacionados e o resumo |
| `rejected` | Não pode ser aplicado, por exemplo porque o plano externo não existe | Interromper tentativas automáticas e mostrar um erro claro |
| `manual_review` | Resultado incerto ou conflito de estado; exige verificação manual | Conservar tarefa e evento originais, aguardando tratamento auditável |

O estado da entrega é independente do recibo de sincronização; `applied` não pode autorizar diretamente a execução no aplicativo cliente.

Requisitos de confiabilidade:

- Entrega pelo menos uma vez; o destinatário deve garantir idempotência de negócio. Não se pode prometer “uma única chamada” na camada de rede.
- Mesmo ID de evento e mesmo corpo original retornam o resultado existente; mesmo ID com corpo diferente retorna HTTP 409 `IDEMPOTENCY_CONFLICT`.
- Versões antigas não podem substituir versões novas. Eventos ou estados diferentes para a mesma versão de compra não devem ser aceitos silenciosamente; retorne HTTP 409 `STATE_VERSION_CONFLICT` para conferência.
- Se a resposta POST se perder ou o processo reiniciar, Launch consulta primeiro o evento original por GET; só reenvia o ID e corpo originais após um `EVENT_NOT_FOUND` explícito.
- Se a entrega real remota for incerta, o integrador também deve conferir sua própria referência idempotente persistida; reenviar não pode simplesmente emitir outra licença.
- HTTP 429 respeita `Retry-After`; timeouts e erros 5xx usam espera progressiva. Ao ultrapassar o limite, exija verificação manual. Recusas de autenticação, configuração ou negócio não podem ser tratadas como sincronização bem-sucedida.

Códigos de erro sugeridos: `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) e `SERVICE_UNAVAILABLE` (503). As mensagens não devem conter credenciais.

### 4.5 Entrega da licença

`GET /launch/v1/entitlements/{entitlement_ref}/delivery` apenas lê uma entrega existente na conexão e ambiente atuais; uma consulta não deve emitir outra licença. Exemplo 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` é `pending`, `ready` ou `unavailable`; sem disponibilidade, `delivery` é `null`. Quando disponível, `type` deve ser um tipo declarado: `license_key`, `activation_url` ou `instructions`; `value` aceita até 4096 bytes UTF-8 e `expires_at` é um horário UTC ou `null`. Todos os campos relacionados devem corresponder à versão de compra atual no Launch; uma entrega de versão antiga não pode ser tratada como resultado atual.

Códigos de licença são dados sensíveis. Respostas de entrega usam `Cache-Control: no-store`, não registram o corpo em logs e devem ser criptografadas se persistidas. URLs de ativação aceitam apenas origens HTTPS permitidas pela conexão; instruções são exibidas como texto simples, sem executar HTML ou scripts. Contas revogadas, integralmente reembolsadas ou sem compra válida não podem continuar obtendo a entrega pelo Launch; sistemas externos controlam a invalidação efetiva das credenciais entregues.

## API administrativa e do cliente Launch

Estas interfaces ficam na origem Launch implantada pelo comerciante e não usam autenticação HMAC do serviço externo. Administradores usam as sessões existentes do site; requisições de escrita incluem `X-CSRF-Token`. Clientes só podem ler suas próprias compras.

| Métodos e caminhos | Finalidade |
| --- | --- |
| `GET /api/v1/admin/licensing/connections` | Retorna `connections`, `bindings`, `mappings` e `default_provider`; a lista contém apenas conexões personalizadas, preservando a interface administrativa original do Licentivo |
| `POST /api/v1/admin/licensing/connections` | Verificar e adicionar conexões personalizadas |
| `PUT /api/v1/admin/licensing/connections/{id}` | Editar nome, origens de ativação permitidas e configuração de assinatura; o endereço de recebimento existente permanece igual |
| `POST /api/v1/admin/licensing/connections/{id}/check` | Verificar novamente; `last_check_error` não nulo em HTTP 200 indica falha na verificação |
| `PUT /api/v1/admin/products/{id}/licensing` | Configurar o provedor do produto por ambiente, associando a conexão e os planos externos |
| `GET /api/v1/me/products/{id}/delivery?environment=sandbox` | O cliente atual lê a entrega personalizada da compra atualmente válida; autenticação por Cookie ou bearer de desktop |
| `GET /api/v1/licensing/documentation` | Este documento não exige login |
| `GET /api/v1/licensing/protocol` | O OpenAPI exigido pelos serviços personalizados não exige login |

Corpo para adicionar uma conexão 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` contém a conexão com os dados sensíveis removidos; não retorna a chave de assinatura nem o ID do segredo. allow_local_http só permite localhost ou loopback no sandbox; HTTPS privado exige permissão administrativa explícita. Uma chave vazia só é permitida ao editar uma conexão existente. Licentivo continua sendo configurado por `POST /api/v1/admin/connections/licentivo`.

Corpo para configurar o produto:

```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"
    }
  ]
}
```

Ao selecionar Licentivo, envie `{"provider":"licentivo","environment":"sandbox","mappings":[]}`; o mapeamento de produto e política mantém a interface original. A primeira troca converte a publicação existente em prévia, exigindo nova verificação e publicação pelo novo provedor. Os mapeamentos só podem incluir versões a adicionar ou conservar; omiti-los não apaga mapeamentos anteriores.

A entrega do cliente retorna o envelope data padrão com HTTP 200. Sem compra, retorna 404; produtos Licentivo retornam 409 `LICENSING_DELIVERY_UNSUPPORTED`; compra não sincronizada, suspensa, expirada ou integralmente reembolsada retorna 409 `ENTITLEMENT_NOT_READY`; indisponibilidade externa ou recibo divergente retorna 503. “Ver entrega da licença” na central do cliente verifica novamente a titularidade e o estado, sem incluir credenciais em URLs ou logs.

As interfaces Licentivo identity, device e integration-check de um produto personalizado retornam 409 `CUSTOM_LICENSING_SDK_REQUIRED`. A antiga `/credential` continua retornando 410. O aplicativo deve usar o SDK do comerciante, sem considerar o estado `ready` do Launch uma autorização de execução.

## Exemplos e validação

O [exemplo de adaptador leve em Python](/pt/launch-documents/custom-licensing-example/) fornece uma estrutura de recebimento com assinatura, nonce, deduplicação de eventos e persistência de recibos; a autorização real precisa ser integrada ao sistema do comerciante.

O [OpenAPI](/pt/assets/launch/external-licensing-openapi.json) do protocolo personalizado e o [OpenAPI](/pt/assets/launch/openapi.json) do próprio Launch descrevem as duas direções separadamente. O destinatário não precisa implementar catálogos de produtos ou políticas nem interfaces de confiança de identidade do Licentivo.

A verificação local usa MySQL isolado, o adaptador HTTP real e serviços simulados de licenciamento e pagamento. Cobre perda de resposta, reinício, renovação, upgrade, reembolso, desativação de conta, recibos divergentes e isolamento da entrega. A validação de produção também exige o serviço de licenciamento e o aplicativo do comerciante. Launch não acrescenta registro de dispositivos, login compartilhado de conta nem capacidades offline.
