Skip to content

Fix password reset and rework checkout-driven account creation#371

Merged
simonhamp merged 1 commit intomainfrom
password-reset-debug
Apr 29, 2026
Merged

Fix password reset and rework checkout-driven account creation#371
simonhamp merged 1 commit intomainfrom
password-reset-debug

Conversation

@simonhamp
Copy link
Copy Markdown
Member

Summary

Fixes a class of bug where users created via the Ultra checkout flow (e.g. alex.ganderton@arrangemy.com) end up stranded — no verification email, no license email, and password reset silently fails — because SuppressMailNotificationListener was dropping any mail to users who were unverified or opted-out of notification emails, with only VerifyEmail carved out.

What changed

Listener / transactional notifications

  • New App\Contracts\TransactionalNotification marker interface
  • SuppressMailNotificationListener now bypasses anything implementing the marker, plus framework VerifyEmail and ResetPassword
  • Tagged LicenseKeyGenerated, BundleGranted, PluginGranted, ProductGranted as transactional

Account claim flow

  • New ClaimAccount notification — uses the password broker token so it routes through the existing reset-password UI; one email verifies their address and sets a password
  • CustomerAuthController::resetPassword now sets email_verified_at = now() when null on a successful reset, so the same flow serves both real password resets and first-time account claims
  • MobilePricing::findOrCreateUser and Api\LicenseController::store dispatch ClaimAccount when the user is newly created
  • ClaimDonationLicense::claim now fires Registered (user already chose their own password there, so just needs verification)

Order success page

  • Dropped license-key copy and Anystack registration callout (we no longer generate licenses)
  • For Ultra: existing/verified users see a "Go to Dashboard" button; newly-created users see a "we've sent a link to {email}, contact support@nativephp.com if you haven't received it" message
  • Polling key switched from licenseKey to subscription

Test plan

  • php artisan test --compact tests/Feature/SuppressMailNotificationListenerTest.php tests/Feature/CustomerAuthenticationTest.php tests/Feature/MobilePricingTest.php tests/Feature/Livewire/OrderSuccessTest.php tests/Feature/ClaimDonationLicenseTest.php — 67 passing
  • vendor/bin/pint --dirty --format agent — clean
  • Manual: verify the order success page renders the "claim your account" copy for a user with email_verified_at = null, and "Go to Dashboard" for a verified user
  • Manual: trigger password reset for an opted-out user and confirm the email arrives in Mailgun
  • Follow-up (separate PR): backfill / re-send ClaimAccount to the population of stranded users created via MobilePricing who never received any email

🤖 Generated with Claude Code

…count creation

Password reset emails (and other transactional mail) were being silently dropped
for users with email_verified_at = null or receives_notification_emails = false,
because SuppressMailNotificationListener only carved out VerifyEmail.

Customers who purchased Ultra via the pricing flow were created with a random
password and never sent a verification email (no Registered event fired), so
they had no way into their account: the post-purchase license email was
suppressed, and password reset was suppressed too.

Changes:
- Introduce App\Contracts\TransactionalNotification marker so transactional
  mail (account recovery, entitlements, receipts) bypasses the listener.
- Tag LicenseKeyGenerated, BundleGranted, PluginGranted, ProductGranted.
- Carve out framework ResetPassword + VerifyEmail explicitly in the listener.
- Add ClaimAccount notification (uses the password broker token + existing
  reset-password UI) so checkout-created users get one email that verifies
  email and lets them set a password.
- Dispatch ClaimAccount from MobilePricing and Api\LicenseController when
  wasRecentlyCreated. Fire Registered from ClaimDonationLicense (user
  already chose their own password there).
- On password reset, set email_verified_at = now() when null so the same
  flow serves both real resets and account claims.
- Rework OrderSuccess: drop license-key copy entirely. For Ultra, show
  "Go to Dashboard" for verified users, or a "check your inbox / contact
  support@nativephp.com" message for newly-created users.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@simonhamp simonhamp marked this pull request as ready for review April 29, 2026 22:07
@simonhamp simonhamp merged commit 1463a60 into main Apr 29, 2026
2 checks passed
@simonhamp simonhamp deleted the password-reset-debug branch April 29, 2026 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant