Laravel Cashier Paddle

repository·2.x·Indexed 42 days ago

https://github.com/laravel/cashier-paddle

A library providing a fluent, expressive interface for integrating Paddle's subscription billing services into Laravel applications. It abstracts subscription lifecycles, including swapping, pausing, quantity management, and trial handling, while providing tools for testing via Cashier::fake() and webhook synchronization.

Tokens
13.2K
Snippets
56
Records
67
Agent score
50%

What's inside Laravel Cashier Paddle

  1. Introduction to Laravel Cashier Paddle

    2.x

    Laravel Cashier Paddle provides an expressive, fluent interface for managing Paddle subscription billing services. It automates the boilerplate code required for subscription management, including:

    • Basic subscription management
    • Swapping subscriptions
    • Managing subscription 'quantities'
    • Pausing subscriptions
    • Handling cancellation grace periods
  2. Extend the Webhook Controller for custom behavior

    2.x

    If you need to add custom logic during webhook processing (e.g., post-processing a subscription creation), extend Laravel//Paddle/Http/Controllers/WebhookController.

    To use your custom controller, you must first disable Cashier's auto-registered route and then define your own route pointing to your controller.

    // 1. Create your custom controller
    use Laravel//Paddle/Http/Controllers/WebhookController as CashierController;
    
    class PaddleWebhookController extends CashierController
    {
        protected function handleSubscriptionCreated(array $payload): void
        {
            parent::handleSubscriptionCreated($payload);
    
            // Additional post-processing
            $subscriptionId = $payload['data']['id'];
            // ...
        }
    }
    
    // 2. Disable auto-registration in AppServiceProvider::boot()
    Cashier::ignoreRoutes();
    
    // 3. Register your custom route in routes/web.php
    Route::post('paddle/webhook', [PaddleWebhookController::class, '__invoke']);
  3. Use Cashier::fake() to intercept API calls and events

    2.x

    Call Cashier::fake() at the beginning of your test to prevent real network requests to Paddle. This method performs two actions simultaneously:

    1. Registers Http::fake() intercepts for all Paddle API endpoints.
    2. Calls Event::fake() for all Cashier events.

    This allows you to seed subscription state manually in your database to test logic like $user->subscribed() without needing actual Paddle interactions.

    use Laravel//Paddle/Cashier;
    
    public function test_user_can_subscribe(): void
    {
        Cashier::fake();
    
        // Seed subscription state that would normally arrive via webhook
        $user = User::factory()->create();
        $user->customer()->create(['paddle_id' => 'cus_test123']);
    
        $user->subscriptions()->create([
            'type'      => 'default',
            'paddle_id' => 'sub_test123',
            'status'    => 'active',
        ])->items()->create([
            'product_id' => 'pro_basic',
            'price_id'   => 'pri_monthly',
            'status'     => 'active',
            'quantity'   => 1,
        ]);
    
        $this->assertTrue($user->subscribed());
        $this->assertFalse($user->subscription()->onTrial());
    }
  4. Exclude Paddle webhooks from CSRF protection

    2.x

    Paddle's POST requests will receive a 419 response and be silently dropped if CSRF protection is active. You must exclude the paddle/* route pattern from CSRF validation.

    Laravel 11+ (bootstrap/app.php):

    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->validateCsrfTokens(except: [
            'paddle/*',
        ]);
    })

    Laravel 10 (app/Http/Middleware/VerifyCsrfToken.php):

    protected $except = [
        'paddle/*',
    ];
  5. Choose between Cashier Paddle 1.x and 2.x based on Paddle version

    2.x

    There is no official upgrade path to migrate from Paddle Classic to Paddle Billing. Your choice of Cashier Paddle version depends entirely on which Paddle API you are using:

    • Paddle Classic: Continue using Cashier Paddle 1.x.
    • Paddle Billing: Use Cashier Paddle 2.x for all new projects.
  6. Simulate Paddle webhooks

    2.x

    To test webhook handling, post a JSON payload directly to the paddle/webhook route.

    Without Signature Verification

    If you unset cashier.webhook_secret in your test environment, you can post the payload directly:

    config()->set('cashier.webhook_secret', null);
    $this->postJson('paddle/webhook', $payload)->assertOk();

    With Signature Verification

    To test with signature verification enabled, you must manually generate a Paddle-Signature header using the cashier.webhook_secret. The signature format is ts={timestamp};h1={hmac_sha256}, where the HMAC is calculated over {$timestamp}:{$json_body}.

    $payload = ['event_type' => 'transaction.completed', 'data' => [...]];
    $json = json_encode($payload, JSON_THROW_ON_ERROR);
    $timestamp = time();
    $signature = hash_hmac('sha256', "{$timestamp}:{$json}", config('cashier.webhook_secret'));
    
    $this->withHeader('Paddle-Signature', "ts={$timestamp};h1={$signature}")
        ->call('POST', 'paddle/webhook', [], [], [], ['CONTENT_TYPE' => 'application/json'], $json)
        ->assertOk();
  7. Manage Paddle customers with the Customer model

    2.x
    The Laravel\Paddle\Customer model represents a Paddle customer in your database. It is designed to be linked to your application's billable entities (such as a User model) via a polymorphic relationship. This model tracks trial information and provides methods to check the status of generic trials.
  8. Publish Cashier Paddle configuration, migrations, and views

    2.x

    To customize the package behavior or appearance, you can publish its assets to your application. Use the following tags with the vendor:publish command:

    • Configuration: cashier-config (publishes config/cashier.php)
    • Migrations: cashier-migrations (publishes database migrations)
    • Views: cashier-views (publishes vendor views to resources/views/vendor/cashier)
    # Publish configuration
    php artisan vendor:publish --tag="cashier-config"
    
    # Publish migrations
    php artisan vendor:publish --tag="cashier-migrations"
    
    # Publish views
    php artisan vendor:publish --tag="cashier-views"
  9. Handle Paddle webhooks in your application

    2.x

    To process Paddle webhooks, you should point your Paddle webhook URL to the route handled by Laravel\Paddle\Http\Controllers\WebhookController.

    When a webhook hits the controller, it performs the following logic:

    1. Dispatches WebhookReceived.
    2. Identifies the handler based on the event_type (e.g., customer.updated maps to handleCustomerUpdated).
    3. Executes the handler to update your local database (Subscriptions, Transactions, Customers).
    4. Dispatches WebhookHandled.

    If you need to perform custom logic when a subscription is updated or a transaction is completed, listen for the corresponding Laravel events dispatched by the controller.