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
Note: The add-on depends on the Shop add-on. It cannot be activated without the Shop add-on being installed and active first.

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

  1. Go to your Paddle Dashboard → Developer Tools → Notifications.
  2. Create a new notification destination.
  3. Set the URL to https://yoursite.com/paddle/webhook.
  4. Select these events: transaction.completed, transaction.payment_failed, transaction.updated, adjustment.created, adjustment.updated.
  5. 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
Security: The API key, client token, and webhook secret are encrypted using Laravel’s 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.

Note: Environment variables are used as fallback defaults. Settings saved in the admin panel take priority and override them.

Getting Paddle Credentials

API Key & Client-Side Token

  1. Log in to your Paddle Dashboard (or Sandbox Dashboard for testing).
  2. Navigate to Developer Tools → Authentication.
  3. Copy your API Key (used for server-side API calls).
  4. Copy your Client-Side Token (used for Paddle.js initialization).

Seller ID

  1. In the Paddle Dashboard, your Seller ID is displayed in the account settings or URL.
  2. Copy the numeric ID and enter it in the admin settings.

Webhook Secret

  1. Go to Developer Tools → Notifications in the Paddle Dashboard.
  2. Create a new notification destination with the URL https://yoursite.com/paddle/webhook.
  3. Select the events to subscribe to (see Supported Events).
  4. Copy the generated webhook secret and enter it in admin settings.
Important: Sandbox and production environments use different credentials. Sandbox client tokens start with 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.
Note: Leave credential fields empty to keep current stored values. All credentials are encrypted before being stored in the database. Never share your API key.

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) or production (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:

  1. 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.”
  2. Form submission (AJAX): When the customer submits the checkout form, JavaScript intercepts the submission and sends it via fetch() with JSON headers.
  3. 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_transactions record 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.
  4. Paddle.js overlay opens: The JavaScript opens the Paddle checkout overlay using Paddle.Checkout.open() with the transaction ID.
  5. Customer completes payment: The customer enters payment details within the Paddle overlay.
  6. Checkout completed callback: On checkout.completed, the JavaScript redirects to the return URL (/paddle/return?_ptxn={transaction_id}).
  7. Return URL confirmation: The PaddleController::return() method calls confirmPayment() 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.
  8. Webhook confirmation: Paddle also sends a transaction.completed webhook 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:

  1. Parse the ts (timestamp) and h1 (hash) values from the header.
  2. Compute HMAC-SHA256(timestamp + ':' + rawBody, webhookSecret).
  3. Compare the computed hash with the received h1 value using hash_equals().
  4. If the signature is invalid or the webhook secret is empty, return a 400 response.
Important: The webhook secret must be configured for signature verification to work. Without it, all webhook requests will be rejected with an “Invalid signature” error.

Refunds

The add-on supports full and partial refunds via the Paddle Adjustments API.

How Refunds Work

  1. An admin initiates a refund from the shop order management page.
  2. 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_transactions record of type refund.
  3. Paddle processes the refund and sends an adjustment.created webhook.
  4. The webhook handler updates the order status to refunded if 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.

Backup first: Always back up your database before running migrations on a production system.

Troubleshooting

Paddle checkout overlay does not open

  • Ensure the paddle_client_token is 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 with live_.
  • Make sure Paddle.js is loading: check that https://cdn.paddle.com/paddle/v2/paddle.js is 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_secret is set and matches the secret in your Paddle Dashboard.
  • Check storage/logs/laravel.log for webhook-related errors.
  • In the Paddle Dashboard under Notifications, check if webhook deliveries are failing.

Webhook returns 400: “Invalid signature”

  • The paddle_webhook_secret in 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_key is correct and has not been revoked.
  • Ensure the paddle_seller_id is 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_key and paddle_seller_id are 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_customers record.
  • 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_KEY has changed, previously encrypted values cannot be decrypted.
  • Re-enter all credentials after an APP_KEY change.
  • Check that the settings table is writable.

Was this article helpful?

Thank you for your feedback!

Still need help? Create a support ticket

Create a Ticket