R-002 — Renovação encadeia a partir de qualquer vigência em andamento não estornada

TLDR: Ao calcular valid_since/valid_until de uma nova Membership, WebhookMembershipService encadeia a partir de qualquer filiação do usuário com vigência em andamento (valid_until >= hoje) — independente do payment_status (paid, pending, overdue) e independente de aprovação de admin — exceto filiações refunded, que nunca contam como vigência ativa.

Given / When / Then

Dado um usuário com uma Membership de valid_until no futuro (vigência em andamento) e payment_status diferente de refunded, aprovada ou não por admin Quando uma nova Membership é criada (webhook do gateway confirma uma nova compra com status: "paid") Então a nova filiação nasce com valid_since igual ao valid_until da filiação vigente anterior e valid_until um ano depois — encadeando o período em vez de sobrepor

Tabela de decisão

payment_status da filiação vigente anterior Conta como vigência em andamento?
paid Sim
pending Sim
overdue Sim
refunded Não
nenhuma filiação com valid_until >= hoje Não — valid_since começa em Date.current

Restrições

  • refunded nunca conta como vigência em andamento, mesmo com valid_until no futuro.
  • Filiação vencida (valid_until no passado) nunca conta, independente do payment_status.
  • Aprovação de admin (admin_approved) não entra no filtro.
  • Sem nenhuma filiação com vigência em andamento, a nova filiação começa em Date.current e termina no fim do mês corrente um ano depois.
  • Não existe scope novo em Membership para isso — o filtro é inline na query, padrão adotado desde o primeiro fix deste método.

Código

app/services/webhook_membership_service.rb — set_valid_since_and_until

ruby membership_eligible_for_renewal = @user.memberships .where.not(payment_status: :refunded) .valid_gte_today .order_by_newest_valid_until .try(:first)

Teste vinculado

test/services/webhook_membership_service_test.rb: - “sets valid_since based on last paid membership even when not admin approved” - “sets valid_since based on last paid membership when it is admin approved” - “sets valid_since based on existing membership when it is pending payment” - “sets valid_since based on existing membership when it is overdue payment” - “sets valid_since from today when existing membership is refunded”

Histórico

  • 2026-07-17: removida .admin_approved do filtro — filiação paga mas ainda não aprovada por admin era ignorada.
  • 2026-07-30: trocado .paid por .where.not(payment_status: :refunded) — filiação pending/overdue ainda vigente era ignorada, sobrepondo períodos quando o checkout de campanha liberava compra com pendência financeira. Ver spec.

Relacionados