PayPal Payment Gateway

Accept payments with PayPal on your Larapen e-shop. This add-on integrates the PayPal REST API to create orders, capture payments, handle webhooks, and process refunds: all through the standard Larapen payment gateway interface.

PayPal Checkout

Redirect customers to PayPal’s hosted checkout. No credit card form needed on your site.

Sandbox & Live

Switch between sandbox (testing) and live (production) modes from the admin panel with a single toggle.

Webhook Support

Receive real-time payment notifications via PayPal webhooks for capture, denial, and refund events.

Refund Processing

Issue full or partial refunds directly through the gateway. Refund transactions are tracked automatically.

Encrypted Credentials

API keys and secrets are encrypted before storage using Laravel’s Crypt facade.

Polymorphic Payables

Works with any model implementing the Payable contract: not limited to shop orders.

Use Cases

Online Store with PayPal Checkout

You run an e-commerce store using the Larapen Shop add-on and want to offer PayPal as a payment option alongside other gateways (e.g. Stripe).

  • Install and activate the PayPal add-on.
  • Enter your PayPal REST API credentials in the admin panel.
  • Customers see PayPal as a payment method during checkout.
  • On selection, they are redirected to PayPal to complete payment, then returned to your site.

Digital Product Sales

You sell digital downloads (e-books, software licenses, templates) and want secure, instant payment confirmation.

  • PayPal webhooks confirm payment in real time, even if the customer closes the browser before returning.
  • The shop marks the order as paid and unlocks digital download links automatically.

Multi-Currency Store

You sell to international customers in multiple currencies.

  • Configure the default PayPal currency in admin settings (USD, EUR, GBP, etc.).
  • Each order sends the correct currency code to PayPal based on the shop configuration.

Requirements

  • Larapen CMS v1.0.0 or later
  • PHP 8.3+
  • MySQL 8.0+
  • The Shop add-on (required dependency)
  • A PayPal Business account with REST API credentials
  • The srmklive/paypal Composer package (PayPal SDK)
Note: The Shop add-on must be installed and active before this add-on can function. The PayPal add-on registers itself as a payment gateway that the shop discovers automatically.

Installation

Step 1: Place the Add-on

Copy or symlink the paypal folder into your Larapen "extensions/addons" directory:

Step 2: Install Dependencies

Ensure the PayPal SDK package is installed:

Step 3: Activate the Add-on

Go to Admin → Add-ons → Installed Add-ons and activate PayPal Payment Gateway.

Step 4: Run Migrations

This creates the paypal_orders table for tracking PayPal order records, captures, and payment metadata.

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 → PayPal → Settings and enter your PayPal API credentials. See Configuration and Getting PayPal Credentials.

Configuration

All settings are managed in Admin → PayPal → Settings (stored in the settings table, group paypal).

Setting Description Default
paypal_mode API mode: sandbox for testing or live for production payments. sandbox
paypal_client_id PayPal REST API Client ID. Stored encrypted in the database. (empty)
paypal_client_secret PayPal REST API Client Secret. Stored encrypted in the database. (empty)
paypal_webhook_id PayPal Webhook ID for verifying incoming webhook event signatures. Stored encrypted. (empty)
paypal_currency ISO 4217 currency code used for PayPal transactions (e.g. USD, EUR, GBP). USD
paypal_brand_name Brand name displayed on the PayPal checkout page (max 127 characters). (app name)

Database Settings → Config Mapping

Settings stored in the database override the config file defaults at boot time via the service provider:

Database Key Config Key Encrypted?
paypal_mode paypal.mode No
paypal_client_id paypal.{mode}.client_id Yes
paypal_client_secret paypal.{mode}.client_secret Yes
paypal_webhook_id paypal.webhook_id Yes
paypal_currency paypal.currency No
paypal_brand_name paypal.brand_name No
Note: The paypal_client_id and paypal_client_secret are stored against the currently active mode. If mode is sandbox, they map to paypal.sandbox.client_id and paypal.sandbox.client_secret.

