# Laravel Cashier Paddle
Laravel Cashier Paddle provides an expressive, fluent interface to Paddle's subscription billing services. It handles almost all of the boilerplate subscription billing code you are dreading writing, including subscription management, swapping plans, subscription quantities, subscription pausing, cancellation grace periods, and much more. This package integrates seamlessly with Laravel's Eloquent ORM and provides robust webhook handling for automatic synchronization with Paddle's billing system.
Built on top of Paddle's API, Cashier simplifies the process of managing recurring billing in Laravel applications. It provides a consistent, Laravel-style API for creating subscriptions, processing one-time charges, managing trials, handling refunds, and tracking transaction history. The package includes Blade components for embedding checkout experiences, comprehensive event system for reacting to billing changes, and full support for multi-item subscriptions with proration handling.
## Adding the Billable Trait to Your Model
Enable billing functionality on any Eloquent model
Add the `Billable` trait to your User model (or any billable entity) to unlock all subscription and payment features.
```php
use Laravel\Paddle\Billable;
class User extends Authenticatable
{
use Billable;
}
// Create a customer in Paddle
$user = User::find(1);
$customer = $user->createAsCustomer([
'trial_ends_at' => now()->addDays(14),
]);
// Check subscription status
if ($user->subscribed('premium')) {
// User has active premium subscription
}
if ($user->onTrial('basic')) {
// User is in trial for basic plan
}
// Get all transactions
foreach ($user->transactions as $transaction) {
echo $transaction->total(); // "$29.99"
echo $transaction->invoicePdf(); // PDF URL
}
```
## Creating a New Subscription
Start a new subscription with a checkout session
Create a subscription checkout for a customer with a specific price ID from your Paddle dashboard.
```php
use App\Models\User;
$user = User::find(1);
// Simple subscription to a single price
$checkout = $user->subscribe('price_1234567890abc')
->returnTo(route('subscriptions.success'));
// Subscription with custom type
$checkout = $user->subscribe('price_premium_monthly', 'premium')
->returnTo(route('subscriptions.success'));
// Multi-price subscription
$checkout = $user->checkout([
'price_basic_monthly' => 1,
'price_addon_storage' => 3,
])->returnTo(route('success'));
// Render checkout in Blade
// In controller: return view('subscribe', ['checkout' => $checkout]);
// In view:
//
// @paddleJS
```
## Checking Subscription Status
Verify if a user has an active subscription
Use various methods to check subscription states including active, trialing, past due, paused, and canceled.
```php
$user = User::find(1);
// Check if subscribed (active or on trial)
if ($user->subscribed('default')) {
// Has valid subscription
}
// Check specific price
if ($user->subscribed('default', 'price_premium')) {
// Subscribed to specific price
}
// Check by product
if ($user->subscribedToProduct(['prod_123', 'prod_456'])) {
// Subscribed to at least one product
}
if ($user->subscribedToPrice(['price_basic', 'price_premium'])) {
// Subscribed to at least one price
}
// Get specific subscription
$subscription = $user->subscription('premium');
if ($subscription->active()) {
// Subscription is active
}
if ($subscription->onTrial()) {
// Still in trial period
}
if ($subscription->recurring()) {
// Active and not on grace period
}
if ($subscription->onGracePeriod()) {
// Canceled but still active until period ends
}
```
## Swapping Subscription Plans
Change a subscription to a different price or plan
Update a subscription to new prices with flexible proration options.
```php
$subscription = $user->subscription('default');
// Swap with proration (default - bill on next renewal)
$subscription->prorate()->swap('price_new_plan');
// Swap and bill immediately with proration
$subscription->prorateImmediately()->swap('price_new_plan');
// Swap without proration
$subscription->noProrate()->swap('price_new_plan');
// Swap immediately without proration
$subscription->immediatelyWithoutProrate()->swap('price_new_plan');
// Don't bill for the swap
$subscription->doNotBill()->swap('price_new_plan');
// Swap and invoice immediately
$subscription->swapAndInvoice('price_premium_yearly');
// Swap multiple items
$subscription->swap([
'price_new_base' => 1,
'price_addon_1' => 2,
]);
```
## Managing Subscription Quantities
Update quantities for metered billing
Increment, decrement, or set exact quantities for subscription items.
```php
$subscription = $user->subscription('default');
// Increment quantity (for single-price subscriptions)
$subscription->incrementQuantity(2);
// With specific price (for multi-price subscriptions)
$subscription->incrementQuantity(1, 'price_per_seat');
// Increment and invoice immediately
$subscription->incrementAndInvoice(5, 'price_per_seat');
// Decrement quantity (minimum 1)
$subscription->decrementQuantity(1, 'price_per_seat');
// Set exact quantity
$subscription->updateQuantity(10, 'price_per_seat');
// Get current quantity
$item = $subscription->findItemOrFail('price_per_seat');
echo $item->quantity; // 10
```
## Pausing and Resuming Subscriptions
Temporarily pause subscriptions
Pause subscriptions immediately or at the next billing period, with optional resume dates.
```php
$subscription = $user->subscription('default');
// Pause at next billing period
$subscription->pause();
// Pause immediately
$subscription->pauseNow();
// Pause until specific date (at next billing)
$subscription->pauseUntil(now()->addMonths(2));
// Pause immediately until specific date
$subscription->pauseNowUntil(now()->addDays(30));
// Check pause status
if ($subscription->paused()) {
echo "Subscription is paused";
}
if ($subscription->onPausedGracePeriod()) {
echo "Will pause on: " . $subscription->paused_at;
}
// Resume a paused subscription
$subscription->resume();
// Resume at future date
$subscription->resume(now()->addDays(7));
```
## Canceling Subscriptions
Cancel subscriptions with grace periods
Cancel subscriptions immediately or at the end of the current billing period.
```php
$subscription = $user->subscription('default');
// Cancel at end of billing period
$subscription->cancel();
// Cancel immediately
$subscription->cancelNow();
// Check cancellation status
if ($subscription->canceled()) {
echo "Subscription is canceled";
}
if ($subscription->onGracePeriod()) {
echo "Active until: " . $subscription->ends_at;
}
// Stop a scheduled cancellation
$subscription->stopCancelation();
// Redirect user to Paddle's cancellation page
return $subscription->redirectToCancel();
// Or get the URL
$cancelUrl = $subscription->cancelUrl();
```
## Managing Trial Periods
Handle free trial periods for subscriptions
Extend trials, check trial status, or force trial completion.
```php
$user = User::find(1);
// Create customer with generic trial
$customer = $user->createAsCustomer([
'trial_ends_at' => now()->addDays(14),
]);
// Check trial status
if ($user->onTrial('premium')) {
echo "On trial until: " . $user->trialEndsAt('premium');
}
if ($user->onGenericTrial()) {
echo "On generic trial";
}
if ($user->hasExpiredTrial('premium')) {
echo "Trial has expired";
}
// Extend subscription trial
$subscription = $user->subscription('premium');
$subscription->extendTrial(now()->addDays(30));
// Force trial to end immediately
$subscription->activate();
```
## Processing One-Time Charges
Create one-time payments without subscriptions
Charge customers for one-time purchases or fees.
```php
$user = User::find(1);
// Simple one-time charge
$checkout = $user->charge(5000, 'Setup Fee')
->returnTo(route('checkout.success'));
// Charge with options
$checkout = $user->charge(9999, 'Consulting Service', [
'price' => [
'description' => '2-hour consulting session',
'unit_price' => [
'amount' => '9999',
'currency_code' => 'USD',
],
'product' => [
'name' => 'Consulting Service',
'tax_category' => 'standard',
],
],
'quantity' => 1,
])->returnTo(route('success'));
// Multiple one-time items
$checkout = $user->chargeMany([
[
'price' => [
'description' => 'Setup Fee',
'unit_price' => ['amount' => '5000', 'currency_code' => 'USD'],
'product' => ['name' => 'Setup', 'tax_category' => 'standard'],
],
'quantity' => 1,
],
[
'price' => [
'description' => 'Training Session',
'unit_price' => ['amount' => '15000', 'currency_code' => 'USD'],
'product' => ['name' => 'Training', 'tax_category' => 'standard'],
],
'quantity' => 2,
],
])->returnTo(route('success'));
```
## Adding One-Time Charges to Subscriptions
Bill additional items on top of existing subscriptions
Add one-time charges to a subscription, billed immediately or at next renewal.
```php
$subscription = $user->subscription('default');
// Charge at next billing period
$subscription->charge('price_one_time_fee');
// Charge multiple items at next billing
$subscription->charge([
'price_setup_fee' => 1,
'price_import_service' => 1,
]);
// Charge immediately
$subscription->chargeAndInvoice('price_one_time_fee');
// Charge multiple items immediately
$subscription->chargeAndInvoice([
'price_priority_support' => 1,
'price_data_migration' => 1,
]);
```
## Handling Transactions and Invoices
Access and manage transaction records
Retrieve transaction details, download invoices, and process refunds.
```php
$user = User::find(1);
// Get all transactions
$transactions = $user->transactions;
foreach ($transactions as $transaction) {
echo $transaction->paddle_id; // Paddle transaction ID
echo $transaction->invoice_number; // Invoice number
echo $transaction->status; // draft, ready, billed, paid, completed
echo $transaction->total(); // "$50.00"
echo $transaction->tax(); // "$5.00"
echo $transaction->currency; // "USD"
echo $transaction->billed_at; // Carbon instance
}
// Get transaction by Paddle ID
$transaction = $user->transactions()
->where('paddle_id', 'txn_123456')
->first();
// Download invoice PDF
if ($url = $transaction->invoicePdf()) {
return redirect($url);
}
// Or use convenience method
return $transaction->redirectToInvoicePdf();
// Get subscription transactions
$subscription = $user->subscription('default');
$transactions = $subscription->transactions;
```
## Processing Refunds and Credits
Refund or credit transactions
Issue full or partial refunds, or credit customer accounts.
```php
$transaction = Transaction::where('paddle_id', 'txn_123456')->first();
// Full refund
$transaction->refund('Customer requested refund');
// Partial refund for specific prices
$transaction->refund('Partial refund for quality issue', [
'price_basic_monthly' => 1500, // Refund $15.00 for this item
'price_addon_feature' => null, // Full refund for this item
]);
// Credit to customer account (no money back)
$transaction->credit('Credit for service disruption');
// Partial credit
$transaction->credit('Partial credit', [
'price_basic_monthly' => 1000, // $10.00 credit
]);
// Check transaction status
if ($transaction->status === Transaction::STATUS_COMPLETED) {
// Transaction is completed
}
```
## Getting Payment Information
Retrieve payment history and upcoming payments
Access last payment details and preview next scheduled payment.
```php
$subscription = $user->subscription('default');
// Get last payment
$lastPayment = $subscription->lastPayment();
if ($lastPayment) {
echo $lastPayment->amount(); // "$29.99"
echo $lastPayment->rawAmount(); // 2999 (cents)
echo $lastPayment->currency()->getCode(); // "USD"
echo $lastPayment->date()->format('Y-m-d'); // "2024-11-01"
}
// Get next scheduled payment
$nextPayment = $subscription->nextPayment();
if ($nextPayment) {
echo $nextPayment->amount(); // "$29.99"
echo $nextPayment->date()->format('F j, Y'); // "December 1, 2024"
}
// Update payment method URL
$updateUrl = $subscription->paymentMethodUpdateUrl();
return redirect($updateUrl);
// Or use convenience method
return $subscription->redirectToUpdatePaymentMethod();
```
## Previewing Prices with Tax
Calculate prices with tax before checkout
Preview prices with tax calculations for the customer's location.
```php
use Laravel\Paddle\Cashier;
// Preview single price
$previews = Cashier::previewPrices('price_basic_monthly');
foreach ($previews as $preview) {
echo $preview->price()->amount(); // "$29.99"
echo $preview->subtotal(); // "$29.99"
echo $preview->tax(); // "$5.40"
echo $preview->total(); // "$35.39"
// Raw values (in cents)
echo $preview->rawSubtotal(); // 2999
echo $preview->rawTax(); // 540
echo $preview->rawTotal(); // 3539
echo $preview->price()->interval(); // "month"
echo $preview->price()->frequency(); // 1
}
// Preview multiple prices
$previews = Cashier::previewPrices([
'price_basic_monthly' => 1,
'price_addon_storage' => 3,
]);
// Preview with customer context
$previews = $user->previewPrices(['price_premium_yearly' => 1]);
// Preview with options
$previews = Cashier::previewPrices('price_basic_monthly', [
'customer_ip_address' => '8.8.8.8',
'address' => [
'postal_code' => '10001',
'country_code' => 'US',
],
]);
```
## Handling Webhooks
Process Paddle webhook events automatically
Webhooks are automatically registered and handled at `/paddle/webhook`. Configure your webhook secret in `.env`.
```php
// .env configuration
PADDLE_SELLER_ID=your-seller-id
PADDLE_CLIENT_SIDE_TOKEN=your-client-token
PADDLE_API_KEY=your-api-key
PADDLE_WEBHOOK_SECRET=your-webhook-secret
// Webhooks handled automatically:
// - customer.updated
// - subscription.created
// - subscription.updated
// - subscription.paused
// - subscription.canceled
// - transaction.completed
// - transaction.updated
// Listen to events in EventServiceProvider
use Laravel\Paddle\Events\SubscriptionCreated;
use Laravel\Paddle\Events\SubscriptionCanceled;
use Laravel\Paddle\Events\TransactionCompleted;
protected $listen = [
SubscriptionCreated::class => [
SendSubscriptionConfirmation::class,
],
SubscriptionCanceled::class => [
SendCancellationEmail::class,
],
TransactionCompleted::class => [
LogTransaction::class,
SendInvoiceEmail::class,
],
];
// Example listener
public function handle(SubscriptionCreated $event)
{
$billable = $event->billable;
$subscription = $event->subscription;
$payload = $event->payload; // Raw Paddle data
// Send welcome email, grant access, etc.
}
```
## Customizing Checkout Experience
Create custom checkout flows with options
Build checkouts for guests, existing customers, or with custom data.
```php
use Laravel\Paddle\Checkout;
// Guest checkout
$checkout = Checkout::guest([
'price_basic_monthly' => 1,
])
->customData(['order_id' => 12345])
->returnTo(route('success'));
// Existing customer checkout
$customer = $user->customer;
$checkout = Checkout::customer($customer, [
'price_premium_yearly' => 1,
])
->returnTo(route('success'));
// Transaction-based checkout (one-time)
$transaction = Cashier::api('POST', 'transactions', [
'items' => [
['price_id' => 'price_one_time', 'quantity' => 1]
]
])['data'];
$checkout = Checkout::transaction($transaction, $customer)
->returnTo(route('success'));
// Render in Blade
Subscribe Now
@paddleJS
```
## Querying Subscriptions with Scopes
Filter subscriptions efficiently
Use Eloquent scopes to query subscriptions by status.
```php
use App\Models\User;
use Laravel\Paddle\Subscription;
// Get all valid subscriptions (active or trialing)
$validSubscriptions = Subscription::valid()->get();
// Get all active subscriptions
$activeSubscriptions = Subscription::active()->get();
// Get trialing subscriptions
$trialingSubscriptions = Subscription::onTrial()->get();
// Get recurring subscriptions (active without grace periods)
$recurringSubscriptions = Subscription::recurring()->get();
// Get paused subscriptions
$pausedSubscriptions = Subscription::paused()->get();
// Get canceled subscriptions
$canceledSubscriptions = Subscription::canceled()->get();
// Get past due subscriptions
$pastDueSubscriptions = Subscription::pastDue()->get();
// Users with active subscriptions
$subscribedUsers = User::whereHas('subscriptions', function ($query) {
$query->active();
})->get();
// Users on grace period after cancellation
$onGracePeriod = Subscription::onGracePeriod()->get();
```
## Customizing Models and Configuration
Override default models and settings
Customize Cashier behavior in your AppServiceProvider.
```php
use Laravel\Paddle\Cashier;
use App\Models\CustomCustomer;
use App\Models\CustomSubscription;
public function boot()
{
// Use custom models
Cashier::useCustomerModel(CustomCustomer::class);
Cashier::useSubscriptionModel(CustomSubscription::class);
Cashier::useSubscriptionItemModel(CustomSubscriptionItem::class);
Cashier::useTransactionModel(CustomTransaction::class);
// Custom currency formatting
Cashier::formatCurrencyUsing(function ($amount, $currency, $locale, $options) {
return number_format($amount / 100, 2) . ' ' . strtoupper($currency);
});
// Don't register routes (manage manually)
Cashier::ignoreRoutes();
// Keep past-due subscriptions active
Cashier::keepPastDueSubscriptionsActive();
}
// config/cashier.php
return [
'seller_id' => env('PADDLE_SELLER_ID'),
'client_side_token' => env('PADDLE_CLIENT_SIDE_TOKEN'),
'api_key' => env('PADDLE_API_KEY'),
'webhook_secret' => env('PADDLE_WEBHOOK_SECRET'),
'path' => env('CASHIER_PATH', 'paddle'),
'currency' => env('CASHIER_CURRENCY', 'USD'),
'currency_locale' => env('CASHIER_CURRENCY_LOCALE', 'en'),
'sandbox' => env('PADDLE_SANDBOX', false),
];
```
## Testing with Cashier Fake
Test billing functionality without API calls
Use CashierFake to test subscription flows in your test suite.
```php
use Laravel\Paddle\Cashier;
use Tests\TestCase;
class SubscriptionTest extends TestCase
{
public function test_user_can_subscribe()
{
// Initialize fake
Cashier::fake();
$user = User::factory()->create();
// Perform subscription actions
$checkout = $user->subscribe('price_basic_monthly');
// Assertions
Cashier::assertSubscriptionCreated(function ($event) {
return $event->subscription->paddle_id === 'sub_123';
});
}
public function test_user_can_cancel_subscription()
{
Cashier::fake([
'subscriptions' => [['id' => 'sub_123', 'status' => 'active']]
]);
$subscription = Subscription::factory()->create();
$subscription->cancel();
Cashier::assertSubscriptionCanceled();
}
public function test_transaction_completed()
{
Cashier::fake();
// Test code that triggers transaction
Cashier::assertTransactionCompleted(function ($event) {
return $event->transaction->paddle_id === 'txn_123';
});
}
}
```
## Summary
Laravel Cashier Paddle streamlines subscription billing by providing a fluent, expressive interface for common billing operations. The package handles the complexities of recurring payments, trial periods, subscription changes, and payment processing while maintaining clean, Laravel-style code. With automatic webhook handling, your application stays synchronized with Paddle's billing system without manual intervention. The Billable trait integrates seamlessly with your existing Eloquent models, requiring minimal setup to add full billing capabilities.
Integration with Cashier Paddle follows Laravel conventions throughout. Use the provided Blade components for frontend checkout experiences, listen to dispatched events for custom business logic, and leverage Eloquent relationships to access billing data. The package supports advanced scenarios like multi-item subscriptions, proration strategies, quantity-based billing, and complex refund logic. Whether building a SaaS application with tiered pricing or a platform with usage-based billing, Cashier Paddle provides the tools needed to implement professional subscription management with confidence.