R-022 — Troca de e-mail unifica as contas quando o destino já existe

TLDR: Quando o checkout manda trocar o e-mail de uma conta para um endereço que já pertence a outra, as duas contas são unificadas na conta do e-mail novo — relações movidas, assinatura mais longa preservada e a conta antiga encerrada com o e-mail renomeado. Antes disso a troca falhava em silêncio e o usuário ficava com duas contas.

Given / When / Then

Dado uma conta com o e-mail antigo e nenhuma conta com o e-mail novo Quando a task change_user_email processa a troca Então a conta é apenas renomeada para o e-mail novo

Dado uma conta com o e-mail antigo e outra conta já usando o e-mail novo Quando a task change_user_email processa a troca Então todas as relações da conta antiga passam para a conta do e-mail novo, a conta antiga é encerrada com o e-mail renomeado para onion-merge-<hex><epoch>-<e-mail original> e a task retorna merged: True

Dado que a conta antiga e a conta do e-mail novo têm assinaturas diferentes Quando as contas são unificadas Então a conta que sobrevive fica com a maior expires_at das duas, com o subscription_status correspondente, e volta a ficar ativa

Dado que as duas contas têm progresso no mesmo áudio, aula, meditação ou live Quando as contas são unificadas Então sobra um único progresso: o concluído vence o não concluído e, entre dois não concluídos, vence a maior position

Dado que as duas contas têm o mesmo registro único não-progresso (reação, objetivo, envio de e-mail ou jornada) Quando as contas são unificadas Então o registro da conta que sobrevive é mantido e o da conta encerrada é descartado

Dado que a conta unificada termina com assinatura enabled Quando ela tem UserTrial em aberto (converted_at nulo), próprio ou vindo da conta encerrada Então esses trials são marcados como convertidos, sem sobrescrever conversão já registrada

Dado que o e-mail antigo e o e-mail novo são o mesmo endereço Quando a task processa a troca Então nada é alterado e a task retorna reason: "same_account"

Constraints

  • A conta que sobrevive é sempre a do e-mail novo — é a que o usuário acessa
  • A conta encerrada segue a forma do merge de customers do checkout: e-mail prefixado com onion-merge-<hex><epoch>-, nome prefixado com [Desativado] , conta inativa e soft-deletada. O endereço original continua legível no fim do e-mail e fica livre para uso
  • Telefone e documento da conta encerrada são preservados para rastreio; manychat_subscriber_id é limpo para não ficar duplicado entre as duas contas
  • Campos de perfil (name, phone, document, manychat_subscriber_id) da conta encerrada só preenchem campos vazios na conta que sobrevive
  • PushDevices movidos têm o campo email atualizado para o e-mail da conta que sobrevive
  • Conta já soft-deletada não é alvo de merge: o e-mail dela já foi anonimizado
  • Todo o merge roda numa transação — se qualquer passo falhar, nenhuma conta é alterada

Linked test

tests/accounts/test_account_merge.py tests/webhooks/test_email_change_merge_journey.py