Fix: apolo_membership nega acesso a quem renovou mas ainda não foi reaprovado
TLDR:
Api::V1::ApoloMembershipControllerpassa a considerar a filiação paga mais recente (não só a já aprovada) eMembership#public_serializepassa a exporvalid_until/valid_until_timestampreais mesmo sem aprovação própria, evitando que otrg-club-apirebaixe quem renovou e está pago mas ainda não foi reaprovado manualmente.
Nota: esta spec foi superada em parte pela correção de renovação antecipada. A troca para
.paiddescrita aqui fez a filiação futura ser sempre a escolhida. A regra vigente está em R-001.
Contexto
Toda renovação (WebhookMembershipService#initialize_new_membership) cria uma nova Membership com o mesmo register_number, e essa nova linha nasce sem admin_approved_at/admin_approved_by_id a menos que user.issued_definitive_card == true — flag que só existe para quem foi emitido/aprovado depois do DATE_RELEASE_NEW_CITRG. Não há backfill dessa flag para quem já tinha carteira emitida/aprovada antes desse mecanismo existir.
Caso real (register_number 15820):
| Filiação | payment_status |
valid_until |
card_status |
admin_approved_* |
|---|---|---|---|---|
| antiga (id 26614) | — | 2026-05-31 (expirada) | issued |
presentes |
| nova (id 35412) | paid |
2027-06-30 (válida) | not_issued |
em branco |
O endpoint GET /api/v1/apolo_membership selecionava apenas entre filiações aprovadas (@user.memberships.admin_approved.order_by_newest_valid_until.try(:first)), então pegava a 26614 (expirada) e ignorava a 35412 (paga e válida). Além disso, valid_until/valid_until_timestamp em public_serialize só eram preenchidos quando apolo_access_permitted? era true — senão vinham null.
O trg-club-api (Subscriptions::Pro::ValidateEligibility) só lê membership.dig(:valid_until_timestamp) para decidir elegibilidade PRO — nunca olha apolo_access_status. Com valid_until_timestamp: null, entendia que não havia filiação ativa e rebaixava a assinatura PRO da pessoa, mesmo com pagamento em dia e aprovação anterior. O gap era puramente de processo (falta de reaprovação manual pós-renovação para quem não tem issued_definitive_card), não inadimplência.
Decisão explícita: apolo_access_permitted? (e portanto apolo_access_status) não é alterado — continua exigindo aprovação própria ou carteira emitida, sem considerar histórico de aprovação de filiações anteriores. Esse booleano segue sendo o gate de compliance do CITRG para os outros consumidores do endpoint. O fix resolve o caso por duas mudanças mais cirúrgicas: qual filiação é selecionada, e quais campos do serializer dependem de apolo_access_permitted?.
Objetivos
- O endpoint
apolo_membershippassa a considerar a filiação paga mais recente (porvalid_until) como candidata, não só a já aprovada — preservando o404para quem nunca teve nenhuma filiação paga. valid_untilevalid_until_timestampnopublic_serializesempre refletem a data real da filiação selecionada, independente deapolo_access_permitted?.apolo_access_statuscontinua computado porapolo_access_permitted?sem mudanças.
Fora de escopo
- Mudanças em
WebhookMembershipService/issued_definitive_card— commits recentes namain(#201/#198/#196/#194) já corrigiram os status exibidos no dashboard/admin para quem tem a flag, mas não tocam no endpoint público nem resolvem quem nunca teve a flag setada (membros legados). - Mascarar suspensão ou pagamento pendente —
payment_status/suspendedjá vêm corretos no serializer.
Mudanças
app/controllers/api/v1/apolo_membership_controller.rb
show: troca a seleção de @user.memberships.admin_approved.order_by_newest_valid_until.try(:first) para @user.memberships.paid.order_by_newest_valid_until.try(:first) — como payment_status não muda com o tempo, isso continua pegando a mais recente entre as pagas (aprovada ou não), preservando o 404 para quem nunca pagou.
app/models/membership.rb
public_serialize: valid_until/valid_until_timestamp deixam de ser condicionados a apolo_access_permitted?.
diff
apolo_access_status: apolo_access_permitted?,
payment_status:,
register_number: register_number_pad,
- valid_until: apolo_access_permitted? ? valid_until.strftime("%m/%Y") : nil,
+ valid_until: valid_until.strftime("%m/%Y"),
card_status:,
- valid_until_timestamp: apolo_access_permitted? ? valid_until : nil,
+ valid_until_timestamp: valid_until,
Plano de implementação
- test:
Membership#public_serialize—valid_until/valid_until_timestamppreenchidos mesmo quandoapolo_access_permitted?éfalse; regressão do caso já aprovado;apolo_access_statussem mudança de comportamento. Arquivo:test/models/membership_test.rb - test:
Api::V1::ApoloMembershipController#show— retorna a filiação paga mais recente mesmo sem aprovação própria; mantém404para usuário sem nenhuma filiação paga; regressão do caso já aprovado. Arquivo:test/controllers/api/v1/apolo_membership_controller_test.rb - fix: trocar o scope de seleção no controller (
paidem vez deadmin_approved). - fix: consolidar a mudança em
public_serialize.
Como verificar
bash
make test test=test/models/membership_test.rb
make test test=test/controllers/api/v1/apolo_membership_controller_test.rb
Manual (console de produção, somente leitura):
ruby
user = User.find_by(email: "...")
membership = user.memberships.paid.order_by_newest_valid_until.first
membership.id # deve ser 35412, não 26614
membership.public_serialize[:valid_until_timestamp] # deve ser "2027-06-30", não null
membership.apolo_access_permitted? # continua false — o gate não muda