Paddle Payment Gateway
Accept payments through Paddle: a merchant of record platform that handles
payments, tax, and compliance. Integrates with the Larapen Shop add-on via the
PaymentGatewayInterface contract.
Paddle Checkout Overlay
Customers pay via Paddle’s embedded checkout overlay powered by Paddle.js v2. No redirect away from your site.
Merchant of Record
Paddle handles sales tax, VAT, and compliance worldwide. You receive net payouts.
Webhook-Driven
Order status updates are confirmed via signed webhooks for reliable payment processing.
Refund Support
Process full or partial refunds directly from the admin panel via the Paddle Adjustments API.
Use Cases
Digital Product Sales
You sell digital products (themes, plugins, e-books) through the Larapen Shop and want Paddle to handle global tax compliance and payouts.
- Install the Paddle add-on alongside the Shop add-on.
- Configure your Paddle API credentials in the admin panel.
- Customers select Paddle at checkout and pay via the overlay.
- Paddle collects tax, processes payment, and sends you net revenue.
Physical or Mixed Product Store
You sell physical goods or a mix of physical and digital products and want a reliable payment gateway.
- Paddle supports credit/debit cards, PayPal, Apple Pay, Google Pay, and local payment methods.
- Orders are confirmed via webhooks, ensuring no lost payments even if the customer closes their browser.
Sandbox Testing
Use Paddle’s sandbox environment for development and testing before going live.
- Set the environment to “Sandbox” in admin settings.
- Use Paddle’s test card numbers to simulate payments.
- Switch to “Production” when ready for live payments.
Requirements
- Larapen CMS v1.0.0 or later
- PHP 8.3+
- MySQL 8.0+
- The Shop add-on must be installed and active (declared dependency)
- A Paddle account: paddle.com
- Paddle PHP SDK (
paddle/paddle-php-sdk) installed via Composer
Installation
Step 1: Place the Add-on
Copy or symlink the paddle folder into your Larapen "extensions/addons" directory:
Step 2: Install the Paddle PHP SDK
Step 3: Activate the Add-on
Go to Admin → Add-ons → Installed Add-ons and activate Paddle Payment Gateway.
Step 4: Run Migrations
This creates 2 tables: paddle_customers and paddle_transactions.
Step 5: Set Permissions
The add-on registers 2 permissions (see Permissions). Assign them to admin roles via Admin → Users → Roles & Permissions.
Step 6: Configure
Navigate to Admin → Paddle → Settings and enter your API key, client-side token, seller ID, and webhook secret. See Configuration and Getting Paddle Credentials.
Step 7: Set Up Webhooks in Paddle
- Go to your Paddle Dashboard → Developer Tools → Notifications.
- Create a new notification destination.
- Set the URL to
https://yoursite.com/paddle/webhook. - Select these events:
transaction.completed,transaction.payment_failed,transaction.updated,adjustment.created,adjustment.updated. - Copy the webhook secret and paste it in admin settings.
Configuration
All settings are managed in Admin → Paddle → Settings (stored in the settings table, group paddle).
| Setting | Description | Default |
|---|---|---|
paddle_api_key |
Paddle API key for server-side API calls (transactions, refunds). Encrypted at rest. | (empty) |
paddle_client_token |
Paddle client-side token for initializing Paddle.js on the frontend. Encrypted at rest. Sandbox tokens start with test_, live tokens start with live_. |
(empty) |
paddle_webhook_secret |
Secret key for verifying Paddle webhook signatures (HMAC-SHA256). Encrypted at rest. | (empty) |
paddle_environment |
Paddle environment: sandbox for testing, production for live payments. |
sandbox |
paddle_seller_id |
Your Paddle seller/vendor ID. Required for Paddle.js initialization. | (empty) |
paddle_currency |
Currency code (e.g., USD, EUR, GBP). Should match your shop currency. |
USD |
Crypt::encryptString() before being stored in the database. They are decrypted at runtime
when the PaddleServiceProvider boots.
Environment Variables
Environment variables serve as defaults. Settings saved in the admin panel override them.
Getting Paddle Credentials
API Key & Client-Side Token
- Log in to your Paddle Dashboard (or Sandbox Dashboard for testing).
- Navigate to Developer Tools → Authentication.
- Copy your API Key (used for server-side API calls).
- Copy your Client-Side Token (used for Paddle.js initialization).
Seller ID
- In the Paddle Dashboard, your Seller ID is displayed in the account settings or URL.
- Copy the numeric ID and enter it in the admin settings.
Webhook Secret
- Go to Developer Tools → Notifications in the Paddle Dashboard.
- Create a new notification destination with the URL
https://yoursite.com/paddle/webhook. - Select the events to subscribe to (see Supported Events).
- Copy the generated webhook secret and enter it in admin settings.
test_ and live tokens start with live_.
Make sure your credentials match the selected environment.
Admin: Settings
The settings page (Admin → Paddle → Settings) is organized into two sections.
API Credentials
Three masked password fields with show/hide toggles:
- API Key: Your Paddle API key from Developer Tools > Authentication. Used for all server-side API calls (creating transactions, processing refunds).
- Client-Side Token: Your Paddle client-side token. Used to initialize Paddle.js on the checkout page. Sandbox tokens start with
test_. - Webhook Secret: Used to verify the HMAC-SHA256 signature on incoming webhook requests.
The page also shows an informational panel with direct links to:
- Paddle Dashboard (production and sandbox)
- Developer Tools > Authentication (API keys)
- Developer Tools > Notifications (webhooks)
- Paddle API documentation
- Test card numbers for sandbox testing
Payment Options
- Environment: Dropdown to select
sandbox(testing) orproduction(live). - Seller ID: Text input for your Paddle seller/vendor ID. Required for Paddle.js initialization.
- Currency: 3-character currency code (e.g.,
USD,EUR,GBP). Should match your shop currency.
Payment Flow
The Paddle add-on uses an overlay checkout flow powered by Paddle.js v2. Here is the complete payment lifecycle:
- Customer selects Paddle: On the shop checkout page, the customer selects “Paddle” as their payment method. An informational message appears: “You will be redirected to Paddle’s secure checkout.”
- Form submission (AJAX): When the customer submits the checkout form, JavaScript intercepts
the submission and sends it via
fetch()with JSON headers. - Server creates Paddle transaction: The
PaddleGateway::createPaymentIntent()method:- Creates a non-catalog transaction via the Paddle API with the order amount, currency, and metadata.
- Stores a local
paddle_transactionsrecord linking the Paddle transaction ID to the order. - Links the Paddle customer to the local user (if authenticated).
- Returns the Paddle transaction ID to the frontend.
- Paddle.js overlay opens: The JavaScript opens the Paddle checkout overlay using
Paddle.Checkout.open()with the transaction ID. - Customer completes payment: The customer enters payment details within the Paddle overlay.
- Checkout completed callback: On
checkout.completed, the JavaScript redirects to the return URL (/paddle/return?_ptxn={transaction_id}). - Return URL confirmation: The
PaddleController::return()method callsconfirmPayment()to check the transaction status via the Paddle API. If completed, the order is marked as paid and the customer is redirected to the success page. - Webhook confirmation: Paddle also sends a
transaction.completedwebhook as a reliable backup. This ensures orders are marked as paid even if the customer closes their browser before the return redirect.
Webhook Handling
Webhooks are received at POST /paddle/webhook. This endpoint is excluded from
CSRF protection and web middleware.
Supported Events
| Event | Action |
|---|---|
transaction.completed |
Marks the local transaction as completed, calls markAsPaid() on the payable (order),
and creates a shop_transactions record with payment details. |
transaction.payment_failed |
Marks the local transaction as failed, calls markPaymentFailed() on the payable,
and creates a failed transaction record with the error code. |
adjustment.created |
If the adjustment action is refund and status is approved or completed,
updates the order’s payment status to refunded. |
adjustment.updated |
Same handling as adjustment.created: checks for refund status changes. |
transaction.updated |
Configured in webhook events list but currently handled by the default case (ignored). |
Signature Verification
All incoming webhooks are verified using HMAC-SHA256 signatures. Paddle sends the signature in
the Paddle-Signature HTTP header in the format:
The verification process:
- Parse the
ts(timestamp) andh1(hash) values from the header. - Compute
HMAC-SHA256(timestamp + ':' + rawBody, webhookSecret). - Compare the computed hash with the received
h1value usinghash_equals(). - If the signature is invalid or the webhook secret is empty, return a
400response.
Refunds
The add-on supports full and partial refunds via the Paddle Adjustments API.
How Refunds Work
- An admin initiates a refund from the shop order management page.
- The
PaddleGateway::refund()method:- Retrieves the Paddle transaction to get the item IDs.
- Creates a partial adjustment with
Action::Refund()against the first line item. - Creates a
shop_transactionsrecord of typerefund.
- Paddle processes the refund and sends an
adjustment.createdwebhook. - The webhook handler updates the order status to
refundedif the adjustment is approved.
Refund Statuses
| Status | Description |
|---|---|
approved |
Refund approved and processed immediately. Order marked as refunded. |
pending |
Refund submitted but awaiting Paddle approval. Order status updated when the adjustment.updated webhook arrives. |
rejected |
Refund was rejected by Paddle. No order status change. |
Updating
Step 1: Replace Files
Replace the add-on directory with the new version.
Step 2: Update Composer Dependencies
Step 3: Run Migrations
Step 4: Clear Caches
Step 5: Verify
Visit Admin → Paddle → Settings and confirm your credentials are still configured. Process a test payment in sandbox mode to verify the integration.
Troubleshooting
Paddle checkout overlay does not open
- Ensure the
paddle_client_tokenis configured in admin settings. - Check the browser console for JavaScript errors. A missing or invalid client token will log
Paddle client token not configured. - Verify that the token matches the environment: sandbox tokens start with
test_, production tokens start withlive_. - Make sure Paddle.js is loading: check that
https://cdn.paddle.com/paddle/v2/paddle.jsis not blocked by your Content Security Policy or ad blocker.
Payment not confirming: order stays in “pending”
- Check that your webhook URL (
/paddle/webhook) is accessible from the internet. Paddle must be able to send POST requests to it. - Verify the
paddle_webhook_secretis set and matches the secret in your Paddle Dashboard. - Check
storage/logs/laravel.logfor webhook-related errors. - In the Paddle Dashboard under Notifications, check if webhook deliveries are failing.
Webhook returns 400: “Invalid signature”
- The
paddle_webhook_secretin admin settings does not match the secret from Paddle Dashboard. - If you recently rotated the webhook secret, update it in admin settings.
- Ensure the raw request body is not being modified by middleware before signature verification.
Transaction creation fails: “Paddle transaction creation failed”
- Check that the
paddle_api_keyis correct and has not been revoked. - Ensure the
paddle_seller_idis set. - Verify the currency code is valid and supported by Paddle.
- Check server logs for the full error message from the Paddle API.
- If using sandbox, make sure the API key is a sandbox key (not production).
Refund fails: “Paddle refund failed”
- Check that the original transaction was completed (only completed transactions can be refunded).
- Verify the refund amount does not exceed the original transaction amount.
- Some Paddle adjustments require manual approval: check the Paddle Dashboard.
- Check server logs for the detailed Paddle API error.
Gateway not appearing on checkout page
- Ensure the Paddle add-on is activated in Admin → Add-ons.
- Ensure both
paddle_api_keyandpaddle_seller_idare configured (isAvailable()checks both). - Verify the Shop add-on is active (Paddle depends on it).
Customer not linked to Paddle customer ID
- Customer linking happens automatically during transaction creation when the payable has a user ID.
- Guest checkouts (no authenticated user) will not create a
paddle_customersrecord. - Check server logs for warnings from
linkCustomer().
Settings not saving: credentials appear empty after save
- Credentials are encrypted before storage and decrypted for display. If the
APP_KEYhas changed, previously encrypted values cannot be decrypted. - Re-enter all credentials after an
APP_KEYchange. - Check that the
settingstable is writable.