R-001 — Resposta do apolo_membership é composta por duas filiações

TLDR: GET /api/v1/apolo_membership monta a resposta a partir de duas filiações: os campos de situação (apolo_access_status, card_status, card_picture) vêm da filiação vigente elegível, e a data final (valid_until/valid_until_timestamp) vem sempre da filiação paga mais distante. Quando nenhuma filiação vigente passa no gate de elegibilidade, a resposta cai na filiação paga mais recente — o comportamento anterior.

Given / When / Then

Dado um usuário com mais de uma Membership paga (histórico de renovação, possivelmente com renovação antecipada ainda no futuro) Quando GET /api/v1/apolo_membership?email=... é chamado Então os campos de situação vêm da filiação vigente (valid_until >= hoje) mais recente que passa em Membership#apolo_access_permitted?, e valid_until/valid_until_timestamp vêm da filiação paga de maior valid_until — independente de qual filiação forneceu a situação

Tabela de decisão

Cenário Base dos campos de situação apolo_access_status valid_until_timestamp
Vigente aprovada + renovação antecipada pendente vigente true da renovação (mais distante)
Vigente pendente + renovação aprovada renovação true da renovação
Nenhuma filiação vigente passa no gate fallback: paga mais recente false da paga mais recente
Todas as filiações vencidas fallback: paga mais recente false da paga mais recente
Nenhuma filiação paga — 404 404

Restrições

  • A data final nunca anda para trás: valid_until/valid_until_timestamp sempre saem da filiação paga de maior valid_until. Essa garantia existe porque o trg-club-api decide elegibilidade PRO lendo apenas valid_until_timestamp — uma data mais próxima rebaixaria a assinatura de quem está pago.
  • O fallback para a filiação paga mais recente é obrigatório: sem ele o endpoint devolveria 404 quando nenhuma candidata passa no gate, e 404 faz o trg-club-api rebaixar a assinatura.
  • Membership#apolo_access_permitted? continua sendo a única fonte da verdade do gate — a seleção o consome, não o reimplementa em SQL.
  • A mudança é estritamente aditiva: nenhum usuário sai de apolo_access_status: true para false. Validado em produção sobre 18.262 usuários com filiação paga (192 corrigidos, 0 regressões).
  • Usuário sem nenhuma filiação paga continua recebendo 404.
  • A vigência é avaliada pelo scope existente valid_gte_today, que inclui a filiação vencendo hoje: o literal de tempo vai sem tipo explícito, o Postgres resolve o operador como date >= date e trunca a hora. Nenhum scope novo de vigência foi criado por causa disso.
  • A revogação automática é preservada: quando a filiação vigente vencer e a renovação continuar sem aprovação, o acesso volta a ser negado sem intervenção.
  • profile.status/profile_status continuam vindo de user_profile.documentation_status (user-level) — não são recompostos por esta regra. Atenção: desde #700 do trgclub-api (04/08/2026) esse campo é gate de PRO — Subscriptions::Pro::ValidateEligibility#documentation_approved? exige == "ok" e ExpireWhenIneligible desativa a assinatura quando falha. Não é mais um campo de display. Ver R-006.

Código

app/controllers/api/v1/apolo_membership_controller.rb

```ruby def membership @membership ||= valid_paid_memberships.detect(&:apolo_access_permitted?) || newest_paid_membership end

def apolo_payload membership.public_serialize.merge( valid_until: newest_paid_membership.valid_until.strftime(“%m/%Y”), valid_until_timestamp: newest_paid_membership.valid_until ) end ```

Teste vinculado

test/controllers/api/v1/apolo_membership_controller_test.rb — seleção da vigente elegível, guarda dos casos “vigente pendente + renovação aprovada”, override das datas, borda de vigência e os três fallbacks test/models/membership_test.rb — valid_gte_today inclui filiação vencendo hoje; public_serialize expõe valid_until/valid_until_timestamp mesmo com apolo_access_permitted? falso

Histórico

  • 2026-07-17: seleção trocada de admin_approved para paid e valid_until/valid_until_timestamp deixaram de ser condicionados a apolo_access_permitted? — quem renovou e ainda não foi reaprovado tinha o PRO rebaixado no trg-club-api. Ver spec.
  • 2026-07-29: seleção passou a compor a resposta a partir de duas filiações — a troca para paid fez a renovação antecipada (futura, ainda não aprovada) ser sempre escolhida, bloqueando o acesso de 192 membros no Apolo. Ver spec.

Relacionados