{{ $plan['name'] }}
+-
+
- 7-day free trial +
- {{ $plan['activation_limit'] }} active Mac{{ $plan['activation_limit'] === 1 ? '' : 's' }} +
- Secure online activation +
- Licence management portal +
diff --git a/app/Http/Controllers/CheckoutController.php b/app/Http/Controllers/CheckoutController.php new file mode 100644 index 0000000..bdfff2b --- /dev/null +++ b/app/Http/Controllers/CheckoutController.php @@ -0,0 +1,38 @@ + ['name' => 'Personal', 'billing' => 'Monthly', 'display_price' => '£1.99 / month', 'price_id' => $paddle['personal_monthly_price_id'], 'activation_limit' => 1], + 'personal-annual' => ['name' => 'Personal', 'billing' => 'Annual', 'display_price' => '£14.99 / year', 'price_id' => $paddle['personal_annual_price_id'], 'activation_limit' => 1], + 'professional-monthly' => ['name' => 'Professional', 'billing' => 'Monthly', 'display_price' => '£3.99 / month', 'price_id' => $paddle['professional_monthly_price_id'], 'activation_limit' => 3], + 'professional-annual' => ['name' => 'Professional', 'billing' => 'Annual', 'display_price' => '£29.99 / year', 'price_id' => $paddle['professional_annual_price_id'], 'activation_limit' => 3], + ]; + + foreach ($plans as $plan) { + if (trim((string) $plan['price_id']) === '') { + throw new RuntimeException('One or more Paddle price IDs are not configured.'); + } + } + + return view('checkout.buy', [ + 'plans' => $plans, + 'paddleToken' => $token, + 'paddleEnvironment' => (string) ($paddle['environment'] ?? 'sandbox'), + ]); + } +} diff --git a/app/Services/Licensing/LicenseDeliveryService.php b/app/Services/Licensing/LicenseDeliveryService.php new file mode 100644 index 0000000..73637a7 --- /dev/null +++ b/app/Services/Licensing/LicenseDeliveryService.php @@ -0,0 +1,38 @@ +whereNull('delivered_at') + ->whereNotNull('recipient_email') + ->where('recipient_email', '!=', '') + ->oldest() + ->limit(max(1, min($limit, 100))) + ->get(); + + foreach ($pending as $outbox) { + $plain = Crypt::decryptString($outbox->encrypted_license_key); + $recipient = strtolower(trim((string) $outbox->recipient_email)); + if ($recipient === '') continue; + + Mail::raw( + "Thank you for choosing Rsync Studio.\n\nYour licence key is:\n\n{$plain}\n\nOpen Rsync Studio → Settings → Licence and enter this key to activate your Mac.\n\nKeep this key private. You can manage activations from the Rsync Studio licence portal.", + fn ($message) => $message->to($recipient)->subject('Your Rsync Studio licence key') + ); + + $outbox->update(['delivered_at' => now()]); + $delivered++; + } + + return $delivered; + } +} diff --git a/resources/views/checkout/buy.blade.php b/resources/views/checkout/buy.blade.php new file mode 100644 index 0000000..6b4ea18 --- /dev/null +++ b/resources/views/checkout/buy.blade.php @@ -0,0 +1,91 @@ +@extends('layouts.app') +@section('content') +
Every subscription starts with a 7-day free trial. Cancel before the trial ends and you won't be charged.
+ @if($paddleEnvironment === 'sandbox')Paddle is confirming your subscription. Your Rsync Studio licence key will be sent to the email used at checkout. You can then activate it in Rsync Studio → Settings → Licence.
+If the email takes a moment, wait for the Paddle confirmation and check your junk folder. You can also create a portal account with the same email address.
+ Create portal account +For licence recovery, create or sign in to your portal account using the same email address used at checkout.
For activation problems, include the app version and the exact error message. Never send SSH passwords, private keys, or your full licence token.