Cadastro de Usuário Trial

TLDR: endpoint público POST /v1/trials/register que valida a campanha, protege contra abuso de trial e conta existente, cria o usuário e o vínculo de trial, e dispara e-mails transacionais.

Status: proposed Created: 2026-08-11 Owner: @prattiz


Context

O marketing usa campanhas (model Campaign) com links de rastreamento para atrair novos usuários. O fluxo de trial precisa de um endpoint público que receba email, phone e slug de campanha, valide elegibilidade e crie o usuário já vinculado ao trial da campanha — sem exigir autenticação prévia.

Objectives

  • Criar endpoint POST /v1/trials/register (público, AllowAny).
  • Validar que a campanha existe e está active.
  • Bloquear e notificar quando o email ou phone já existem como conta padrão.
  • Bloquear e notificar quando o email ou phone já foram usados em algum trial anterior.
  • Criar o usuário com senha aleatória segura.
  • Criar o UserTrial vinculado à campanha e ao trial da campanha.
  • Enviar e-mail de boas-vindas com credenciais ao novo usuário.

Non-goals

  • Criar o Trial ou a Campaign via endpoint — são dados administrativos.
  • Retornar tokens JWT na resposta — o usuário fará login normalmente depois.
  • Validar se o Trial associado à campanha está ativo — essa garantia é responsabilidade do administrador ao configurar a campanha.
  • Renovação de trial (allow_renewal) — fora de escopo.

Changes

apps/trials/services.py (novo)

Módulo de serviço com a função register_user_trial(email, phone, slug):

  1. Busca Campaign pelo slug com status=active; lança ValidationError se não encontrada.
  2. Verifica User.objects.filter(Q(email=email) | Q(phone=phone)):
    • Se existir → dispara Celery task send_trial_rejected_existing_account e lança ValidationError.
  3. Verifica UserTrial.objects.filter(user__email=email) | UserTrial.objects.filter(user__phone=phone) — join via all_objects para incluir soft-deleted:
    • Se existir → dispara Celery task send_trial_rejected_already_used e lança ValidationError.
  4. Gera senha aleatória segura (secrets.token_urlsafe(12)).
  5. Cria o User (email, phone, name="") com user.set_password(password).
  6. Calcula started_at = now(), expires_at = started_at + timedelta(days=trial.duration_days).
  7. Cria UserTrial(user, trial, campaign, started_at, expires_at, status=ACTIVE).
  8. Dispara Celery task send_trial_welcome com email e senha gerada.
  9. Retorna o UserTrial criado.

Rastreio de origem: satisfeito pelo UserTrial.campaign FK — não é necessário campo adicional em User.

apps/trials/serializer.py (atualizar)

Adicionar UserTrialRegisterSerializer(serializers.Serializer): - email — EmailField - phone — CharField - slug — CharField - validate() (ou create()) delega para register_user_trial.

apps/trials/views.py (atualizar)

Adicionar UserTrialRegisterView(APIView): - permission_classes = [AllowAny] - authentication_classes = [] - POST: valida serializer, chama service, retorna 201.

apps/trials/urls.py (novo)

python urlpatterns = [ path("register", UserTrialRegisterView.as_view(), name="trial-register"), ]

routes/api.py (atualizar)

Adicionar dentro do bloco v1/: python re_path(r"^trials\/?", include(("apps.trials.urls", "trials"), namespace="trials")),

apps/trials/tasks.py (novo)

Três tasks Celery (queue default):

  • send_trial_welcome(email, name, password) — chama SendEmails.trial_welcome.
  • send_trial_rejected_existing_account(email) — chama SendEmails.trial_rejected_existing_account.
  • send_trial_rejected_already_used(email) — chama SendEmails.trial_rejected_already_used.

apps/emails/send_emails.py (atualizar)

Adicionar três métodos estáticos em SendEmails:

  • trial_welcome(data) — template emails/trial_welcome.html; subject: "Seu acesso trial está liberado - Onion".
  • trial_rejected_existing_account(data) — template emails/trial_rejected_existing_account.html; subject: "Não foi possível criar seu trial - Onion".
  • trial_rejected_already_used(data) — template emails/trial_rejected_already_used.html; subject: "Não foi possível criar seu trial - Onion".

Templates de e-mail (3 novos)

Criar em templates/emails/ seguindo o padrão dos existentes: - trial_welcome.html — boas-vindas, credenciais (email + senha), links de download. - trial_rejected_existing_account.html — informa que já existe conta ativa. - trial_rejected_already_used.html — informa que trial já foi utilizado anteriormente.

How to verify

  1. POST /v1/trials/register com slug de campanha inexistente → 400 com mensagem de erro.
  2. POST /v1/trials/register com email de usuário existente → 400 + e-mail trial_rejected_existing_account disparado.
  3. POST /v1/trials/register com email de usuário que já teve trial → 400 + e-mail trial_rejected_already_used disparado.
  4. POST /v1/trials/register com dados válidos → 201, User criado, UserTrial criado com status=active, campaign apontando para a campanha correta, e-mail trial_welcome disparado.
  5. python manage.py check passa sem erros.

Documentation

Criar .project/docs/rules/trials/user_trial_register.md com as regras de negócio de elegibilidade (conta existente bloqueia, trial anterior bloqueia, campanha inativa bloqueia).