Environment Variables

Note: Environment variables are used as defaults. Settings saved in the admin panel (stored encrypted in the database) override them.

Getting PayPal Credentials

  1. Go to developer.paypal.com/dashboard and log in with your PayPal Business account.
  2. Navigate to Apps & Credentials.
  3. Click Create App (or select an existing app).
  4. Copy the Client ID and Client Secret from the app details page.
  5. For sandbox testing, toggle to the Sandbox tab to get sandbox credentials.
  6. For webhooks, go to Webhooks in the dashboard, create a webhook pointing to https://yoursite.com/paypal/webhook, and copy the Webhook ID.

Required Webhook Events

When creating your PayPal webhook, subscribe to these events:

  • PAYMENT.CAPTURE.COMPLETED: payment was successfully captured
  • PAYMENT.CAPTURE.DENIED: payment capture was denied
  • PAYMENT.CAPTURE.REFUNDED: a refund was processed
Important: The webhook URL must be publicly accessible over HTTPS. Sandbox webhooks require a live URL (not localhost). Use a tunnel service like ngrok for local testing.

Admin: Settings

The settings page (PayPal → Settings) is organized into two sections:

API Credentials

  • Client ID: masked password field with show/hide toggle. Your PayPal REST API Client ID.
  • Client Secret: masked password field with show/hide toggle. Stored encrypted in the database.
  • Webhook ID: masked password field with show/hide toggle. Used for verifying webhook signatures. Optional but recommended.
Security: All three credential fields are encrypted with Laravel’s Crypt::encryptString() before being saved to the settings table. They are decrypted only when displayed in the form or when configuring the PayPal API client. Leave fields empty to keep current values.

Payment Options

  • Mode: dropdown to select Sandbox (Testing) or Live (Production). Controls which set of API credentials is used.
  • Currency: three-character ISO 4217 currency code (e.g. USD, EUR, GBP). Used as the default currency for PayPal orders.
  • Brand Name: the name shown on the PayPal checkout page (max 127 characters). Falls back to the application name if empty.

A help card at the top of the settings page provides direct links to:

Checkout Flow

The PayPal payment flow follows the standard redirect-based checkout pattern:

  1. Customer selects PayPal: during shop checkout, the customer chooses PayPal as their payment method.
  2. Order creation: the shop calls PaypalGateway::createPaymentIntent($order), which creates a PayPal order via the REST API with CAPTURE intent.
  3. Local record: a record is saved to the paypal_orders table with the PayPal Order ID, amount, currency, approval URL, and the polymorphic payable reference.
  4. Redirect to PayPal: the customer is redirected to PayPal’s hosted checkout page (the approval_url from the API response).
  5. Customer approves: the customer logs into PayPal, reviews the order, and clicks “Pay”.
  6. Return to site: PayPal redirects the customer back to /paypal/return?token={paypal_order_id}.
  7. Payment capture: the PaypalController::return() method calls PaypalGateway::confirmPayment() to capture the authorized payment.
  8. Order completion: if capture succeeds, the order is marked as paid, a transaction record is created, and the customer is redirected to the success page.

Cancellation

If the customer clicks “Cancel” on the PayPal checkout page, they are redirected to /paypal/cancel. The controller redirects them back to the shop checkout page with a “Payment cancelled” warning message.

Dual confirmation: Payments are confirmed both by the return redirect (immediate) and by webhooks (asynchronous). This ensures orders are marked as paid even if the customer closes their browser before the return redirect completes.

Payment Confirmation

When a payment is captured successfully, the gateway performs these actions:

  1. Updates the paypal_orders record with: status = COMPLETED, capture_id, payer_id, payer_email, and confirmed_at.
  2. Calls $payable->markAsPaid('paypal', $captureId) on the order model.
  3. Creates a Transaction record in the shop_transactions table with:
    • gateway = 'paypal'
    • gateway_transaction_id = {capture_id}
    • status = 'completed'
    • type = 'payment'
    • Metadata including paypal_order_id, payer_id, and payer_email

