Paypal

PayPal supports subscriptions and invoice previews. Use this page if you prefer PayPal and do not need a customer portal.

Configuration

<?php

return [
  'bnomei.klub.provider.handler' => \Bnomei\Klub\Provider\PayPalProvider::class,
  'bnomei.klub.providers.paypal' => [
    'client_id' => fn() => env('PAYPAL_CLIENT_ID'),
    'secret' => fn() => env('PAYPAL_SECRET'),
    'environment' => fn() => env('PAYPAL_ENV', 'sandbox'),
    'webhook_id' => fn() => env('PAYPAL_WEBHOOK_ID'),
    'cancel_behavior' => 'suspend',
    'field' => 'paypal',
  ],
];

Environment variables:

  • PAYPAL_CLIENT_ID
  • PAYPAL_SECRET
  • PAYPAL_ENV (sandbox, live, or production)
  • PAYPAL_WEBHOOK_ID

Optional provider keys:

  • cancel_behavior (suspend or cancel)
  • checkout for provider-specific payload overrides

Webhooks

Configure webhooks to POST /klub/webhooks/paypal.

PayPal requires the verification headers PayPal-Transmission-Id, PayPal-Transmission-Time, PayPal-Transmission-Sig, PayPal-Cert-Url, and PayPal-Auth-Algo. Klub validates them using PAYPAL_WEBHOOK_ID.

Portal and invoices

  • Portal: not supported.
  • Invoice preview: supported and links to the subscription transaction details.

Plan data

PayPal uses local plans (providers.paypal.plans or site plans YAML):

  • Subscriptions: set id to your PayPal plan ID (P-...) and use the normal subscription route.
  • One-time payments: set type: one_time and provide unit_amount + currency so Orders payload amounts are generated correctly.

Gotchas

  • One-time checkouts need unit_amount and currency in local plan data.
  • Webhook verification requires PAYPAL_WEBHOOK_ID.

Orders custom_id

For one-time Orders checkouts, Klub sets custom_id to {userId}|{priceId} so webhooks can resolve both the user and the price even if the buyer never returns. If no user is available, custom_id is just the priceId.

Avoid using | in price IDs.

Cancel behavior

PayPal supports soft cancel (suspend) and hard cancel. Klub defaults to suspend so users can resume.

  • Set bnomei.klub.providers.paypal.cancel_behavior to cancel for hard cancellations.
  • POST /klub/renew/{subscriptionId} only resumes suspended subscriptions. Cancelled or expired subscriptions must be restarted via checkout.

See Purchases for flow details.

Kirby Klub is not affiliated with the developers of Kirby CMS. We are merely standing on the shoulder of giants.
© 2026 Bruno Meilick All rights reserved.