REF-401-01: License Activation & Renewal
ADR: ADR-401 — Licensing: License & DownloadEscopo: Fluxo de ativação, WOO_SL adapter, renovação (auto + manual), lembretes escalonados, expiração
1. Fluxo de Ativação
1. Entitlement PLG provisionado (auto ou manual)
└── License criada (chave gerada)
2. Cliente obtém chave
├── Portal: seção "Minhas Licenças"
└── Email: notificação de provisioning inclui chave
3. Cliente ativa em domínio
├── POST /licenses/{id}/activate { domain: "moodle.cliente.com" }
├── Validação:
│ ├── License existe e está ativa?
│ ├── Domínio já ativado nesta license?
│ ├── Max ativações não excedido?
│ └── Domínio é válido (formato URL)?
├── Sucesso → Ativação registrada
└── Falha → Erro específico (LICENSE_EXPIRED, MAX_ACTIVATIONS, DUPLICATE_DOMAIN)
4. Cliente desativa domínio
└── DELETE /licenses/{id}/activate { domain: "moodle.cliente.com" }
└── Ativação removida, slot liberado2. Campos da License
| Campo | Tipo | Descrição |
|---|---|---|
license_key | string | Chave única (gerada ou via WOO_SL) |
entitlement_id | FK | Entitlement PLG pai |
max_activations | int | Máximo de domínios simultâneos |
activations | json[] | Lista de domínios ativados com timestamps |
status | enum | active / expired / revoked |
expires_at | datetime | Data de expiração (segue Entitlement) |
3. WOO_SL Adapter Pattern
| Cenário | Comportamento |
|---|---|
| WOO_SL ativo | LicenseAdapter lê/escreve via WOO_SL API |
| WOO_SL ausente | Gestão built-in: chave simples, ativação por domínio |
| Migração WOO_SL → built-in | Dados preservados, adapter troca transparentemente |
LicenseRepositoryInterface (Domain/)
├── WooSLLicenseRepository (WordPress/) — usa WOO_SL
└── BuiltInLicenseRepository (WordPress/) — gestão simplificadaSeleção automática via DI Container: class_exists('WC_Software_License') determina qual implementação.
4. Renovação
Auto-Renovação (WC Subscriptions ativo)
WC Subscription renova automaticamente
→ woocommerce_subscription_renewal_payment_complete
→ Entitlement PLG: expiration_date estendida
→ License: expires_at atualizada
→ Hook: middag_entitlement_status_changed (se expired → active)Renovação Manual (sem WC Subscriptions)
30 dias antes da expiração:
→ Lembrete ao cliente (email + portal)
→ Link de renovação no portal
Cliente aceita renovação:
→ Quote de renovação gerado (ou compra direta)
→ Pagamento → Order → Entitlement estendido
→ License: expires_at atualizada5. Lembretes Escalonados
| Dias antes | Tipo | Conteúdo |
|---|---|---|
| 30 | Informativo | "Sua licença expira em 30 dias" |
| 15 | Ação recomendada | "Renove para manter acesso" + link |
| 7 | Urgente | "Expira em 7 dias — renove agora" |
| 0 | Expiração | "Sua licença expirou" + link |
Configurável via NotificationPolicy (expiry_warning_days). Se auto-renovação ativa, tom muda para informativo.
6. Expiração → Lock de Acesso
License expira (Entitlement → expired)
├── Ativações existentes: mantidas (não removidas)
├── Novas ativações: bloqueadas (LICENSE_EXPIRED)
├── Validação de license (API): retorna expired
├── Downloads: bloqueados
└── Portal: license visível mas marcada como expiradaReativação: Renovação (pagamento) → Entitlement active → License active → ativações existentes voltam a funcionar.
7. Edge Validation (Cloudflare Workers)
O método LicenseService::checkLicenseValidity(licenseKey, domain) é o endpoint de validação consumido por Cloudflare Workers para autorizar downloads e verificar licenças na edge:
Request: { license_key: "...", domain: "moodle.cliente.com" }
Response: { valid: true/false, reason: "...", product_slug: "...", expires_at: "..." }| Validação | Erro retornado |
|---|---|
| Chave não encontrada | LICENSE_NOT_FOUND |
| Licença expirada | LICENSE_EXPIRED |
| Licença revogada | LICENSE_REVOKED |
| Domínio não ativado | DOMAIN_NOT_ACTIVATED |
| Produto não corresponde | PRODUCT_MISMATCH |
Eventual consistency: Workers podem cachear resultado por até 5 minutos. Revogação/expiração tem delay aceitável.
8. DownloadsService — Access Checks
O DownloadsService implementa verificações de acesso como fallback para downloads diretos via WordPress (cenário legado ou quando CDN indisponível):
| Check | Comportamento se falhar |
|---|---|
| Licença ativa para o produto? | HTTP 403, mensagem: licença expirada/inválida |
| Organização autorizada? | HTTP 403, mensagem: sem permissão |
| Limite de downloads excedido? | HTTP 429, mensagem: limite atingido |
| Produto existe e tem arquivo? | HTTP 404, mensagem: produto não encontrado |
Todos os downloads são logados para auditoria (user ID, timestamp, IP, produto, versão).
9. Tipos de Licença
| Tipo | Max domínios | Produtos disponíveis |
|---|---|---|
| Licença individual | Configurável (ex: 1-5) | 1 plugin específico |
| Licença bundle | Configurável (ex: 5-10) | Conjunto definido de plugins |
| Licença agency | Ilimitado | Todos os plugins do catálogo |
O tipo de licença é determinado pelo produto WC comprado. O mapeamento tipo → produtos disponíveis → max ativações é configurável no admin.
10. LicenseService API
| Método | Descrição |
|---|---|
getLicensesByFilter(filters) | Listagem com filtros (status, produto, domínio, org) |
getLicenseDetails(licenseId) | Detalhes completos da licença |
activateLicense(licenseId, domain) | Ativar licença em domínio (valida max ativações) |
deactivateLicense(licenseId, domain) | Desativar licença de domínio (libera slot) |
checkLicenseValidity(licenseKey, domain) | Validação para Cloudflare Workers (edge) |
getDomainsByLicense(licenseId) | Domínios associados com timestamps de ativação |
renewLicense(licenseId) | Renovação manual de licença |
11. REST API v1 Endpoints
| Método | Endpoint | Descrição |
|---|---|---|
| GET | /middag-account/v1/licenses | Listagem por organização (filtro: status, produto, domínio) |
| GET | /middag-account/v1/licenses/{id} | Detalhes da licença (chave, produto, domínios, datas) |
| POST | /middag-account/v1/licenses/{id}/activate | Ativar licença em domínio |
| POST | /middag-account/v1/licenses/{id}/deactivate | Desativar licença de domínio |
| GET | /middag-account/v1/licenses/{id}/domains | Listar domínios associados |
Todos os endpoints requerem autenticação JWT e contexto de organização.