updated deployer code

This commit is contained in:
Mark Gregory
2026-09-29 16:55:11 +01:00
parent 390bdae783
commit f2c54c6518
16 changed files with 922 additions and 55 deletions
+13
View File
@@ -0,0 +1,13 @@
# ============================================================
# SonarDesk Paddle SANDBOX
# ============================================================
PADDLE_ENV=sandbox
PADDLE_PRODUCT_ID=pro_01m3ptwmqy8npywsq4h4q27p0w
PADDLE_PERSONAL_MONTHLY_PRICE_ID=pri_01m3pv9azapr6t57j7eawx9eej
PADDLE_PERSONAL_YEARLY_PRICE_ID=pri_01m3pv9b6t3w0qaa6erv05jy33
PADDLE_PROFESSIONAL_MONTHLY_PRICE_ID=pri_01m3pv9bnteca41p1mj1jk0xxe
PADDLE_PROFESSIONAL_YEARLY_PRICE_ID=pri_01m3pv9bx1s1129zhy5z2pa2f8
View File
+6
View File
@@ -0,0 +1,6 @@
projectKey=sonar_desk
serverUrl=https://sonarcube.absdev.net
serverVersion=26.9.0.129388
dashboardUrl=https://sonarcube.absdev.net/dashboard?id=sonar_desk
ceTaskId=ed0bb599-656b-42f0-a0ec-c838a1ab98b2
ceTaskUrl=https://sonarcube.absdev.net/api/ce/task?id=ed0bb599-656b-42f0-a0ec-c838a1ab98b2
+415
View File
@@ -0,0 +1,415 @@
# SonarDesk Commercial Launch & Selling Guide
## 1. Recommended commercial model
Launch SonarDesk as a **direct-download macOS product with recurring licensing**. Use **Paddle as the production Merchant of Record (MoR)** for the initial launch because this codebase already has Paddle checkout, subscription lifecycle handling, customer lookup, upgrades/cancellation, webhook verification and product mapping implemented.
Do **not** operate Paddle and Lemon Squeezy as two public checkout systems at launch. Pick one system of record. Running two providers doubles reconciliation, refund, subscription-state and support complexity.
### Launch pricing
| Plan | Monthly | Annual | Activations | Positioning |
|---|---:|---:|---:|---|
| Personal | £2.99/month | £24.99/year | 1 Mac | Individual developer |
| Professional | £4.99/month | £44.99/year | 3 Macs | Multi-Mac / professional workflow |
Both plans should include the complete feature set and a **7-day free trial**. Do not artificially cripple Personal. The upgrade is based on activation allowance, which is simple to understand and easy to support.
Annual should be the visual default. At these prices it produces better unit economics than very low monthly pricing because both Paddle and Lemon Squeezy publish a fixed transaction component in addition to their percentage fee.
### Launch offer
For the first 30 days, use a single promotion rather than permanently reducing the list price:
- 20% off the first annual term using a launch coupon such as `LAUNCH20`.
- Do not discount monthly plans.
- Do not promise “lifetime” access while the licensing model is subscription-based.
- Keep the public list price stable so the product does not look disposable or permanently on sale.
## 2. Why Paddle is the recommended launch provider
Paddle's public pay-as-you-go pricing is 5% + $0.50 per checkout transaction and includes payments/billing, tax compliance and related Merchant of Record services. Paddle supports a sandbox, checkout, subscription lifecycle events and signed webhooks.
This project is already designed around Paddle:
- `CheckoutController` supplies Paddle price IDs to Paddle.js.
- `/api/v1/webhooks/paddle` accepts Paddle events.
- `PaddleWebhookVerifier` verifies signed webhook requests.
- `PurchaseProvisioner` provisions from `subscription.*` and `transaction.completed` events.
- `PaddleCatalogSeeder` maps Paddle products/prices to SonarDesk licence products.
- The portal can upgrade Personal to Professional and cancel Paddle subscriptions.
That makes Paddle the lowest-risk route to a release.
Official references:
- Paddle pricing: https://www.paddle.com/pricing
- Paddle quickstart: https://developer.paddle.com/get-started/quickstart/
- Paddle webhooks: https://developer.paddle.com/webhooks/
- Paddle webhook simulator: https://developer.paddle.com/webhooks/simulator/
## 3. Paddle setup — complete production checklist
### A. Create/complete the Paddle account
1. Create the Paddle account and complete business verification.
2. Add the legal/business details that Paddle requests.
3. Configure payout details.
4. Add the production website/domain.
5. Complete Paddle's domain/default payment link approval before launch.
6. Keep the account in sandbox while integrating. Switch the application to production only after the full purchase flow has passed end-to-end testing.
### B. Create the catalog
Create two Paddle products:
**SonarDesk Personal**
- Monthly recurring price: **£3.99 GBP / month**
- Annual recurring price: **£29.99 GBP / year**
- Trial: **7 days**
**SonarDesk Professional**
- Monthly recurring price: **£6.99 GBP / month**
- Annual recurring price: **£59.99 GBP / year**
- Trial: **7 days**
Use tax-inclusive/exclusive behaviour consistently with the checkout experience you want Paddle to present. Paddle, as MoR, calculates the applicable transaction taxes in checkout.
Copy the resulting two product IDs and four price IDs.
### C. Create application credentials
In Paddle Developer Tools:
1. Create a server API key for the licensing application.
2. Create a client-side token for Paddle.js.
3. Never put the server API key into browser JavaScript or source control.
4. Keep sandbox and production credentials separate.
### D. Configure the webhook
Create a Paddle notification destination:
`https://YOUR-DOMAIN/api/v1/webhooks/paddle`
Subscribe at minimum to:
- `transaction.completed`
- `subscription.created`
- `subscription.updated`
- `subscription.activated`
- `subscription.canceled`
- `subscription.paused`
- `subscription.resumed`
- `subscription.past_due`
The current code uses subscription events as the authoritative state for recurring licences. Paddle documents `transaction.completed` as the point at which transaction processing is complete. Paddle also documents at-least-once webhook delivery, so webhook handlers must remain idempotent; this project stores webhook event IDs for that purpose.
Copy the webhook endpoint secret.
### E. Production `.env`
Set the following values on the production server. Do not commit them.
```dotenv
APP_ENV=production
APP_DEBUG=false
APP_URL=https://YOUR-DOMAIN
PADDLE_ENVIRONMENT=production
PADDLE_API_URL=https://api.paddle.com
PADDLE_API_KEY=YOUR_PRODUCTION_API_KEY
PADDLE_CLIENT_SIDE_TOKEN=YOUR_PRODUCTION_CLIENT_TOKEN
PADDLE_WEBHOOK_SECRET=YOUR_PRODUCTION_WEBHOOK_SECRET
PADDLE_WEBHOOK_TOLERANCE=300
PADDLE_PERSONAL_PRODUCT_ID=pro_...
PADDLE_PERSONAL_MONTHLY_PRICE_ID=pri_...
PADDLE_PERSONAL_ANNUAL_PRICE_ID=pri_...
PADDLE_PROFESSIONAL_PRODUCT_ID=pro_...
PADDLE_PROFESSIONAL_MONTHLY_PRICE_ID=pri_...
PADDLE_PROFESSIONAL_ANNUAL_PRICE_ID=pri_...
```
Also configure the existing SonarDesk licence signing keys, mailer, support email and application database.
### F. Seed the price mapping
After the production IDs are in `.env`:
```bash
php artisan config:clear
php artisan db:seed --class=PaddleCatalogSeeder
php artisan config:cache
```
Check the `license_products` table and confirm there are four active Paddle rows with the correct product/price IDs and activation limits.
### G. Test in sandbox before production
Test every one of these scenarios:
1. Personal monthly trial signup.
2. Personal annual trial signup.
3. Professional monthly trial signup.
4. Professional annual trial signup.
5. Licence email is delivered.
6. Licence activates in SonarDesk.
7. Second activation is rejected for Personal.
8. Up to three activations work for Professional.
9. Portal displays the licence.
10. Deactivate a Mac and activate a replacement Mac.
11. Upgrade Personal to Professional.
12. Cancel a subscription.
13. Simulate a failed payment / past-due subscription.
14. Simulate cancellation/expiry and confirm entitlement behaviour.
15. Replay the same webhook and confirm a duplicate licence is not created.
16. Refund test transaction and verify the intended entitlement state.
17. Verify support and transactional email deliverability.
18. Verify checkout on Safari and Chrome, including a private window.
Use Paddle's webhook simulator for lifecycle testing as well as a real sandbox checkout.
### H. Go live
1. Create the production catalog (do not reuse sandbox IDs).
2. Add production API key/client token/webhook secret.
3. Seed the production catalog mapping.
4. Change `PADDLE_ENVIRONMENT=production` and `PADDLE_API_URL=https://api.paddle.com`.
5. Run `php artisan config:cache`.
6. Make one real low-value purchase with a controlled account.
7. Confirm the webhook, licence issuance, email, activation, portal and cancellation path.
8. Refund the test purchase in Paddle after validation if appropriate.
## 4. Lemon Squeezy — alternative setup
Lemon Squeezy is also a Merchant of Record and publishes base ecommerce pricing of 5% + $0.50 per transaction, with additional fees in some cases (for example certain international, PayPal and subscription payments). It also provides software licence keys and a License API.
Official references:
- Pricing: https://www.lemonsqueezy.com/pricing
- Fees: https://docs.lemonsqueezy.com/help/getting-started/fees
- Webhooks: https://docs.lemonsqueezy.com/help/webhooks
- License API: https://docs.lemonsqueezy.com/api/license-api
### Important for this codebase
The application has a Lemon Squeezy webhook verifier and basic purchase provisioning, but the **public checkout and customer portal are currently Paddle-first**. Lemon Squeezy should therefore be treated as an alternative migration path, not a second launch checkout, until its full subscription lifecycle and portal actions are implemented and tested.
If switching to Lemon Squeezy:
1. Create a store and complete activation/verification.
2. Create Personal and Professional products/variants with the same public pricing.
3. Decide whether SonarDesk's own signed licence system remains authoritative (recommended for consistency with the Mac app) or whether you migrate fully to Lemon Squeezy's License API. Do not mix both without a clear source of truth.
4. Create a webhook at:
`https://YOUR-DOMAIN/api/v1/webhooks/lemon-squeezy`
5. Generate a strong signing secret and set:
```dotenv
LEMON_SQUEEZY_WEBHOOK_SECRET=YOUR_SECRET
```
6. Subscribe to the events the application needs, including order creation/refund and all relevant subscription state events.
7. Add `license_products` mappings where `payment_provider=lemon-squeezy`, mapping Lemon Squeezy variant IDs to `external_price_id`.
8. Implement/verify checkout links or overlay checkout on `/buy` before exposing Lemon Squeezy publicly.
9. Extend `PurchaseProvisioner::provisionLemonSqueezy()` so subscription updates, cancellations, pauses, payment failures/recovery and upgrades are handled with the same completeness as Paddle.
10. Add customer-portal actions for cancellation/change-of-plan or route customers to Lemon Squeezy's supported customer management flow.
11. Run the same end-to-end test matrix used for Paddle.
Lemon Squeezy webhooks are signed and should always be verified. Their documentation states that unsuccessful webhook deliveries are retried; keep processing idempotent and persist event identity/state.
## 5. Website conversion strategy
The updated site now has a clearer commercial funnel:
`Home → Features → Pricing → Paddle checkout → licence email → app activation → customer portal`
### Home page
The home page should answer these questions in order:
1. What is it? A native macOS companion for SonarQube.
2. Why should I care? Faster access to project health and issues without browser-tab friction.
3. What does it do? Projects, quality gates, measures, issues, multiple servers and secure token storage.
4. Is it safe? SonarQube tokens are stored in macOS Keychain and server permissions remain authoritative.
5. What does it cost? Clear Personal and Professional pricing.
6. What should I do next? Start the 7-day free trial.
### Product language to keep consistent
**Primary one-line pitch**
> SonarDesk is a native macOS companion for SonarQube that puts project health, quality gates and issues in one focused desktop workspace.
**Short description**
> Monitor SonarQube projects, quality gates, bugs, vulnerabilities, code smells and measures without living in a browser.
**Positioning rule**
Never imply SonarDesk replaces SonarQube. The stronger and more credible position is: **SonarQube remains the source of truth; SonarDesk makes everyday monitoring faster on macOS.**
## 6. Launch channels
### Phase 1 — pre-launch (7–14 days)
- Finish notarised/signable release build and direct-download hosting.
- Finish Paddle production approval.
- Record a 30–60 second clean screen capture showing: connect server → project dashboard → issues → issue details.
- Produce 4–6 high-resolution screenshots.
- Create the launch coupon.
- Test website analytics and conversion events.
- Prepare support macros for activation, lost key, machine replacement, refund and SonarQube connection problems.
- Recruit 5–10 testers from existing developer contacts for final feedback and testimonials you have permission to publish.
### Phase 2 — launch day
Publish in this order:
1. SonarDesk release page on your own site.
2. LinkedIn launch post.
3. Medium article.
4. Relevant developer communities where self-promotion is allowed and useful.
5. Existing customer/user mailing list only if recipients have consented to product marketing.
Do not spam generic communities. Show the product solving a concrete SonarQube workflow problem.
### Phase 3 — first 30 days
Publish practical content rather than repeating sales copy:
- “A faster way to review SonarQube issues on macOS”
- “How I use SonarQube quality gates in a desktop workflow”
- “What SonarDesk stores locally and how tokens are protected”
- “Personal vs Professional: which SonarDesk licence do I need?”
- Short release notes for every update
Track: website → pricing clicks, checkout starts, trial starts, trial-to-paid conversion, cancellations, refunds, activation failures and support topics.
## 7. LinkedIn release post
**SonarDesk 1.0 is now available for macOS.**
If SonarQube is part of your development workflow, you probably know the routine: open the browser, find the project, check the quality gate, scan the latest issues, then switch back to your editor.
I built SonarDesk to make that recurring workflow faster on the Mac.
SonarDesk is a native macOS companion for SonarQube. It brings project health, quality gates, bugs, vulnerabilities, code smells and measures into one focused desktop workspace.
Version 1.0 includes:
- Project dashboards and quality gate status
- Bugs, vulnerabilities and code smells
- Detailed issue views and filtering
- Server-side pagination for larger issue sets
- Multiple SonarQube server configurations
- SonarQube token storage in macOS Keychain
- Direct licensing and a self-service customer portal
SonarQube remains the source of truth. SonarDesk is designed to make the information you check throughout the day quicker to reach and easier to keep visible.
Personal is £24.99/year for one active Mac. Professional is £44.99/year for up to three active Macs. Monthly options are also available, and every plan starts with a 7-day free trial.
Learn more and try SonarDesk: **YOUR_RELEASE_URL**
#macOS #SonarQube #SoftwareDevelopment #CodeQuality #DeveloperTools
## 8. Medium launch article
# Introducing SonarDesk: a native SonarQube companion for macOS
SonarQube is a valuable part of the software-development toolchain. It gives teams a central place to understand code quality, security findings and the state of quality gates. But the information developers need throughout the day often lives one browser context-switch away from the work itself.
That is the problem I wanted SonarDesk to solve.
## A focused Mac workspace for SonarQube
SonarDesk is a native macOS companion for SonarQube. It is not a replacement for SonarQube, and it does not try to become another analysis engine. Your SonarQube server remains the source of truth.
Instead, SonarDesk focuses on the recurring desktop workflow: open a project, see its health, check the quality gate, browse bugs, vulnerabilities and code smells, inspect an issue, and get back to the code.
## What SonarDesk 1.0 includes
The first release includes project dashboards, quality gate status, important measures, issue browsing and detailed issue views. Issue lists support practical filtering and server-side pagination, which helps keep the workflow manageable as result sets grow.
SonarDesk can also keep multiple SonarQube server configurations available from the same Mac application. SonarQube access tokens are stored using macOS Keychain, and access remains governed by the permissions of the credentials you provide.
## Why a native app?
The browser is excellent for deep investigation and administration, and SonarDesk is not intended to remove it from the workflow. The benefit of a dedicated desktop app is focus. The project-health information you check repeatedly can have a consistent place on the Mac, separate from documentation, tickets, dashboards and all the other tabs competing for attention.
## Direct distribution and licensing
SonarDesk is distributed directly for macOS. Customers receive a licence and can manage activations through the SonarDesk customer portal.
There are two plans. Personal supports one active Mac and costs £24.99 per year or £2.99 per month. Professional supports up to three active Macs and costs £44.99 per year or £4.99 per month. Both include the complete feature set and start with a seven-day free trial.
## What comes next
Version 1.0 establishes the core SonarQube monitoring workflow on macOS. Future releases will continue to focus on making project and issue review faster, clearer and more useful without turning SonarDesk into a replacement for the platform it connects to.
If SonarQube is part of your day-to-day development workflow and you work on a Mac, you can learn more and start a free trial here:
**YOUR_RELEASE_URL**
## 9. Short announcement copy
**SonarDesk 1.0 for macOS is available now.** Monitor SonarQube project health, quality gates, bugs, vulnerabilities and code smells from a focused native Mac app. Personal and Professional plans include a 7-day free trial. **YOUR_RELEASE_URL**
## 10. Product listing copy
### Name
SonarDesk
### Tagline
Native SonarQube monitoring for macOS.
### 80-character style description
Monitor SonarQube projects, quality gates and issues from your Mac.
### Full product description
SonarDesk is a native macOS companion for developers and engineering teams using SonarQube. It provides a focused desktop view of project health, quality gates, measures, bugs, vulnerabilities and code smells, with detailed issue views, filtering, server-side pagination and support for multiple SonarQube server configurations. SonarQube tokens are stored in macOS Keychain, and your existing SonarQube permissions remain authoritative.
## 11. Release readiness checklist
Do not announce the public release until all items below are true:
- Production app is signed with Developer ID and notarised.
- DMG/ZIP download is served over HTTPS.
- Version and build number are final.
- Sparkle/update mechanism, if used, is production-configured and signed.
- Privacy policy, terms and cookie information match actual behaviour.
- Support address is monitored.
- Paddle production checkout is approved and working.
- Webhook signature verification is enabled.
- Production webhook endpoint returns successful responses.
- Production product and price IDs are mapped in the database.
- Licence signing private key is backed up securely and is not in source control.
- Licence activation/validation/deactivation work from a clean Mac.
- Purchase email and support email pass SPF/DKIM/DMARC checks.
- Trial, paid renewal, cancellation, failed payment and refund paths are tested.
- Website has working Home, Features, Pricing, Release, Support, Privacy and Terms pages.
- Open Graph image and favicon render correctly.
- A real production purchase has been tested end-to-end.
- Download analytics and checkout conversion events are visible.
- Launch screenshots/video are ready.
- LinkedIn and Medium copy has been proofread and final URLs inserted.
## 12. First commercial targets
For the first 90 days, optimize for learning rather than vanity traffic. Review weekly:
- Unique visitors to the product page
- Pricing-page visits
- Checkout starts
- Trial starts
- Trial-to-paid conversion
- Annual vs monthly mix
- Personal vs Professional mix
- Cancellation/refund reasons
- Activation failure rate
- Top support issues
Do not change pricing every week. Collect enough data to understand whether the problem is traffic, positioning, checkout friction, onboarding or product value before changing the commercial model.
+4 -4
View File
@@ -17,10 +17,10 @@ final class CheckoutController extends Controller
}
$plans = [
'personal-monthly' => ['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],
'personal-monthly' => ['name' => 'Personal', 'billing' => 'Monthly', 'display_price' => '£2.99 / month', 'price_id' => $paddle['personal_monthly_price_id'], 'activation_limit' => 1],
'personal-annual' => ['name' => 'Personal', 'billing' => 'Annual', 'display_price' => '£24.99 / year', 'price_id' => $paddle['personal_annual_price_id'], 'activation_limit' => 1],
'professional-monthly' => ['name' => 'Professional', 'billing' => 'Monthly', 'display_price' => '£4.99 / month', 'price_id' => $paddle['professional_monthly_price_id'], 'activation_limit' => 3],
'professional-annual' => ['name' => 'Professional', 'billing' => 'Annual', 'display_price' => '£44.99 / year', 'price_id' => $paddle['professional_annual_price_id'], 'activation_limit' => 3],
];
foreach ($plans as $plan) {
+81 -13
View File
@@ -3,36 +3,104 @@
namespace App\Services\Payments;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use RuntimeException;
final class PaddleWebhookVerifier implements WebhookVerifier
{
public function verify(Request $request, string $rawBody): void
{
$secret = (string) config('sonardesk.paddle.webhook_secret');
$header = (string) $request->header('Paddle-Signature');
if ($secret === '' || $header === '') {
throw new RuntimeException('Paddle webhook verification is not configured.');
$secret = trim((string) config('sonardesk.paddle.webhook_secret'));
$header = trim((string) $request->header('Paddle-Signature'));
if ($secret === '') {
throw new RuntimeException('Paddle webhook secret is not configured.');
}
if ($header === '') {
throw new RuntimeException('Paddle-Signature header is missing.');
}
$parts = [];
foreach (explode(';', $header) as $part) {
[$k,$v] = array_pad(explode('=', trim($part), 2), 2, null);
if ($k && $v) {
$parts[$k][] = $v;
[$key, $value] = array_pad(
explode('=', trim($part), 2),
2,
null
);
if ($key !== null && $value !== null) {
$parts[$key][] = $value;
}
}
$ts = isset($parts['ts'][0]) ? (int) $parts['ts'][0] : 0;
$timestamp = isset($parts['ts'][0])
? (int) $parts['ts'][0]
: 0;
$signatures = $parts['h1'] ?? [];
$tolerance = max(1, (int) config('sonardesk.paddle.timestamp_tolerance', 300));
if (! $ts || abs(time() - $ts) > $tolerance) {
throw new RuntimeException('Paddle webhook timestamp is outside the allowed tolerance.');
if ($timestamp === 0) {
throw new RuntimeException(
'Paddle webhook signature has no timestamp.'
);
}
$expected = hash_hmac('sha256', $ts.':'.$rawBody, $secret);
if ($signatures === []) {
throw new RuntimeException(
'Paddle webhook signature has no h1 signature.'
);
}
$tolerance = max(
1,
(int) config('sonardesk.paddle.timestamp_tolerance', 300)
);
$age = abs(time() - $timestamp);
if ($age > $tolerance) {
throw new RuntimeException(
"Paddle webhook timestamp is outside the allowed tolerance ({$age}s)."
);
}
$signedPayload = $timestamp . ':' . $rawBody;
$expected = hash_hmac(
'sha256',
$signedPayload,
$secret
);
Log::debug('Paddle webhook signature diagnostics', [
'timestamp' => $timestamp,
'age_seconds' => $age,
'body_length' => strlen($rawBody),
'body_sha256' => hash('sha256', $rawBody),
// Safe diagnostics — do NOT log the secret itself.
'secret_prefix' => substr($secret, 0, 11),
'secret_length' => strlen($secret),
'received_signature_prefixes' => array_map(
static fn (string $signature): string =>
substr($signature, 0, 12),
$signatures
),
'expected_signature_prefix' => substr($expected, 0, 12),
]);
foreach ($signatures as $signature) {
if (hash_equals($expected, $signature)) {
return;
}
}
throw new RuntimeException('Paddle webhook signature is invalid.');
throw new RuntimeException(
'Paddle webhook signature is invalid.'
);
}
}
Executable → Regular
+5 -2
View File
@@ -26,6 +26,7 @@ return [
'alert_emails' => array_values(array_filter(array_map('trim', explode(',', (string) env('SONARDESK_SUPPORT_ALERT_EMAILS', ''))))),
'notify_admin_users' => filter_var(env('SONARDESK_SUPPORT_NOTIFY_ADMINS', true), FILTER_VALIDATE_BOOL),
],
'paddle' => [
'api_key' => env('PADDLE_API_KEY'),
'client_side_token' => env('PADDLE_CLIENT_SIDE_TOKEN'),
@@ -33,13 +34,15 @@ return [
'api_url' => env('PADDLE_API_URL', env('APP_ENV') === 'production' ? 'https://api.paddle.com' : 'https://sandbox-api.paddle.com'),
'webhook_secret' => env('PADDLE_WEBHOOK_SECRET'),
'timestamp_tolerance' => (int) env('PADDLE_WEBHOOK_TOLERANCE', 300),
'personal_product_id' => env('PADDLE_PERSONAL_PRODUCT_ID'),
// SonarDesk is one Paddle product with four recurring prices.
'product_id' => env('PADDLE_PRODUCT_ID'),
'personal_monthly_price_id' => env('PADDLE_PERSONAL_MONTHLY_PRICE_ID'),
'personal_annual_price_id' => env('PADDLE_PERSONAL_ANNUAL_PRICE_ID'),
'professional_product_id' => env('PADDLE_PROFESSIONAL_PRODUCT_ID'),
'professional_monthly_price_id' => env('PADDLE_PROFESSIONAL_MONTHLY_PRICE_ID'),
'professional_annual_price_id' => env('PADDLE_PROFESSIONAL_ANNUAL_PRICE_ID'),
],
'lemon_squeezy' => [
'webhook_secret' => env('LEMON_SQUEEZY_WEBHOOK_SECRET'),
],
Binary file not shown.
+15
View File
@@ -0,0 +1,15 @@
@extends('layouts.app', ['title' => 'Features — SonarDesk', 'description' => 'Explore SonarDesk features for monitoring SonarQube projects, quality gates, issues and measures from macOS.'])
@section('content')
<section class="hero-card marketing-hero compact-hero"><span class="eyebrow">Features</span><h1>A focused desktop companion for SonarQube.</h1><p class="lead">Built around the checks developers make every day: project status, quality gates, issues, security findings and measures.</p><div class="support-actions marketing-actions"><a class="btn btn-primary" href="{{ route('buy') }}">Start free trial</a><a class="btn btn-secondary" href="{{ route('release') }}">Read the release notes</a></div></section>
<div class="feature-detail-grid">
<article class="feature-detail"><span>Projects</span><h2>Project dashboard</h2><p>Keep configured SonarQube projects together and scan project health without opening a collection of browser tabs.</p></article>
<article class="feature-detail"><span>Quality</span><h2>Quality gates and measures</h2><p>Surface the status and metrics that tell you whether a project needs attention, in a layout designed for quick review.</p></article>
<article class="feature-detail"><span>Issues</span><h2>Bugs, vulnerabilities and code smells</h2><p>Browse the major SonarQube issue categories, filter the list and open an issue to inspect its details and context.</p></article>
<article class="feature-detail"><span>Scale</span><h2>Server-side pagination</h2><p>Issue browsing is designed to remain practical on projects with larger result sets by paging data from the server.</p></article>
<article class="feature-detail"><span>Connections</span><h2>Multiple SonarQube servers</h2><p>Configure the environments you work with and keep them available from the same SonarDesk workspace.</p></article>
<article class="feature-detail"><span>Security</span><h2>macOS Keychain</h2><p>SonarQube tokens are stored using macOS Keychain rather than being treated as ordinary application preferences.</p></article>
<article class="feature-detail"><span>Licensing</span><h2>Self-service customer portal</h2><p>Customers can manage licence information and activations from the SonarDesk portal, with secure online activation.</p></article>
<article class="feature-detail"><span>Distribution</span><h2>Built for direct Mac distribution</h2><p>SonarDesk is distributed directly for macOS, giving the product a focused release and support channel outside the Mac App Store.</p></article>
</div>
<section class="cta-panel"><div><span class="eyebrow">7-day free trial</span><h2>Try the complete app.</h2><p>Choose Personal for one active Mac or Professional for up to three.</p></div><a class="btn btn-primary" href="{{ route('buy') }}">View pricing</a></section>
@endsection
+6
View File
@@ -96,6 +96,7 @@
.danger-outline{background:#fff;color:var(--red);border:1px solid #f3b7b2}.danger-outline:hover{background:var(--red-soft)}
.full-width{width:100%}
.marketing-hero{padding:72px 0 46px}.marketing-hero h1{max-width:900px;font-size:64px;line-height:1.01;margin:12px 0 20px}.marketing-hero .lead{font-size:20px;max-width:820px}.compact-hero{padding-bottom:36px}.compact-hero h1{font-size:52px}.marketing-actions{margin-top:28px}.microcopy{font-size:13px;color:var(--muted);margin-top:14px}.trust-strip{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line);border:1px solid var(--line);border-radius:14px;overflow:hidden;margin:0 0 68px}.trust-strip span{background:var(--surface);padding:18px;text-align:center;font-size:13px;font-weight:750;color:#475467}.section-block{padding:24px 0 64px}.section-heading{max-width:760px;margin-bottom:28px}.section-heading h2,.split-panel h2,.cta-panel h2{font-size:38px;line-height:1.12;letter-spacing:-.03em;margin:8px 0 12px}.section-heading p{font-size:17px;color:#667085}.feature-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.feature-card{background:var(--surface);border:1px solid var(--line);border-radius:16px;padding:24px;box-shadow:var(--shadow-sm)}.feature-card h3{font-size:19px;margin:18px 0 8px}.feature-card p{color:#667085;margin:0}.feature-icon{display:inline-flex;width:38px;height:38px;align-items:center;justify-content:center;border-radius:10px;background:var(--blue-soft);color:var(--blue-dark);font-size:12px;font-weight:900}.split-panel{display:grid;grid-template-columns:1.2fr .8fr;gap:44px;align-items:center;background:#0f172a;color:#fff;border-radius:22px;padding:42px;margin-bottom:64px}.split-panel .lead{color:#cbd5e1}.check-panel{display:grid;gap:10px}.check-panel div{padding:13px 14px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.05);border-radius:10px;font-weight:650}.check-panel div:before{content:'✓';margin-right:10px;color:#86efac}.pricing-preview-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}.marketing-price{font-size:38px;font-weight:900;letter-spacing:-.035em;margin-top:14px}.marketing-price small{font-size:15px;font-weight:650;color:var(--muted);letter-spacing:0}.center-action{text-align:center;margin-top:24px}.cta-panel{display:flex;align-items:center;justify-content:space-between;gap:30px;background:var(--surface);border:1px solid var(--line);border-radius:20px;padding:34px 38px;box-shadow:var(--shadow-md);margin:12px 0 20px}.cta-panel h2{margin-bottom:6px}.cta-panel p{margin:0;color:#667085}.feature-detail-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px;margin:10px 0 64px}.feature-detail{background:var(--surface);border:1px solid var(--line);border-radius:16px;padding:28px}.feature-detail>span{color:var(--blue);font-size:11px;font-weight:850;text-transform:uppercase;letter-spacing:.12em}.feature-detail h2{font-size:24px;margin:8px 0}.feature-detail p{color:#667085;margin:0}.release-body{display:grid;grid-template-columns:minmax(0,1fr) 310px;gap:42px;margin:10px 0 64px}.release-main{background:var(--surface);border:1px solid var(--line);border-radius:18px;padding:36px}.release-main h2{font-size:28px;margin:32px 0 10px}.release-main p,.release-list{color:#475467;font-size:16px;line-height:1.75}.release-list{padding-left:22px}.release-aside{align-self:start;position:sticky;top:92px}
.pricing-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px;margin:20px 0 28px}
.price-card,.card,.panel{background:var(--surface);border:1px solid var(--line);border-radius:16px;padding:24px;box-shadow:var(--shadow-sm)}
.price-card{display:flex;flex-direction:column;justify-content:space-between;gap:24px}.price-card.featured{border-color:#9dbcf7}
@@ -140,6 +141,7 @@
@media(max-width:980px){
.hero-grid{grid-template-columns:1fr;gap:48px}.app-window{max-width:720px;transform:none}.feature-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.workflow-grid{grid-template-columns:repeat(2,1fr)}.security-panel{grid-template-columns:1fr;gap:28px}.nav-email{display:none}
}
@media(max-width:900px){.feature-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.trust-strip{grid-template-columns:repeat(2,1fr)}.release-body{grid-template-columns:1fr}.release-aside{position:static}.marketing-hero h1{font-size:50px}}
@media(max-width:760px){
.site-header{position:relative}.nav-shell{padding:10px 16px;min-height:64px;gap:12px;flex-wrap:wrap}.brand{margin-right:auto}.nav-links{order:3;width:100%;gap:18px;overflow-x:auto;padding:2px 0 4px}.nav-links::-webkit-scrollbar{display:none}.nav-account{margin-left:0}.nav-account .nav-auth-link{display:none}.wrap{padding:28px 16px 48px}.pricing-grid,.plan-grid{grid-template-columns:1fr}.hero-card h1,.portal-header h1{font-size:32px}.portal-header,.licence-detail-head,.licence-title-row,.upgrade-strip,.subscription-warning{align-items:stretch;flex-direction:column}.portal-header .btn,.licence-title-row .btn{width:100%}.licence-stats{grid-template-columns:1fr}.detail-grid{grid-template-columns:1fr}.history-row{grid-template-columns:1fr auto}.history-row a{grid-column:1/-1}.device-row{grid-template-columns:38px 1fr}.device-row form{grid-column:1/-1}.device-row form button{width:100%}
.launch-hero{padding:58px 18px 88px;background:linear-gradient(180deg,#0b1220 0,#101b31 84%,#f7f9fc 84%)}.launch-hero h1{font-size:48px}.hero-copy{font-size:17px}.app-body{grid-template-columns:120px 1fr;min-height:330px}.app-sidebar{padding:12px 8px}.side-item{padding:7px 8px}.app-content{padding:14px}.launch-section{padding:68px 18px}.feature-grid,.workflow-grid{grid-template-columns:1fr}.security-panel{padding:30px 24px;border-radius:20px}.security-panel h2{font-size:32px}.cta-box{padding:36px 26px;align-items:flex-start;flex-direction:column}.cta-box h2{font-size:32px}.footer-shell{align-items:flex-start;flex-direction:column}.launch-cta{padding:16px 18px 72px}
@@ -176,7 +178,9 @@
</a>
<div class="nav-links">
<a href="{{ route('home') }}">Product</a>
<a href="{{ route('features') }}">Features</a>
<a href="{{ route('buy') }}">Pricing</a>
<a href="{{ route('release') }}">Release</a>
<a href="{{ route('support') }}">Support</a>
<a href="{{ route('privacy') }}">Privacy</a>
@auth
@@ -232,7 +236,9 @@
</div>
<div class="footer-links">
<a href="{{ route('home') }}">Product</a>
<a href="{{ route('features') }}">Features</a>
<a href="{{ route('buy') }}">Pricing</a>
<a href="{{ route('release') }}">Release</a>
<a href="{{ route('support') }}">Support</a>
<a href="{{ route('privacy') }}">Privacy</a>
<a href="{{ route('terms') }}">Terms</a>
+43 -14
View File
@@ -1,19 +1,48 @@
@extends('layouts.app', [
'title' => 'SonarDesk — SonarQube monitoring for macOS',
'description' => 'A professional native macOS dashboard for SonarQube projects, quality gates, bugs, vulnerabilities and code smells.'
'title' => 'SonarDesk — Native SonarQube monitoring for macOS',
'description' => 'SonarDesk is a native macOS companion for SonarQube: monitor quality gates, issues, measures and project health from one focused desktop workspace.'
])
@section('content')
<section class="hero-card" style="padding:64px 0 38px">
<span class="eyebrow">Native SonarQube monitor for macOS</span>
<h1 style="max-width:850px;font-size:58px">Your SonarQube projects.<br>One focused Mac workspace.</h1>
<p class="lead">SonarDesk gives development teams a fast native view of project health, quality gates, bugs, vulnerabilities, code smells and measures without living in a browser.</p>
<div class="support-actions" style="margin-top:24px"><a class="btn btn-primary" href="{{ route('buy') }}">Start free trial</a><a class="btn btn-secondary" href="{{ route('support') }}">Contact support</a></div>
<section class="hero-card marketing-hero">
<span class="eyebrow">SonarQube, without the browser tab sprawl</span>
<h1>Your code quality.<br>At home on the Mac.</h1>
<p class="lead">SonarDesk is a focused native macOS companion for developers and teams using SonarQube. See project health, quality gates, bugs, vulnerabilities, code smells and measures in one fast desktop workspace.</p>
<div class="support-actions marketing-actions">
<a class="btn btn-primary" href="{{ route('buy') }}">Start 7-day free trial</a>
<a class="btn btn-secondary" href="{{ route('features') }}">Explore features</a>
</div>
<p class="microcopy">Direct download for macOS · Secure licence activation · Cancel anytime</p>
</section>
<div class="grid" style="grid-template-columns:repeat(3,minmax(0,1fr));margin:22px 0 44px">
<div class="card"><span class="eyebrow">Projects</span><h2>Everything at a glance</h2><p>Review quality gates and core measures across every configured SonarQube project.</p></div>
<div class="card"><span class="eyebrow">Issues</span><h2>Find what needs work</h2><p>Browse bugs, vulnerabilities and code smells with filtering and server-side pagination.</p></div>
<div class="card"><span class="eyebrow">Security</span><h2>Credentials stay protected</h2><p>SonarQube tokens are stored in the macOS Keychain and API access respects your server permissions.</p></div>
</div>
<section class="panel" style="padding:34px"><div class="portal-header"><div><span class="eyebrow">Built for engineering teams</span><h2 style="font-size:34px;margin:7px 0">A professional desktop companion for SonarQube.</h2><p class="lead compact">Multiple servers, project dashboards, quality gates, issue views, secure token storage and a native macOS experience.</p></div></div></section>
<section style="padding:54px 0 10px"><div class="portal-header"><div><span class="eyebrow">Licensing</span><h2 style="font-size:34px;margin:7px 0">Simple licensing for SonarDesk.</h2><p class="lead compact">Start with a free trial, then manage your licence, activations and subscription from the customer portal.</p></div><a class="btn" href="{{ route('buy') }}">View pricing</a></div></section>
<section class="trust-strip" aria-label="SonarDesk highlights">
<span>Native macOS app</span><span>Multiple SonarQube servers</span><span>Keychain token storage</span><span>Issue detail workflow</span>
</section>
<section class="section-block">
<div class="section-heading"><span class="eyebrow">Built for daily engineering work</span><h2>See the signal. Open the issue. Get back to code.</h2><p>SonarDesk keeps the information you check most often close at hand, without replacing SonarQube or changing your existing workflow.</p></div>
<div class="feature-grid">
<article class="feature-card"><div class="feature-icon">01</div><h3>Project health at a glance</h3><p>Review configured projects, quality gate state and important measures from a clean Mac dashboard.</p></article>
<article class="feature-card"><div class="feature-icon">02</div><h3>Issue details that are useful</h3><p>Open bugs, vulnerabilities and code smells to see the context you need before switching back to the codebase.</p></article>
<article class="feature-card"><div class="feature-icon">03</div><h3>Focused filtering</h3><p>Work through issue lists with practical filters and server-side pagination rather than fighting a giant browser view.</p></article>
<article class="feature-card"><div class="feature-icon">04</div><h3>Multiple servers</h3><p>Keep the SonarQube environments you work with available from one desktop application.</p></article>
<article class="feature-card"><div class="feature-icon">05</div><h3>macOS security</h3><p>SonarQube access tokens are stored in the macOS Keychain. SonarDesk respects the permissions of the token you provide.</p></article>
<article class="feature-card"><div class="feature-icon">06</div><h3>Native workflow</h3><p>A purpose-built macOS interface with familiar navigation, desktop behaviour and a focused experience for engineering teams.</p></article>
</div>
</section>
<section class="section-block split-panel">
<div><span class="eyebrow">Designed to complement SonarQube</span><h2>Keep SonarQube as the source of truth.</h2><p class="lead compact">SonarDesk is a desktop companion, not a replacement. It connects to your SonarQube server using your credentials and presents the project and issue information you need for everyday monitoring.</p></div>
<div class="check-panel"><div>Quality gates and measures</div><div>Bugs, vulnerabilities and code smells</div><div>Project and issue navigation</div><div>Secure token storage in Keychain</div><div>Customer portal for licences and activations</div></div>
</section>
<section class="section-block pricing-preview">
<div class="section-heading"><span class="eyebrow">Straightforward pricing</span><h2>Start small. Upgrade when you need more Macs.</h2><p>Every plan includes the full SonarDesk application. Professional increases the activation allowance for people working across multiple Macs.</p></div>
<div class="pricing-preview-grid">
<article class="price-card"><span class="eyebrow">Personal</span><h3>For one Mac</h3><div class="marketing-price">£24.99 <small>/ year</small></div><p>or £2.99 monthly</p><ul class="features"><li>Full SonarDesk feature set</li><li>1 active Mac</li><li>7-day free trial</li><li>Customer licence portal</li></ul></article>
<article class="price-card featured"><span class="pill">Most popular</span><span class="eyebrow">Professional</span><h3>For a multi-Mac workflow</h3><div class="marketing-price">£44.99 <small>/ year</small></div><p>or £4.99 monthly</p><ul class="features"><li>Full SonarDesk feature set</li><li>Up to 3 active Macs</li><li>7-day free trial</li><li>Customer licence portal</li></ul></article>
</div>
<div class="center-action"><a class="btn btn-primary" href="{{ route('buy') }}">See pricing and start trial</a></div>
</section>
<section class="cta-panel"><div><span class="eyebrow">SonarDesk for macOS</span><h2>Make SonarQube easier to keep an eye on.</h2><p>Try SonarDesk for seven days and connect it to your existing SonarQube environment.</p></div><a class="btn btn-primary" href="{{ route('buy') }}">Start free trial</a></section>
@endsection
+16
View File
@@ -0,0 +1,16 @@
@extends('layouts.app', ['title' => 'SonarDesk 1.0 — Release', 'description' => 'SonarDesk 1.0 for macOS: a native desktop companion for SonarQube project health, quality gates and issues.'])
@section('content')
<section class="hero-card marketing-hero compact-hero"><span class="eyebrow">Introducing SonarDesk 1.0</span><h1>SonarQube monitoring, built for the Mac.</h1><p class="lead">SonarDesk brings project health, quality gates, bugs, vulnerabilities, code smells and measures into a dedicated native macOS workspace.</p><div class="support-actions marketing-actions"><a class="btn btn-primary" href="{{ route('buy') }}">Start free trial</a><a class="btn btn-secondary" href="{{ route('features') }}">Explore features</a></div></section>
<section class="release-body">
<div class="release-main">
<span class="eyebrow">Version 1.0</span><h2>Why SonarDesk</h2>
<p>SonarQube is an essential part of many development workflows, but checking it throughout the day often means returning to another browser tab, navigating back to a project and rebuilding context. SonarDesk is designed to make that recurring check faster and more focused on macOS.</p>
<p>It does not replace SonarQube. Your SonarQube server remains the source of truth. SonarDesk gives you a dedicated desktop view of the information you monitor most often.</p>
<h2>What is included</h2>
<ul class="release-list"><li>Project dashboards and project health views</li><li>Quality gate status and important measures</li><li>Issue browsing for bugs, vulnerabilities and code smells</li><li>Detailed issue views and practical filtering</li><li>Server-side pagination for larger issue sets</li><li>Support for multiple SonarQube server configurations</li><li>SonarQube token storage using macOS Keychain</li><li>Direct licensing with a self-service customer portal</li></ul>
<h2>Who it is for</h2><p>SonarDesk is for developers, technical leads and engineering teams who already use SonarQube and want a faster, dedicated way to keep project quality visible while working on a Mac.</p>
<h2>Launch pricing</h2><p>Personal is £24.99/year or £2.99/month for one active Mac. Professional is £44.99/year or £4.99/month for up to three active Macs. Both include a 7-day free trial and the complete SonarDesk feature set.</p>
</div>
<aside class="release-aside"><div class="card"><span class="eyebrow">At a glance</span><h3>SonarDesk 1.0</h3><p>Native macOS application</p><p>Direct distribution</p><p>7-day free trial</p><p>Personal + Professional plans</p><a class="btn btn-primary full-width" href="{{ route('buy') }}">Try SonarDesk</a></div></aside>
</section>
@endsection
+2
View File
@@ -16,6 +16,8 @@ use Illuminate\Support\Facades\Route;
Route::get('/', fn () => view('portal.welcome'))->name('home');
Route::get('/buy', CheckoutController::class)->name('buy');
Route::view('/features', 'features')->name('features');
Route::view('/release', 'release')->name('release');
Route::get('/support', [SupportController::class, 'create'])->name('support');
Route::post('/support', [SupportController::class, 'store'])->middleware('throttle:5,10')->name('support.store');
Route::view('/privacy', 'legal.privacy')->name('privacy');
+20 -20
View File
@@ -134,10 +134,10 @@ PERSONAL_MONTHLY=$(create_price \
"Personal Monthly" \
"SonarDesk Personal — monthly subscription with 7-day free trial." \
"month" \
"199" \
"199" \
"199" \
"299")
"299" \
"299" \
"349" \
"499")
PERSONAL_MONTHLY_ID=$(echo "$PERSONAL_MONTHLY" | jq -r '.data.id')
@@ -148,10 +148,10 @@ PERSONAL_ANNUAL=$(create_price \
"Personal Annual" \
"SonarDesk Personal — annual subscription with 7-day free trial." \
"year" \
"1499" \
"1499" \
"1699" \
"2499")
"2499" \
"2499" \
"2899" \
"4199")
PERSONAL_ANNUAL_ID=$(echo "$PERSONAL_ANNUAL" | jq -r '.data.id')
@@ -174,10 +174,10 @@ PRO_MONTHLY=$(create_price \
"Professional Monthly" \
"SonarDesk Professional — monthly subscription with 7-day free trial." \
"month" \
"399" \
"399" \
"449" \
"699")
"499" \
"499" \
"599" \
"849")
PRO_MONTHLY_ID=$(echo "$PRO_MONTHLY" | jq -r '.data.id')
@@ -188,10 +188,10 @@ PRO_ANNUAL=$(create_price \
"Professional Annual" \
"SonarDesk Professional — annual subscription with 7-day free trial." \
"year" \
"2999" \
"2999" \
"3499" \
"4999")
"4499" \
"4499" \
"5199" \
"7499")
PRO_ANNUAL_ID=$(echo "$PRO_ANNUAL" | jq -r '.data.id')
@@ -219,13 +219,13 @@ echo
printf "%-24s %-12s %-12s %-12s %-12s\n" \
"Plan" "USD" "UK" "Ireland" "Australia"
printf "%-24s %-12s %-12s %-12s %-12s\n" \
"Personal Monthly" '$1.99' '£1.99' '€1.99' 'A$2.99'
"Personal Monthly" '$2.99' '£2.99' '€3.49' 'A$4.99'
printf "%-24s %-12s %-12s %-12s %-12s\n" \
"Personal Annual" '$14.99' '£14.99' '€16.99' 'A$24.99'
"Personal Annual" '$24.99' '£24.99' '€28.99' 'A$41.99'
printf "%-24s %-12s %-12s %-12s %-12s\n" \
"Professional Monthly" '$3.99' '£3.99' '€4.49' 'A$6.99'
"Professional Monthly" '$4.99' '£4.99' '€5.99' 'A$8.49'
printf "%-24s %-12s %-12s %-12s %-12s\n" \
"Professional Annual" '$29.99' '£29.99' '€34.99' 'A$49.99'
"Professional Annual" '$44.99' '£44.99' '€51.99' 'A$74.99'
echo
echo "All four prices include a 7-day FREE trial."
echo
+296
View File
@@ -0,0 +1,296 @@
# pdl_sdbx_apikey_01m3pv5nbpjq94nh2hzqwhzbv5_TW7kG6xyGMcbxFAnEAtKqc_ALK
#client side
# pdl_sdbx_apikey_01m3pveweeskjzz1rc8f12a7x3_G9qWxtDBxGZJWa3f7VGKz6_A15
#!/usr/bin/env bash
set -euo pipefail
PADDLE_API="https://sandbox-api.paddle.com"
PRODUCT_ID="pro_01m3ptwmqy8npywsq4h4q27p0w"
OUTPUT_FILE=".env.paddle"
echo "============================================================"
echo " SonarDesk Paddle SANDBOX Price Setup"
echo "============================================================"
echo
echo "Product: $PRODUCT_ID"
echo
command -v curl >/dev/null || {
echo "ERROR: curl is required"
exit 1
}
command -v jq >/dev/null || {
echo "ERROR: jq is required"
echo "Install with: brew install jq"
exit 1
}
read -rsp "Enter Paddle SANDBOX API key: " PADDLE_API_KEY
echo
echo
if [[ -z "$PADDLE_API_KEY" ]]; then
echo "ERROR: No API key supplied."
exit 1
fi
# ------------------------------------------------------------
# API request helper
# ------------------------------------------------------------
api_request() {
local method="$1"
local endpoint="$2"
local data="${3:-}"
local tmp
tmp="$(mktemp)"
if [[ "$method" == "GET" ]]; then
HTTP_CODE=$(curl -sS \
-o "$tmp" \
-w "%{http_code}" \
-H "Authorization: Bearer ${PADDLE_API_KEY}" \
-H "Content-Type: application/json" \
"${PADDLE_API}${endpoint}")
else
HTTP_CODE=$(curl -sS \
-o "$tmp" \
-w "%{http_code}" \
-X "$method" \
-H "Authorization: Bearer ${PADDLE_API_KEY}" \
-H "Content-Type: application/json" \
-d "$data" \
"${PADDLE_API}${endpoint}")
fi
RESPONSE="$(cat "$tmp")"
rm -f "$tmp"
}
# ------------------------------------------------------------
# Verify API key
# ------------------------------------------------------------
echo "1. Testing Paddle API key..."
api_request GET "/event-types"
if [[ "$HTTP_CODE" != "200" ]]; then
echo
echo "ERROR: Paddle rejected the API key."
echo "HTTP: $HTTP_CODE"
echo
echo "$RESPONSE" | jq . 2>/dev/null || echo "$RESPONSE"
exit 1
fi
echo " ✓ API key works"
echo
# ------------------------------------------------------------
# Verify product
# ------------------------------------------------------------
echo "2. Checking SonarDesk product..."
api_request GET "/products/${PRODUCT_ID}"
if [[ "$HTTP_CODE" != "200" ]]; then
echo
echo "ERROR: Cannot access SonarDesk product."
echo "HTTP: $HTTP_CODE"
echo
echo "$RESPONSE" | jq . 2>/dev/null || echo "$RESPONSE"
exit 1
fi
PRODUCT_NAME="$(echo "$RESPONSE" | jq -r '.data.name')"
echo " ✓ Product found: $PRODUCT_NAME"
echo " ✓ $PRODUCT_ID"
echo
# ------------------------------------------------------------
# Create price
# ------------------------------------------------------------
create_price() {
local variable="$1"
local name="$2"
local description="$3"
local amount="$4"
local interval="$5"
local display="$6"
echo "Creating: $name"
echo " $display"
PAYLOAD=$(jq -n \
--arg product "$PRODUCT_ID" \
--arg name "$name" \
--arg description "$description" \
--arg amount "$amount" \
--arg interval "$interval" \
'{
product_id: $product,
name: $name,
description: $description,
unit_price: {
amount: $amount,
currency_code: "GBP"
},
billing_cycle: {
interval: $interval,
frequency: 1
},
trial_period: {
interval: "day",
frequency: 7,
requires_payment_method: true
},
tax_mode: "account_setting",
quantity: {
minimum: 1,
maximum: 1
}
}')
echo
echo "Request:"
echo "$PAYLOAD" | jq .
echo
api_request POST "/prices" "$PAYLOAD"
if [[ "$HTTP_CODE" != "201" ]]; then
echo "============================================================"
echo " ERROR CREATING PRICE"
echo "============================================================"
echo
echo "Price: $name"
echo "HTTP: $HTTP_CODE"
echo
echo "Paddle response:"
echo
echo "$RESPONSE" | jq . 2>/dev/null || echo "$RESPONSE"
echo
exit 1
fi
PRICE_ID="$(echo "$RESPONSE" | jq -r '.data.id')"
if [[ -z "$PRICE_ID" || "$PRICE_ID" == "null" ]]; then
echo "ERROR: Paddle returned no price ID."
echo "$RESPONSE" | jq .
exit 1
fi
printf -v "$variable" '%s' "$PRICE_ID"
echo " ✓ CREATED"
echo " ✓ $PRICE_ID"
echo
}
# ------------------------------------------------------------
# Create SonarDesk prices
# ------------------------------------------------------------
echo "============================================================"
echo " Creating SonarDesk Prices"
echo "============================================================"
echo
create_price \
PERSONAL_MONTHLY \
"Personal Monthly" \
"SonarDesk Personal - Monthly subscription" \
"299" \
"month" \
"£2.99/month"
create_price \
PERSONAL_ANNUAL \
"Personal Annual" \
"SonarDesk Personal - Annual subscription" \
"2499" \
"year" \
"£24.99/year"
create_price \
PROFESSIONAL_MONTHLY \
"Professional Monthly" \
"SonarDesk Professional - Monthly subscription" \
"499" \
"month" \
"£4.99/month"
create_price \
PROFESSIONAL_ANNUAL \
"Professional Annual" \
"SonarDesk Professional - Annual subscription" \
"4499" \
"year" \
"£44.99/year"
# ------------------------------------------------------------
# Generate Laravel configuration
# ------------------------------------------------------------
cat > "$OUTPUT_FILE" <<EOF
# ============================================================
# SonarDesk Paddle SANDBOX
# ============================================================
PADDLE_ENV=sandbox
PADDLE_PRODUCT_ID=${PRODUCT_ID}
PADDLE_PERSONAL_MONTHLY_PRICE_ID=${PERSONAL_MONTHLY}
PADDLE_PERSONAL_YEARLY_PRICE_ID=${PERSONAL_ANNUAL}
PADDLE_PROFESSIONAL_MONTHLY_PRICE_ID=${PROFESSIONAL_MONTHLY}
PADDLE_PROFESSIONAL_YEARLY_PRICE_ID=${PROFESSIONAL_ANNUAL}
EOF
echo
echo "============================================================"
echo " SUCCESS"
echo "============================================================"
echo
echo "SonarDesk Paddle prices:"
echo
echo "Personal Monthly"
echo " £2.99/month"
echo " $PERSONAL_MONTHLY"
echo
echo "Personal Annual"
echo " £24.99/year"
echo " $PERSONAL_ANNUAL"
echo
echo "Professional Monthly"
echo " £4.99/month"
echo " $PROFESSIONAL_MONTHLY"
echo
echo "Professional Annual"
echo " £44.99/year"
echo " $PROFESSIONAL_ANNUAL"
echo
echo "Configuration:"
echo " $OUTPUT_FILE"
echo
echo "Now check:"
echo " Paddle Sandbox -> Catalog -> Products -> SonarDesk"
echo
-2
View File
@@ -1,2 +0,0 @@
*
!.gitignore