diff --git a/app/Contracts/TransactionalNotification.php b/app/Contracts/TransactionalNotification.php
new file mode 100644
index 00000000..556be31c
--- /dev/null
+++ b/app/Contracts/TransactionalNotification.php
@@ -0,0 +1,16 @@
+wasRecentlyCreated) {
+ $user->notify(new ClaimAccount(
+ Password::broker()->createToken($user)
+ ));
+ }
+
// Create the license via job
$subscription = Subscription::from($validated['subscription']);
diff --git a/app/Http/Controllers/Auth/CustomerAuthController.php b/app/Http/Controllers/Auth/CustomerAuthController.php
index 8f933b20..8b77bfe9 100644
--- a/app/Http/Controllers/Auth/CustomerAuthController.php
+++ b/app/Http/Controllers/Auth/CustomerAuthController.php
@@ -156,9 +156,17 @@ public function resetPassword(Request $request): RedirectResponse
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function ($user, $password): void {
- $user->forceFill([
- 'password' => $password,
- ]);
+ $attributes = ['password' => $password];
+
+ // Proving control of the inbox + setting a password is sufficient
+ // to consider the email verified. This also lets the same flow
+ // serve as the "claim your account" path for users created via
+ // checkout.
+ if (! $user->email_verified_at) {
+ $attributes['email_verified_at'] = now();
+ }
+
+ $user->forceFill($attributes);
$user->save();
}
diff --git a/app/Listeners/SuppressMailNotificationListener.php b/app/Listeners/SuppressMailNotificationListener.php
index a78a54dd..9b7c5cce 100644
--- a/app/Listeners/SuppressMailNotificationListener.php
+++ b/app/Listeners/SuppressMailNotificationListener.php
@@ -2,7 +2,9 @@
namespace App\Listeners;
+use App\Contracts\TransactionalNotification;
use App\Models\User;
+use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Auth\Notifications\VerifyEmail;
use Illuminate\Notifications\Events\NotificationSending;
@@ -18,8 +20,13 @@ public function handle(NotificationSending $event): bool
return true;
}
- // System notifications like email verification should always be sent
- if ($event->notification instanceof VerifyEmail) {
+ // Transactional notifications (account recovery, verification,
+ // purchase receipts, entitlement grants) must always be delivered.
+ // Framework notifications can't implement our marker, so they're
+ // listed explicitly.
+ if ($event->notification instanceof TransactionalNotification
+ || $event->notification instanceof VerifyEmail
+ || $event->notification instanceof ResetPassword) {
return true;
}
diff --git a/app/Livewire/ClaimDonationLicense.php b/app/Livewire/ClaimDonationLicense.php
index 96f2064c..748982d7 100644
--- a/app/Livewire/ClaimDonationLicense.php
+++ b/app/Livewire/ClaimDonationLicense.php
@@ -7,6 +7,7 @@
use App\Jobs\CreateAnystackLicenseJob;
use App\Models\OpenCollectiveDonation;
use App\Models\User;
+use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
@@ -107,6 +108,8 @@ public function claim(): void
'name' => $this->name,
'password' => Hash::make($this->password),
]);
+
+ event(new Registered($user));
}
// Parse name for first/last
diff --git a/app/Livewire/MobilePricing.php b/app/Livewire/MobilePricing.php
index 3d07d3bd..062da46f 100644
--- a/app/Livewire/MobilePricing.php
+++ b/app/Livewire/MobilePricing.php
@@ -4,9 +4,11 @@
use App\Enums\Subscription;
use App\Models\User;
+use App\Notifications\ClaimAccount;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Laravel\Cashier\Cashier;
@@ -182,11 +184,19 @@ private function findOrCreateUser(string $email): User
'email' => 'required|email|max:255',
]);
- return User::firstOrCreate([
+ $user = User::firstOrCreate([
'email' => $email,
], [
'password' => Hash::make(Str::random(72)),
]);
+
+ if ($user->wasRecentlyCreated) {
+ $user->notify(new ClaimAccount(
+ Password::broker()->createToken($user)
+ ));
+ }
+
+ return $user;
}
private function successUrl(): string
diff --git a/app/Livewire/OrderSuccess.php b/app/Livewire/OrderSuccess.php
index a59266ed..fa28695b 100644
--- a/app/Livewire/OrderSuccess.php
+++ b/app/Livewire/OrderSuccess.php
@@ -3,7 +3,6 @@
namespace App\Livewire;
use App\Enums\Subscription;
-use App\Models\License;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Laravel\Cashier\Cashier;
use Livewire\Attributes\Layout;
@@ -17,10 +16,10 @@ class OrderSuccess extends Component
{
public ?string $email = null;
- public ?string $licenseKey = null;
-
public ?Subscription $subscription = null;
+ public bool $isExistingUser = false;
+
public string $checkoutSessionId;
public function mount(string $checkoutSessionId): void
@@ -67,9 +66,10 @@ public function loadData(): void
return;
}
- $this->email = $subscriptionRecord->user->email;
- $this->licenseKey = License::query()
- ->whereBelongsTo($subscriptionItem)
- ->first()?->key;
+ $user = $subscriptionRecord->user;
+ $this->email = $user->email;
+ // Users created via checkout start with email_verified_at = null and
+ // are sent a ClaimAccount email. Verified users already have access.
+ $this->isExistingUser = ! is_null($user->email_verified_at);
}
}
diff --git a/app/Notifications/BundleGranted.php b/app/Notifications/BundleGranted.php
index a88b6dd3..6ce81cf9 100644
--- a/app/Notifications/BundleGranted.php
+++ b/app/Notifications/BundleGranted.php
@@ -2,6 +2,7 @@
namespace App\Notifications;
+use App\Contracts\TransactionalNotification;
use App\Models\Plugin;
use App\Models\PluginBundle;
use Illuminate\Bus\Queueable;
@@ -10,7 +11,7 @@
use Illuminate\Notifications\Notification;
use Illuminate\Support\Collection;
-class BundleGranted extends Notification implements ShouldQueue
+class BundleGranted extends Notification implements ShouldQueue, TransactionalNotification
{
use Queueable;
diff --git a/app/Notifications/ClaimAccount.php b/app/Notifications/ClaimAccount.php
new file mode 100644
index 00000000..9b97b332
--- /dev/null
+++ b/app/Notifications/ClaimAccount.php
@@ -0,0 +1,41 @@
+
+ */
+ public function via(object $notifiable): array
+ {
+ return ['mail'];
+ }
+
+ public function toMail(object $notifiable): MailMessage
+ {
+ $url = route('password.reset', [
+ 'token' => $this->token,
+ 'email' => $notifiable->getEmailForPasswordReset(),
+ ]);
+
+ return (new MailMessage)
+ ->subject('Welcome to NativePHP — Claim Your Account')
+ ->greeting('Welcome to NativePHP!')
+ ->line('Thanks for your purchase. We\'ve created an account for you so you can access your licenses and downloads.')
+ ->line('To finish setting up your account, please click the button below to verify your email address and set a password.')
+ ->action('Claim Your Account', $url)
+ ->line('This link will expire in '.config('auth.passwords.users.expire').' minutes. If it expires, you can request a new one from the password reset page.')
+ ->salutation("Happy coding!\n\nThe NativePHP Team");
+ }
+}
diff --git a/app/Notifications/LicenseKeyGenerated.php b/app/Notifications/LicenseKeyGenerated.php
index 585b59f3..ac8879dd 100644
--- a/app/Notifications/LicenseKeyGenerated.php
+++ b/app/Notifications/LicenseKeyGenerated.php
@@ -2,13 +2,14 @@
namespace App\Notifications;
+use App\Contracts\TransactionalNotification;
use App\Enums\Subscription;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
-class LicenseKeyGenerated extends Notification implements ShouldQueue
+class LicenseKeyGenerated extends Notification implements ShouldQueue, TransactionalNotification
{
use Queueable;
diff --git a/app/Notifications/PluginGranted.php b/app/Notifications/PluginGranted.php
index 5c7721a4..86bc5825 100644
--- a/app/Notifications/PluginGranted.php
+++ b/app/Notifications/PluginGranted.php
@@ -2,13 +2,14 @@
namespace App\Notifications;
+use App\Contracts\TransactionalNotification;
use App\Models\Plugin;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
-class PluginGranted extends Notification implements ShouldQueue
+class PluginGranted extends Notification implements ShouldQueue, TransactionalNotification
{
use Queueable;
diff --git a/app/Notifications/ProductGranted.php b/app/Notifications/ProductGranted.php
index d95d9960..38f1a410 100644
--- a/app/Notifications/ProductGranted.php
+++ b/app/Notifications/ProductGranted.php
@@ -2,13 +2,14 @@
namespace App\Notifications;
+use App\Contracts\TransactionalNotification;
use App\Models\Product;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
-class ProductGranted extends Notification implements ShouldQueue
+class ProductGranted extends Notification implements ShouldQueue, TransactionalNotification
{
use Queueable;
diff --git a/package-lock.json b/package-lock.json
index a9c8c099..bdd48d5c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,5 +1,5 @@
{
- "name": "ivory-tiger",
+ "name": "opal-parrot",
"lockfileVersion": 3,
"requires": true,
"packages": {
diff --git a/resources/views/livewire/order-success.blade.php b/resources/views/livewire/order-success.blade.php
index 734d2231..6320e34b 100644
--- a/resources/views/livewire/order-success.blade.php
+++ b/resources/views/livewire/order-success.blade.php
@@ -1,4 +1,4 @@
-
+
{{-- Hero Section --}}
- You've purchased a license.
+ Welcome to NativePHP Ultra.
- @if ($licenseKey)
-
- License key
-
-
+ Your subscription is now active. Head to your
+ dashboard to manage it and access everything
+ included with Ultra.
+
- setTimeout(() => (this.showMessage = false), 2000)
- },
- }"
- >
-
-
- Copied!
-
-
-
-
{{ $licenseKey }}
-
-
- Store this somewhere safe. You'll need it later.
-
- @if ($email)
-
- Email
+ Go to Dashboard
+
+ @else
+
+ We've sent a link to
+ {{ $email }}
+ so you can claim your account and access your
+ dashboard.
-
-
-
- Copied!
-
-
-
-
{{ $email }}
-
+ Didn't get the email? Check your spam folder, or
+ reach out to
+ support@nativephp.com
+ and we'll sort it out.
+
@endif
@else
- License registration in progress
+ Finalising your subscription
- Please
-
- check your email
-
- shortly for a copy of your license key. This page
- will also update if your license key is ready.
-
-
-
- Once you receive your license key, you can start
- building amazing mobile apps with NativePHP!
+ This will only take a moment. The page will update
+ automatically.
- As a Max subscriber, you have access to
- the NativePHP/mobile repository. To
- access it, please log in to
-
- AnyStack.sh
- using the same email address you used
- for your purchase.
-