feat(auth): grant initial trial only after BOTH email verified AND name saved (BUNYIP-221) #250
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
psa-systems/bunyip!250
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/BUNYIP-221-trial-after-onboarding"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Post-BUNYIP-206 the onboarding gate kept users on /onboarding until first + last name were saved AND the email was verified, but the trial / tier grant in AuthService::confirm_email_verification still fired the instant the verify link was clicked, regardless of whether names had been entered. A user could verify, never type a name, and sit on the onboarding page burning trial days, and hostile signups could claim the slot-limited Lifetime / EarlyAdopter tiers without ever completing onboarding.
Move the grant behind both conditions via a new AuthService::maybe_grant_initial_tier(user_id, ip, trigger). The helper re-reads the user, gates on email_verified + non-empty first + non-empty last, idempotent-skips already-granted users (lifetime_member, trial_ends_at populated, or non-default subscription_tier), then runs the existing advisory-locked tier-assign tx with a FOR UPDATE re-read so two concurrent triggers cannot double-grant. Both endpoints call it: confirm_email_verification (after the verify commits) and update_current_user_profile (after a successful name save), so whichever side closes the gate produces the grant. The lifetime $0-subscription side-effect is lifted into a shared maybe_create_lifetime_subscription helper so both call sites stay in sync.
AuditAction gains InitialTierGranted; EmailVerified is logged separately for the verify itself with no tier metadata. confirm_email_verification's return type changes from (Uuid, String, SubscriptionTier) to (Uuid, String, Option); the bunyip-web wrapper already uses unwrap_or_default, so an empty subscription_tier in the response body falls through to a neutral "verified" message on the page.
#BUNYIP-221