Skip to content

REF-202-01: Entitlement Lifecycle

ADR: ADR-202 — Entitlement: Agregador CentralEscopo: Diagrama de estados, transições permitidas, payment recovery flow, upgrade/downgrade, cancellation, win-back, expiração e grace period


1. Diagrama de Estados

                    ┌──────────┐
     Criação ─────► │  active   │
   (auto/manual)    └────┬──┬──┘
                         │  │
          ┌──────────────┘  └──────────────┐
          │                                │
          ▼                                ▼
    ┌───────────┐                    ┌──────────┐
    │ suspended │                    │ expired  │
    └─────┬──┬──┘                    └────┬──┬──┘
          │  │                            │  │
          │  └──► active (reativado)      │  └──► active (renovado)
          │                               │
          ▼                               ▼
    ┌───────────┐                    ┌───────────┐
    │ cancelled │◄───────────────────│ cancelled │
    └─────┬─────┘                    └───────────┘

          └──► active (win-back)

2. Transições Permitidas

DeParaTriggerQuem Pode
activeCriação (auto-provisioning ou manual)Sistema / Admin
activesuspendedFalha de pagamento / hold adminSistema / Admin
activeexpiredData de expiração atingidaCron (auto)
activecancelledCancelamento diretoAdmin
suspendedactivePagamento regularizado / lift adminSistema / Admin
suspendedcancelledTimeout configurável ou decisão adminSistema / Admin
expiredactiveRenovação (novo pagamento)Sistema / Admin
expiredcancelledTimeout configurávelSistema / Admin
cancelledactiveWin-back (reativa mesmo entitlement)Admin (somente)

Transições proibidas: Nenhum estado pode pular para outro fora da tabela acima.


3. Payment Recovery Flow (Falha de Pagamento)

Invoice.payment_failed (Stripe webhook)

├── PaymentRecoveryPolicy consultada
│   ├── Suspensão imediata? → Entitlement → suspended
│   └── Suspensão antecipada? → suspended (clientes com histórico irregular)

├── Stripe continua retry (retry padrão Stripe)
│   └── middag-account espelha cada evento invoice.payment_failed

├── Retry bem-sucedido → Entitlement → active (automático)

└── Falha final (todos retries esgotados)
    └── Entitlement permanece suspended
        └── Período suspended→cancelled: configurável (PaymentRecoveryPolicy)
            ├── Admin pode cancelar manualmente a qualquer momento
            └── Cron cancela automaticamente após timeout

Notificações: Stripe cuida das notificações de cobrança. middag-account não duplica.


4. Upgrade/Downgrade

AspectoRegra
Código do entitlementPreservado (mesmo código, tier muda como atributo)
HistóricoCompleto — todas mudanças de tier registradas
EfeitoImediato ou próximo ciclo (configurável via TierChangePolicy)
DowngradeConfigurável por produto — pode exigir aprovação bidirecional (admin e/ou cliente)
CréditosSeguem ciclo de billing (cobrança no próximo ciclo = créditos no próximo ciclo)
CooldownTempo mínimo entre mudanças de tier (configurável, evita gaming)
Quem executaAdmin manualmente (v5.0). Self-serve no portal futuro.

5. Cancellation & Win-Back

AspectoRegra
Grace period com serviçoNão existe — expired = acesso cortado imediato
Visibilidade pós-expiraçãoConfigurável por produto (CancellationPolicy)
Expired → cancelledTempo configurável com default (CancellationPolicy)
Retenção de dados fiscaisPrazo legal (LGPD/fiscal) — não configurável
Retenção de dados operacionaisPeríodo configurável (CancellationPolicy) — custo de armazenamento considerado
Portal pós-cancelDashboard admin acessível. Produtos/serviços com licenciamento ativo: bloqueados.
Exportação de dadosOferecida no processo de cancelamento. Recuperação posterior como serviço pago.
Win-backReativa mesmo Entitlement — preserva histórico, mesmo código
Quem faz win-backAdmin somente (decisão humana)

6. Expiração Automática (Cron)

Cron diário verifica:
├── Entitlements active com expiration_date < now()
│   └── Transição: active → expired
│       └── Hook: middag_entitlement_status_changed($ent, 'active', 'expired')

└── Entitlements expired com (expired_at + CancellationPolicy.timeout) < now()
    └── Transição: expired → cancelled
        └── Hook: middag_entitlement_status_changed($ent, 'expired', 'cancelled')

7. Hooks de Lifecycle

HookParâmetrosQuando
middag_entitlement_status_changed$entitlement, $old_status, $new_statusQualquer transição
middag_entitlement_suspended$entitlement, $reasonactive → suspended
middag_entitlement_expired$entitlementactive → expired
middag_entitlement_cancelled$entitlement, $reasonQualquer → cancelled
middag_entitlement_reactivated$entitlement, $from_statussuspended/expired/cancelled → active

8. EntitlementEntity — Campos

CampoTipoNotas
entitlement_idintAuto-increment, interno
entitlement_codestringUnique, imutável. Formato: {CLASS}-{YEAR}{MONTH}{SEQ:4d}
entitlement_classenumPLG, ENV, SVC, ORD, AFL, EDU. Imutável.
organization_idintFK para Organization. Obrigatório.
product_namestringNome legível para o usuário
product_descriptiontextNullable
statusenumactive, suspended, expired, cancelled
companyenummiddag_br, middag_global — qual entidade MIDDAG
lifecycleenumongoing, project (SVC). Nullable para outras classes.
typestringSubtipo do serviço (SVC): hosting, support, infrastructure, consulting, development, mobile-apps, migration, upgrade, project-management. Valores do enum category do catálogo.
parent_entitlement_idintFK nullable. Hierarquia parent-child (ver REF-202-03).
materialized_pathstringPath para queries eficientes (ver REF-202-03)
created_atdatetimeAuto
updated_atdatetimeAuto
expires_atdatetimeNullable. Null = perpétuo.
auto_createdboolTrue se criado via auto-provisioning pós-pagamento
quote_idintFK nullable. Vínculo com Quote de origem.
metadataJSONExtensível por classe. Dados específicos da classe.

9. REST API Endpoints

MétodoEndpointDescriçãoScope necessário
GET/middag-account/v1/entitlementsLista entitlements da org. Filtro por classe/status.entitlements
GET/middag-account/v1/entitlements/{id}Detalhe com Orders, Licenses, Contracts vinculadosentitlements
GET/middag-account/v1/entitlements/code/{code}Busca por código (ex: PLG-2026040142)entitlements
POST/middag-account/v1/entitlementsCriação manual (admin only)admin
PUT/middag-account/v1/entitlements/{id}Atualização (admin only)admin
GET/middag-account/v1/entitlements/{id}/ordersPedidos vinculadosentitlements
GET/middag-account/v1/entitlements/{id}/licensesLicenças vinculadasentitlements
GET/middag-account/v1/entitlements/{id}/contractsContratos vinculadosentitlements
GET/middag-account/v1/entitlements/{id}/childrenEntitlements filhosentitlements