Refunds

The gateway supports full and partial refunds via PaypalGateway::refund().

Refund Process

  1. The admin initiates a refund from the shop order management.
  2. The gateway calls PayPal’s refundCapturedPayment() API using the original capture ID.
  3. If successful, a new Transaction record is created with type = 'refund'.
  4. The order status is updated if the refund covers the full amount.

Refund Statuses

Status Description
COMPLETED Refund processed immediately.
PENDING Refund is pending (e.g. eCheck payments). A PAYMENT.CAPTURE.REFUNDED webhook will confirm it later.
Note: Partial refunds are supported. You can refund any amount up to the original payment amount. An optional reason/note can be included, which PayPal displays to the buyer.

Webhook Setup

PayPal webhooks provide asynchronous payment event notifications. They serve as a safety net to confirm payments even when the customer’s return redirect fails.

Webhook URL

Configure PayPal to send webhook events to:

This endpoint is CSRF-exempt and does not require authentication.

Configuration

  1. Go to PayPal Developer Dashboard → Webhooks.
  2. Click Add Webhook.
  3. Enter your webhook URL.
  4. Select the three required events (see below).
  5. Copy the generated Webhook ID and paste it in Admin → PayPal → Settings.

Updating

Step 1: Replace Files

Replace the add-on directory with the new version.

Step 2: Run Migrations

Step 3: Clear Caches

Step 4: Verify

Visit PayPal → Settings and confirm your API credentials are still configured. Try a sandbox test payment to ensure everything works correctly.

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

Troubleshooting

“PayPal API credentials are not configured”

  • Ensure you have entered both the Client ID and Client Secret in Admin → PayPal → Settings.
  • Verify the correct Mode is selected: sandbox credentials do not work in live mode and vice versa.
  • Check that the credentials are for the correct mode (sandbox vs. live).

“PayPal authentication failed”

  • Double-check that the Client ID and Client Secret are correct (no extra spaces or line breaks).
  • Ensure your PayPal app is not suspended or deleted.
  • Verify your server can reach api-m.sandbox.paypal.com (sandbox) or api-m.paypal.com (live) over HTTPS.
  • Check server logs for detailed error messages from the PayPal API.

Customer redirected to checkout but payment not captured

  • The return URL may not have been reached (customer closed browser). Check if the webhook received a PAYMENT.CAPTURE.COMPLETED event.
  • Ensure the webhook URL is correctly configured in the PayPal Developer Dashboard.
  • Verify the paypal_orders record was created (check the status column).

Webhooks not being received

  • Verify the webhook URL is publicly accessible over HTTPS.
  • Check the PayPal Developer Dashboard → Webhooks → Events for delivery status.
  • Ensure the webhook is not behind IP-based firewall rules that block PayPal’s servers.
  • For local development, use a tunnel service (e.g. ngrok) to expose your local server.

Webhook signature verification failing

  • Ensure the Webhook ID in admin settings matches the one in the PayPal Developer Dashboard.
  • If you recently recreated the webhook, update the Webhook ID in your settings.
  • Leave the Webhook ID empty to disable signature verification (not recommended for production).

Refund fails: “Refund failed”

  • Ensure the original payment was captured (status COMPLETED).
  • Check that the refund amount does not exceed the original payment amount.
  • PayPal may reject refunds for payments older than 180 days.
  • Check server logs for the specific PayPal API error message.

Orders stuck in CREATED or APPROVED status

  • The customer may have approved the payment but the capture failed. Check server logs for errors during confirmPayment().
  • Try processing the capture manually via the PayPal Merchant Dashboard.
  • Ensure the srmklive/paypal package is up to date.

“Array to string conversion” errors

  • This typically occurs when the srmklive/paypal library receives unexpected config keys. The gateway filters config to only pass supported keys. Ensure you are using a compatible version of the package.
  • Clear the config cache: php artisan config:clear

Was this article helpful?

Thank you for your feedback!

Still need help? Create a support ticket

Create a Ticket