From 7c8b5fabd68d0f22678abdb9e1eed9325929e1b3 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Wed, 9 Sep 2026 12:36:34 +0100 Subject: [PATCH] paddel setup new verifier --- app/Http/Controllers/CheckoutController.php | 38 ++++++++ .../Licensing/LicenseDeliveryService.php | 38 ++++++++ resources/views/checkout/buy.blade.php | 91 +++++++++++++++++++ resources/views/support.blade.php | 3 + tests/Feature/CheckoutPageTest.php | 27 ++++++ 5 files changed, 197 insertions(+) create mode 100644 app/Http/Controllers/CheckoutController.php create mode 100644 app/Services/Licensing/LicenseDeliveryService.php create mode 100644 resources/views/checkout/buy.blade.php create mode 100644 resources/views/support.blade.php create mode 100644 tests/Feature/CheckoutPageTest.php 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') +
+ Rsync Studio +

Choose your licence

+

Every subscription starts with a 7-day free trial. Cancel before the trial ends and you won't be charged.

+ @if($paddleEnvironment === 'sandbox')
Sandbox checkout — no real payment is taken
@endif +
+ +
+@foreach($plans as $code => $plan) +
+
+
+
+

{{ $plan['name'] }}

+
{{ $plan['billing'] }} billing
+
+ @if($plan['billing'] === 'Annual')Best value@endif +
+
{{ $plan['display_price'] }}
+
    +
  • 7-day free trial
  • +
  • {{ $plan['activation_limit'] }} active Mac{{ $plan['activation_limit'] === 1 ? '' : 's' }}
  • +
  • Secure online activation
  • +
  • Licence management portal
  • +
+
+ +
+@endforeach +
+ + + + + +@endsection diff --git a/resources/views/support.blade.php b/resources/views/support.blade.php new file mode 100644 index 0000000..0632136 --- /dev/null +++ b/resources/views/support.blade.php @@ -0,0 +1,3 @@ +@extends('layouts.app') @section('content') +

Rsync Studio Support

For licence recovery, create or sign in to your portal account using the same email address used at checkout.

Open licence portal

For activation problems, include the app version and the exact error message. Never send SSH passwords, private keys, or your full licence token.

+@endsection diff --git a/tests/Feature/CheckoutPageTest.php b/tests/Feature/CheckoutPageTest.php new file mode 100644 index 0000000..eeb07d4 --- /dev/null +++ b/tests/Feature/CheckoutPageTest.php @@ -0,0 +1,27 @@ +set('rsync-studio.paddle.client_side_token', 'test_example'); + config()->set('rsync-studio.paddle.environment', 'sandbox'); + config()->set('rsync-studio.paddle.personal_monthly_price_id', 'pri_personal_monthly'); + config()->set('rsync-studio.paddle.personal_annual_price_id', 'pri_personal_annual'); + config()->set('rsync-studio.paddle.professional_monthly_price_id', 'pri_professional_monthly'); + config()->set('rsync-studio.paddle.professional_annual_price_id', 'pri_professional_annual'); + + $this->get('/buy') + ->assertOk() + ->assertSee('7-day free trial') + ->assertSee('pri_personal_monthly') + ->assertSee('pri_personal_annual') + ->assertSee('pri_professional_monthly') + ->assertSee('pri_professional_annual') + ->assertSee('test_example'); + } +}