IntroductionIntroduction
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. In addition to basic subscription management, Cashier can handle: coupons, swapping subscription, subscription "quantities", cancellation grace periods, and more.Laravel Cashier Paddle[https://github.com/laravel/cashier-paddle] provides an expressive, fluent interface to Paddle's[https://paddle.com] subscription billing services. It handles almost all of the boilerplate subscription billing code you are dreading. In addition to basic subscription management, Cashier can handle: coupons, swapping subscription, subscription "quantities", cancellation grace periods, and more.
While working with Cashier we recommend you also review Paddle's user guides and API documentation.While working with Cashier we recommend you also review Paddle's user guides[https://developer.paddle.com/guides] and API documentation[https://developer.paddle.com/api-reference].
Upgrading CashierUpgrading Cashier
When upgrading to a new version of Cashier, it's important that you carefully review the upgrade guide.When upgrading to a new version of Cashier, it's important that you carefully review the upgrade guide[https://github.com/laravel/cashier-paddle/blob/master/UPGRADE.md].
InstallationInstallation
First, install the Cashier package for Paddle using the Composer package manager:First, install the Cashier package for Paddle using the Composer package manager:
composer require laravel/cashier-paddle
set up Cashier's webhook handling.Warning
Warning! To ensure Cashier properly handles all Paddle events, remember to
To ensure Cashier properly handles all Paddle events, remember to set up Cashier's webhook handling[#handling-paddle-webhooks].
Paddle SandboxPaddle Sandbox
During local and staging development, you should register a Paddle Sandbox account. This account will give you a sandboxed environment to test and develop your applications without making actual payments. You may use Paddle's test card numbers to simulate various payment scenarios.During local and staging development, you should register a Paddle Sandbox account[https://developer.paddle.com/getting-started/sandbox]. This account will give you a sandboxed environment to test and develop your applications without making actual payments. You may use Paddle's test card numbers[https://developer.paddle.com/getting-started/sandbox#test-cards] to simulate various payment scenarios.
When using the Paddle Sandbox environment, you should set
the PADDLE_SANDBOX
environment variable to
true
within your application's
.env
file:When
using the Paddle Sandbox environment, you should set
the PADDLE_SANDBOX
environment variable
to true
within your application's
.env
file:
PADDLE_SANDBOX=true
After you have finished developing your application you may apply for a Paddle vendor account. Before your application is placed into production, Paddle will need to approve your application's domain.After you have finished developing your application you may apply for a Paddle vendor account[https://paddle.com]. Before your application is placed into production, Paddle will need to approve your application's domain.
Database MigrationsDatabase Migrations
The Cashier service provider registers its own database
migration directory, so remember to migrate your
database after installing the package. The Cashier
migrations will create a new customers
table. In addition, a new subscriptions
table will be created to store all of your customer's
subscriptions. Finally, a new receipts
table will be created to store all of your application's
receipt information:The Cashier
service provider registers its own database
migration directory, so remember to migrate your
database after installing the package. The Cashier
migrations will create a new customers
table. In addition, a new subscriptions
table will be created to store all of your
customer's subscriptions. Finally, a new
receipts
table will be created to store
all of your application's receipt
information:
php artisan migrate
If you need to overwrite the migrations that are included
with Cashier, you can publish them using the
vendor:publish
Artisan command:If you need to overwrite the
migrations that are included with Cashier, you can
publish them using the vendor:publish
Artisan command:
php artisan vendor:publish --tag="cashier-migrations"
If you would like to prevent Cashier's migrations from
running entirely, you may use the
ignoreMigrations
provided by Cashier.
Typically, this method should be called in the
register
method of your
AppServiceProvider
:If you would like to prevent
Cashier's migrations from running entirely, you may
use the ignoreMigrations
provided by
Cashier. Typically, this method should be called in
the register
method of your
AppServiceProvider
:
use Laravel\Paddle\Cashier;
/**
* Register any application services.
*
* @return void
*/
public function register()
{
Cashier::ignoreMigrations();
}
ConfigurationConfiguration
Billable ModelBillable Model
Before using Cashier, you must add the
Billable
trait to your user model
definition. This trait provides various methods to allow
you to perform common billing tasks, such as creating
subscriptions, applying coupons and updating payment
method information:Before using
Cashier, you must add the Billable
trait to your user model definition. This trait
provides various methods to allow you to perform
common billing tasks, such as creating
subscriptions, applying coupons and updating payment
method information:
use Laravel\Paddle\Billable;
class User extends Authenticatable
{
use Billable;
}
If you have billable entities that are not users, you may also add the trait to those classes:If you have billable entities that are not users, you may also add the trait to those classes:
use Illuminate\Database\Eloquent\Model;
use Laravel\Paddle\Billable;
class Team extends Model
{
use Billable;
}
API KeysAPI Keys
Next, you should configure your Paddle keys in your
application's .env
file. You can retrieve
your Paddle API keys from the Paddle control panel:Next, you should configure your
Paddle keys in your application's .env
file. You can retrieve your Paddle API keys from the
Paddle control panel:
PADDLE_VENDOR_ID=your-paddle-vendor-id
PADDLE_VENDOR_AUTH_CODE=your-paddle-vendor-auth-code
PADDLE_PUBLIC_KEY="your-paddle-public-key"
PADDLE_SANDBOX=true
The PADDLE_SANDBOX
environment variable
should be set to true
when you are using Paddle's Sandbox
environment. The PADDLE_SANDBOX
variable should be set to false
if you are
deploying your application to production and are using
Paddle's live vendor environment.The PADDLE_SANDBOX
environment variable should be set to
true
when you are using Paddle's
Sandbox environment[#paddle-sandbox]. The
PADDLE_SANDBOX
variable should be set
to false
if you are deploying your
application to production and are using Paddle's
live vendor environment.
Paddle JSPaddle JS
Paddle relies on its own JavaScript library to initiate
the Paddle checkout widget. You can load the JavaScript
library by placing the @paddleJS
Blade
directive right before your application layout's closing
</head>
tag:Paddle relies on its own
JavaScript library to initiate the Paddle checkout
widget. You can load the JavaScript library by
placing the @paddleJS
Blade directive
right before your application layout's closing
</head>
tag:
<head>
...
@paddleJS
</head>
Currency ConfigurationCurrency Configuration
The default Cashier currency is United States Dollars
(USD). You can change the default currency by defining a
CASHIER_CURRENCY
environment variable
within your application's .env
file:The default Cashier currency is
United States Dollars (USD). You can change the
default currency by defining a
CASHIER_CURRENCY
environment variable
within your application's .env
file:
CASHIER_CURRENCY=EUR
In addition to configuring Cashier's currency, you may
also specify a locale to be used when formatting money
values for display on invoices. Internally, Cashier
utilizes PHP's
NumberFormatter
class to set the
currency locale:In addition to
configuring Cashier's currency, you may also specify
a locale to be used when formatting money values for
display on invoices. Internally, Cashier utilizes
PHP's NumberFormatter
class[https://www.php.net/manual/en/class.numberformatter.php]
to set the currency locale:
CASHIER_CURRENCY_LOCALE=nl_BE
Warning! In order to use locales other than
en
, ensure theext-intl
PHP extension is installed and configured on your server.Warning
In order to use locales other thanen
, ensure theext-intl
PHP extension is installed and configured on your server.
Overriding Default ModelsOverriding Default Models
You are free to extend the models used internally by Cashier by defining your own model and extending the corresponding Cashier model:You are free to extend the models used internally by Cashier by defining your own model and extending the corresponding Cashier model:
use Laravel\Paddle\Subscription as CashierSubscription;
class Subscription extends CashierSubscription
{
// ...
}
After defining your model, you may instruct Cashier to
use your custom model via the
Laravel\Paddle\Cashier
class. Typically,
you should inform Cashier about your custom models in
the boot
method of your application's
App\Providers\AppServiceProvider
class:After defining your
model, you may instruct Cashier to use your custom
model via the Laravel\Paddle\Cashier
class. Typically, you should inform Cashier about
your custom models in the boot
method
of your application's
App\Providers\AppServiceProvider
class:
use App\Models\Cashier\Receipt;
use App\Models\Cashier\Subscription;
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Cashier::useReceiptModel(Receipt::class);
Cashier::useSubscriptionModel(Subscription::class);
}
Core ConceptsCore Concepts
Pay LinksPay Links
Paddle lacks an extensive CRUD API to perform subscription state changes. Therefore, most interactions with Paddle are done through its checkout widget. Before we can display the checkout widget, we must generate a "pay link" using Cashier. A "pay link" will inform the checkout widget of the billing operation we wish to perform:Paddle lacks an extensive CRUD API to perform subscription state changes. Therefore, most interactions with Paddle are done through its checkout widget[https://developer.paddle.com/guides/how-tos/checkout/paddle-checkout]. Before we can display the checkout widget, we must generate a "pay link" using Cashier. A "pay link" will inform the checkout widget of the billing operation we wish to perform:
use App\Models\User;
use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$payLink = $request->user()->newSubscription('default', $premium = 34567)
->returnTo(route('home'))
->create();
return view('billing', ['payLink' => $payLink]);
});
Cashier includes a paddle-button
Blade component. We
may pass the pay link URL to this component as a
"prop". When this button is clicked, Paddle's
checkout widget will be displayed:Cashier includes a
paddle-button
Blade
component[/docs/{{version}}/blade#components].
We may pass the pay link URL to this component as a
"prop". When this button is clicked,
Paddle's checkout widget will be
displayed:
<x-paddle-button :url="$payLink" class="px-8 py-4">
Subscribe
</x-paddle-button>
By default, this will display a button with the standard
Paddle styling. You can remove all Paddle styling by
adding the data-theme="none"
attribute to the component:By
default, this will display a button with the
standard Paddle styling. You can remove all Paddle
styling by adding the
data-theme="none"
attribute
to the component:
<x-paddle-button :url="$payLink" class="px-8 py-4" data-theme="none">
Subscribe
</x-paddle-button>
The Paddle checkout widget is asynchronous. Once the user creates or updates a subscription within the widget, Paddle will send your application webhooks so that you may properly update the subscription state in our own database. Therefore, it's important that you properly set up webhooks to accommodate for state changes from Paddle.The Paddle checkout widget is asynchronous. Once the user creates or updates a subscription within the widget, Paddle will send your application webhooks so that you may properly update the subscription state in our own database. Therefore, it's important that you properly set up webhooks[#handling-paddle-webhooks] to accommodate for state changes from Paddle.
For more information on pay links, you may review the Paddle API documentation on pay link generation.For more information on pay links, you may review the Paddle API documentation on pay link generation[https://developer.paddle.com/api-reference/product-api/pay-links/createpaylink].
Warning
Warning! After a subscription state change, the delay for receiving the corresponding webhook is typically minimal but you should account for this in your application by considering that your user's subscription might not be immediately available after completing the checkout.
After a subscription state change, the delay for receiving the corresponding webhook is typically minimal but you should account for this in your application by considering that your user's subscription might not be immediately available after completing the checkout.
Manually Rendering Pay LinksManually Rendering Pay Links
You may also manually render a pay link without using Laravel's built-in Blade components. To get started, generate the pay link URL as demonstrated in previous examples:You may also manually render a pay link without using Laravel's built-in Blade components. To get started, generate the pay link URL as demonstrated in previous examples:
$payLink = $request->user()->newSubscription('default', $premium = 34567)
->returnTo(route('home'))
->create();
Next, simply attach the pay link URL to an a
element in your HTML:Next,
simply attach the pay link URL to an a
element in your HTML:
<a href="#!" class="ml-4 paddle_button" data-override="{{ $payLink }}">
Paddle Checkout
</a>
Payments Requiring Additional ConfirmationPayments Requiring Additional Confirmation
Sometimes additional verification is required in order to confirm and process a payment. When this happens, Paddle will present a payment confirmation screen. Payment confirmation screens presented by Paddle or Cashier may be tailored to a specific bank or card issuer's payment flow and can include additional card confirmation, a temporary small charge, separate device authentication, or other forms of verification.Sometimes additional verification is required in order to confirm and process a payment. When this happens, Paddle will present a payment confirmation screen. Payment confirmation screens presented by Paddle or Cashier may be tailored to a specific bank or card issuer's payment flow and can include additional card confirmation, a temporary small charge, separate device authentication, or other forms of verification.
Inline CheckoutInline Checkout
If you don't want to make use of Paddle's "overlay" style checkout widget, Paddle also provides the option to display the widget inline. While this approach does not allow you to adjust any of the checkout's HTML fields, it allows you to embed the widget within your application.If you don't want to make use of Paddle's "overlay" style checkout widget, Paddle also provides the option to display the widget inline. While this approach does not allow you to adjust any of the checkout's HTML fields, it allows you to embed the widget within your application.
To make it easy for you to get started with inline
checkout, Cashier includes a
paddle-checkout
Blade component. To get
started, you should generate a pay
link and pass the pay link to the component's
override
attribute:To make it easy for you to get
started with inline checkout, Cashier includes a
paddle-checkout
Blade component. To get
started, you should generate a pay
link[#pay-links] and pass the pay link to
the component's override
attribute:
<x-paddle-checkout :override="$payLink" class="w-full" />
To adjust the height of the inline checkout component,
you may pass the height
attribute to the
Blade component:To adjust the
height of the inline checkout component, you may
pass the height
attribute to the Blade
component:
<x-paddle-checkout :override="$payLink" class="w-full" height="500" />
Inline Checkout Without Pay LinksInline Checkout Without Pay Links
Alternatively, you may customize the widget with custom options instead of using a pay link:Alternatively, you may customize the widget with custom options instead of using a pay link:
@php
$options = [
'product' => $productId,
'title' => 'Product Title',
];
@endphp
<x-paddle-checkout :options="$options" class="w-full" />
Please consult Paddle's guide on Inline Checkout as well as their parameter reference for further details on the inline checkout's available options.Please consult Paddle's guide on Inline Checkout[https://developer.paddle.com/guides/how-tos/checkout/inline-checkout] as well as their parameter reference[https://developer.paddle.com/reference/paddle-js/parameters] for further details on the inline checkout's available options.
Warning! If you would like to also use the
passthrough
option when specifying custom options, you should provide a key / value array as its value. Cashier will automatically handle converting the array to a JSON string. In addition, thecustomer_id
passthrough option is reserved for internal Cashier usage.Warning
If you would like to also use thepassthrough
option when specifying custom options, you should provide a key / value array as its value. Cashier will automatically handle converting the array to a JSON string. In addition, thecustomer_id
passthrough option is reserved for internal Cashier usage.
Manually Rendering An Inline CheckoutManually Rendering An Inline Checkout
You may also manually render an inline checkout without using Laravel's built-in Blade components. To get started, generate the pay link URL as demonstrated in previous examples.You may also manually render an inline checkout without using Laravel's built-in Blade components. To get started, generate the pay link URL as demonstrated in previous examples[#pay-links].
Next, you may use Paddle.js to initialize the checkout. To keep this example simple, we will demonstrate this using Alpine.js; however, you are free to translate this example to your own frontend stack:Next, you may use Paddle.js to initialize the checkout. To keep this example simple, we will demonstrate this using Alpine.js[https://github.com/alpinejs/alpine]; however, you are free to translate this example to your own frontend stack:
<div class="paddle-checkout" x-data="{}" x-init="
Paddle.Checkout.open({
override: {{ $payLink }},
method: 'inline',
frameTarget: 'paddle-checkout',
frameInitialHeight: 366,
frameStyle: 'width: 100%; background-color: transparent; border: none;'
});
">
</div>
User IdentificationUser Identification
In contrast to Stripe, Paddle users are unique across all
of Paddle, not unique per Paddle account. Because of
this, Paddle's API's do not currently provide a method
to update a user's details such as their email address.
When generating pay links, Paddle identifies users using
the customer_email
parameter. When creating
a subscription, Paddle will try to match the user
provided email to an existing Paddle user.In contrast to Stripe, Paddle
users are unique across all of Paddle, not unique
per Paddle account. Because of this, Paddle's API's
do not currently provide a method to update a user's
details such as their email address. When generating
pay links, Paddle identifies users using the
customer_email
parameter. When creating
a subscription, Paddle will try to match the user
provided email to an existing Paddle
user.
In light of this behavior, there are some important things to keep in mind when using Cashier and Paddle. First, you should be aware that even though subscriptions in Cashier are tied to the same application user, they could be tied to different users within Paddle's internal systems. Secondly, each subscription has its own connected payment method information and could also have different email addresses within Paddle's internal systems (depending on which email was assigned to the user when the subscription was created).In light of this behavior, there are some important things to keep in mind when using Cashier and Paddle. First, you should be aware that even though subscriptions in Cashier are tied to the same application user, they could be tied to different users within Paddle's internal systems. Secondly, each subscription has its own connected payment method information and could also have different email addresses within Paddle's internal systems (depending on which email was assigned to the user when the subscription was created).
Therefore, when displaying subscriptions you should
always inform the user which email address or payment
method information is connected to the subscription on a
per-subscription basis. Retrieving this information can
be done with the following methods provided by the
Laravel\Paddle\Subscription
model:Therefore, when displaying
subscriptions you should always inform the user
which email address or payment method information is
connected to the subscription on a per-subscription
basis. Retrieving this information can be done with
the following methods provided by the
Laravel\Paddle\Subscription
model:
$subscription = $user->subscription('default');
$subscription->paddleEmail();
$subscription->paymentMethod();
$subscription->cardBrand();
$subscription->cardLastFour();
$subscription->cardExpirationDate();
There is currently no way to modify a user's email
address through the Paddle API. When a user wants to
update their email address within Paddle, the only way
for them to do so is to contact Paddle customer support.
When communicating with Paddle, they need to provide the
paddleEmail
value of the subscription to
assist Paddle in updating the correct user.There is currently no way to
modify a user's email address through the Paddle
API. When a user wants to update their email address
within Paddle, the only way for them to do so is to
contact Paddle customer support. When communicating
with Paddle, they need to provide the
paddleEmail
value of the subscription
to assist Paddle in updating the correct
user.
PricesPrices
Paddle allows you to customize prices per currency,
essentially allowing you to configure different prices
for different countries. Cashier Paddle allows you to
retrieve all of the prices for a given product using the
productPrices
method. This method accepts
the product IDs of the products you wish to retrieve
prices for:Paddle allows you to
customize prices per currency, essentially allowing
you to configure different prices for different
countries. Cashier Paddle allows you to retrieve all
of the prices for a given product using the
productPrices
method. This method
accepts the product IDs of the products you wish to
retrieve prices for:
use Laravel\Paddle\Cashier;
$prices = Cashier::productPrices([123, 456]);
The currency will be determined based on the IP address of the request; however, you may optionally provide a specific country to retrieve prices for:The currency will be determined based on the IP address of the request; however, you may optionally provide a specific country to retrieve prices for:
use Laravel\Paddle\Cashier;
$prices = Cashier::productPrices([123, 456], ['customer_country' => 'BE']);
After retrieving the prices you may display them however you wish:After retrieving the prices you may display them however you wish:
<ul>
@foreach ($prices as $price)
<li>{{ $price->product_title }} - {{ $price->price()->gross() }}</li>
@endforeach
</ul>
You may also display the net price (excludes tax) and display the tax amount separately:You may also display the net price (excludes tax) and display the tax amount separately:
<ul>
@foreach ($prices as $price)
<li>{{ $price->product_title }} - {{ $price->price()->net() }} ( {{ $price->price()->tax() }} tax)</li>
@endforeach
</ul>
If you retrieved prices for subscription plans you can display their initial and recurring price separately:If you retrieved prices for subscription plans you can display their initial and recurring price separately:
<ul>
@foreach ($prices as $price)
<li>{{ $price->product_title }} - Initial: {{ $price->initialPrice()->gross() }} - Recurring: {{ $price->recurringPrice()->gross() }}</li>
@endforeach
</ul>
For more information, check Paddle's API documentation on prices.For more information, check Paddle's API documentation on prices[https://developer.paddle.com/api-reference/checkout-api/prices/getprices].
CustomersCustomers
If a user is already a customer and you would like to display the prices that apply to that customer, you may do so by retrieving the prices directly from the customer instance:If a user is already a customer and you would like to display the prices that apply to that customer, you may do so by retrieving the prices directly from the customer instance:
use App\Models\User;
$prices = User::find(1)->productPrices([123, 456]);
Internally, Cashier will use the user's paddleCountry
method to retrieve the prices in their currency.
So, for example, a user living in the United States will
see prices in USD while a user in Belgium will see
prices in EUR. If no matching currency can be found the
default currency of the product will be used. You can
customize all prices of a product or subscription plan
in the Paddle control panel.Internally, Cashier will use the
user's paddleCountry
method[#customer-defaults] to retrieve the
prices in their currency. So, for example, a user
living in the United States will see prices in USD
while a user in Belgium will see prices in EUR. If
no matching currency can be found the default
currency of the product will be used. You can
customize all prices of a product or subscription
plan in the Paddle control panel.
CouponsCoupons
You may also choose to display prices after a coupon
reduction. When calling the productPrices
method, coupons may be passed as a comma delimited
string:You may also choose to
display prices after a coupon reduction. When
calling the productPrices
method,
coupons may be passed as a comma delimited
string:
use Laravel\Paddle\Cashier;
$prices = Cashier::productPrices([123, 456], [
'coupons' => 'SUMMERSALE,20PERCENTOFF'
]);
Then, display the calculated prices using the
price
method:Then,
display the calculated prices using the
price
method:
<ul>
@foreach ($prices as $price)
<li>{{ $price->product_title }} - {{ $price->price()->gross() }}</li>
@endforeach
</ul>
You may display the original listed prices (without
coupon discounts) using the listPrice
method:You may display the
original listed prices (without coupon discounts)
using the listPrice
method:
<ul>
@foreach ($prices as $price)
<li>{{ $price->product_title }} - {{ $price->listPrice()->gross() }}</li>
@endforeach
</ul>
Warning
Warning! When using the prices API, Paddle only allows applying coupons to one-time purchase products and not to subscription plans.
When using the prices API, Paddle only allows applying coupons to one-time purchase products and not to subscription plans.
CustomersCustomers
Customer DefaultsCustomer Defaults
Cashier allows you to define some useful defaults for your customers when creating pay links. Setting these defaults allow you to pre-fill a customer's email address, country, and postal code so that they can immediately move on to the payment portion of the checkout widget. You can set these defaults by overriding the following methods on your billable model:Cashier allows you to define some useful defaults for your customers when creating pay links. Setting these defaults allow you to pre-fill a customer's email address, country, and postal code so that they can immediately move on to the payment portion of the checkout widget. You can set these defaults by overriding the following methods on your billable model:
/**
* Get the customer's email address to associate with Paddle.
*
* @return string|null
*/
public function paddleEmail()
{
return $this->email;
}
/**
* Get the customer's country to associate with Paddle.
*
* This needs to be a 2 letter code. See the link below for supported countries.
*
* @return string|null
* @link https://developer.paddle.com/reference/platform-parameters/supported-countries
*/
public function paddleCountry()
{
//
}
/**
* Get the customer's postal code to associate with Paddle.
*
* See the link below for countries which require this.
*
* @return string|null
* @link https://developer.paddle.com/reference/platform-parameters/supported-countries#countries-requiring-postcode
*/
public function paddlePostcode()
{
//
}
These defaults will be used for every action in Cashier that generates a pay link.These defaults will be used for every action in Cashier that generates a pay link[#pay-links].
SubscriptionsSubscriptions
Creating SubscriptionsCreating Subscriptions
To create a subscription, first retrieve an instance of
your billable model from your database, which typically
will be an instance of App\Models\User
.
Once you have retrieved the model instance, you may use
the newSubscription
method to create the
model's subscription pay link:To create a subscription, first
retrieve an instance of your billable model from
your database, which typically will be an instance
of App\Models\User
. Once you have
retrieved the model instance, you may use the
newSubscription
method to create the
model's subscription pay link:
use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$payLink = $request->user()->newSubscription('default', $premium = 12345)
->returnTo(route('home'))
->create();
return view('billing', ['payLink' => $payLink]);
});
The first argument passed to the
newSubscription
method should be the
internal name of the subscription. If your application
only offers a single subscription, you might call this
default
or primary
. This
subscription name is only for internal application usage
and is not meant to be shown to users. In addition, it
should not contain spaces and it should never be changed
after creating the subscription. The second argument
given to the newSubscription
method is the
specific plan the user is subscribing to. This value
should correspond to the plan's identifier in Paddle.
The returnTo
method accepts a URL that your
user will be redirected to after they successfully
complete the checkout.The first
argument passed to the newSubscription
method should be the internal name of the
subscription. If your application only offers a
single subscription, you might call this
default
or primary
. This
subscription name is only for internal application
usage and is not meant to be shown to users. In
addition, it should not contain spaces and it should
never be changed after creating the subscription.
The second argument given to the
newSubscription
method is the specific
plan the user is subscribing to. This value should
correspond to the plan's identifier in Paddle. The
returnTo
method accepts a URL that your
user will be redirected to after they successfully
complete the checkout.
The create
method will create a pay link
which you can use to generate a payment button. The
payment button can be generated using the
paddle-button
Blade component
that is included with Cashier Paddle:The create
method
will create a pay link which you can use to generate
a payment button. The payment button can be
generated using the paddle-button
Blade
component[/docs/{{version}}/blade#components]
that is included with Cashier Paddle:
<x-paddle-button :url="$payLink" class="px-8 py-4">
Subscribe
</x-paddle-button>
After the user has finished their checkout, a
subscription_created
webhook will be
dispatched from Paddle. Cashier will receive this
webhook and setup the subscription for your customer. In
order to make sure all webhooks are properly received
and handled by your application, ensure you have
properly setup
webhook handling.After
the user has finished their checkout, a
subscription_created
webhook will be
dispatched from Paddle. Cashier will receive this
webhook and setup the subscription for your
customer. In order to make sure all webhooks are
properly received and handled by your application,
ensure you have properly setup webhook
handling[#handling-paddle-webhooks].
Additional DetailsAdditional Details
If you would like to specify additional customer or
subscription details, you may do so by passing them as
an array of key / value pairs to the create
method. To learn more about the additional fields
supported by Paddle, check out Paddle's documentation on
generating
pay links:If you would
like to specify additional customer or subscription
details, you may do so by passing them as an array
of key / value pairs to the create
method. To learn more about the additional fields
supported by Paddle, check out Paddle's
documentation on generating pay
links[https://developer.paddle.com/api-reference/product-api/pay-links/createpaylink]:
$payLink = $user->newSubscription('default', $monthly = 12345)
->returnTo(route('home'))
->create([
'vat_number' => $vatNumber,
]);
CouponsCoupons
If you would like to apply a coupon when creating the
subscription, you may use the withCoupon
method:If you would like to
apply a coupon when creating the subscription, you
may use the withCoupon
method:
$payLink = $user->newSubscription('default', $monthly = 12345)
->returnTo(route('home'))
->withCoupon('code')
->create();
MetadataMetadata
You can also pass an array of metadata using the
withMetadata
method:You can also pass an array of
metadata using the withMetadata
method:
$payLink = $user->newSubscription('default', $monthly = 12345)
->returnTo(route('home'))
->withMetadata(['key' => 'value'])
->create();
Warning! When providing metadata, please avoid using
subscription_name
as a metadata key. This key is reserved for internal use by Cashier.Warning
When providing metadata, please avoid usingsubscription_name
as a metadata key. This key is reserved for internal use by Cashier.
Checking Subscription StatusChecking Subscription Status
Once a user is subscribed to your application, you may
check their subscription status using a variety of
convenient methods. First, the subscribed
method returns true
if the user has an
active subscription, even if the subscription is
currently within its trial period:Once a user is subscribed to your
application, you may check their subscription status
using a variety of convenient methods. First, the
subscribed
method returns
true
if the user has an active
subscription, even if the subscription is currently
within its trial period:
if ($user->subscribed('default')) {
//
}
The subscribed
method also makes a great
candidate for a route
middleware, allowing you to filter access to
routes and controllers based on the user's subscription
status:The
subscribed
method also makes a great
candidate for a route
middleware[/docs/{{version}}/middleware],
allowing you to filter access to routes and
controllers based on the user's subscription
status:
<?php
namespace App\Http\Middleware;
use Closure;
class EnsureUserIsSubscribed
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($request->user() && ! $request->user()->subscribed('default')) {
// This user is not a paying customer...
return redirect('billing');
}
return $next($request);
}
}
If you would like to determine if a user is still within
their trial period, you may use the onTrial
method. This method can be useful for determining if you
should display a warning to the user that they are still
on their trial period:If you
would like to determine if a user is still within
their trial period, you may use the
onTrial
method. This method can be
useful for determining if you should display a
warning to the user that they are still on their
trial period:
if ($user->subscription('default')->onTrial()) {
//
}
The subscribedToPlan
method may be used to
determine if the user is subscribed to a given plan
based on a given Paddle plan ID. In this example, we
will determine if the user's default
subscription is actively subscribed to the monthly
plan:The
subscribedToPlan
method may be used to
determine if the user is subscribed to a given plan
based on a given Paddle plan ID. In this example, we
will determine if the user's default
subscription is actively subscribed to the monthly
plan:
if ($user->subscribedToPlan($monthly = 12345, 'default')) {
//
}
By passing an array to the subscribedToPlan
method, you may determine if the user's
default
subscription is actively subscribed
to the monthly or the yearly plan:By passing an array to the
subscribedToPlan
method, you may
determine if the user's default
subscription is actively subscribed to the monthly
or the yearly plan:
if ($user->subscribedToPlan([$monthly = 12345, $yearly = 54321], 'default')) {
//
}
The recurring
method may be used to
determine if the user is currently subscribed and is no
longer within their trial period:The recurring
method
may be used to determine if the user is currently
subscribed and is no longer within their trial
period:
if ($user->subscription('default')->recurring()) {
//
}
Cancelled Subscription StatusCancelled Subscription Status
To determine if the user was once an active subscriber
but has cancelled their subscription, you may use the
cancelled
method:To determine if the user was once
an active subscriber but has cancelled their
subscription, you may use the cancelled
method:
if ($user->subscription('default')->cancelled()) {
//
}
You may also determine if a user has cancelled their
subscription, but are still on their "grace
period" until the subscription fully expires. For
example, if a user cancels a subscription on March 5th
that was originally scheduled to expire on March 10th,
the user is on their "grace period" until
March 10th. Note that the subscribed
method
still returns true
during this time:You may also determine if a user
has cancelled their subscription, but are still on
their "grace period" until the
subscription fully expires. For example, if a user
cancels a subscription on March 5th that was
originally scheduled to expire on March 10th, the
user is on their "grace period" until
March 10th. Note that the subscribed
method still returns true
during this
time:
if ($user->subscription('default')->onGracePeriod()) {
//
}
To determine if the user has cancelled their subscription
and is no longer within their "grace period",
you may use the ended
method:To determine if the user has
cancelled their subscription and is no longer within
their "grace period", you may use the
ended
method:
if ($user->subscription('default')->ended()) {
//
}
Past Due StatusPast Due Status
If a payment fails for a subscription, it will be marked
as past_due
. When your subscription is in
this state it will not be active until the customer has
updated their payment information. You may determine if
a subscription is past due using the
pastDue
method on the subscription
instance:If a payment fails for
a subscription, it will be marked as
past_due
. When your subscription is in
this state it will not be active until the customer
has updated their payment information. You may
determine if a subscription is past due using the
pastDue
method on the subscription
instance:
if ($user->subscription('default')->pastDue()) {
//
}
When a subscription is past due, you should instruct the user to update their payment information. You may configure how past due subscriptions are handled in your Paddle subscription settings.When a subscription is past due, you should instruct the user to update their payment information[#updating-payment-information]. You may configure how past due subscriptions are handled in your Paddle subscription settings[https://vendors.paddle.com/subscription-settings].
If you would like subscriptions to still be considered
active when they are past_due
, you may use
the keepPastDueSubscriptionsActive
method
provided by Cashier. Typically, this method should be
called in the register
method of your
AppServiceProvider
:If you would like subscriptions
to still be considered active when they are
past_due
, you may use the
keepPastDueSubscriptionsActive
method
provided by Cashier. Typically, this method should
be called in the register
method of
your AppServiceProvider
:
use Laravel\Paddle\Cashier;
/**
* Register any application services.
*
* @return void
*/
public function register()
{
Cashier::keepPastDueSubscriptionsActive();
}
Warning! When a subscription is in a
past_due
state it cannot be changed until payment information has been updated. Therefore, theswap
andupdateQuantity
methods will throw an exception when the subscription is in apast_due
state.Warning
When a subscription is in apast_due
state it cannot be changed until payment information has been updated. Therefore, theswap
andupdateQuantity
methods will throw an exception when the subscription is in apast_due
state.
Subscription ScopesSubscription Scopes
Most subscription states are also available as query scopes so that you may easily query your database for subscriptions that are in a given state:Most subscription states are also available as query scopes so that you may easily query your database for subscriptions that are in a given state:
// Get all active subscriptions...
$subscriptions = Subscription::query()->active()->get();
// Get all of the cancelled subscriptions for a user...
$subscriptions = $user->subscriptions()->cancelled()->get();
A complete list of available scopes is available below:A complete list of available scopes is available below:
Subscription::query()->active();
Subscription::query()->onTrial();
Subscription::query()->notOnTrial();
Subscription::query()->pastDue();
Subscription::query()->recurring();
Subscription::query()->ended();
Subscription::query()->paused();
Subscription::query()->notPaused();
Subscription::query()->onPausedGracePeriod();
Subscription::query()->notOnPausedGracePeriod();
Subscription::query()->cancelled();
Subscription::query()->notCancelled();
Subscription::query()->onGracePeriod();
Subscription::query()->notOnGracePeriod();
Subscription Single ChargesSubscription Single Charges
Subscription single charges allow you to charge subscribers with a one-time charge on top of their subscriptions:Subscription single charges allow you to charge subscribers with a one-time charge on top of their subscriptions:
$response = $user->subscription('default')->charge(12.99, 'Support Add-on');
In contrast to single charges, this method will immediately charge the customer's stored payment method for the subscription. The charge amount should always be defined in the currency of the subscription.In contrast to single charges[#single-charges], this method will immediately charge the customer's stored payment method for the subscription. The charge amount should always be defined in the currency of the subscription.
Updating Payment InformationUpdating Payment Information
Paddle always saves a payment method per subscription. If
you want to update the default payment method for a
subscription, you should first generate a subscription
"update URL" using the updateUrl
method on the subscription model:Paddle always saves a payment
method per subscription. If you want to update the
default payment method for a subscription, you
should first generate a subscription "update
URL" using the updateUrl
method on
the subscription model:
use App\Models\User;
$user = User::find(1);
$updateUrl = $user->subscription('default')->updateUrl();
Then, you may use the generated URL in combination with
Cashier's provided paddle-button
Blade
component to allow the user to initiate the Paddle
widget and update their payment information:Then, you may use the generated
URL in combination with Cashier's provided
paddle-button
Blade component to allow
the user to initiate the Paddle widget and update
their payment information:
<x-paddle-button :url="$updateUrl" class="px-8 py-4">
Update Card
</x-paddle-button>
When a user has finished updating their information, a
subscription_updated
webhook will be
dispatched by Paddle and the subscription details will
be updated in your application's database.When a user has finished updating
their information, a
subscription_updated
webhook will be
dispatched by Paddle and the subscription details
will be updated in your application's
database.
Changing PlansChanging Plans
After a user has subscribed to your application, they may
occasionally want to change to a new subscription plan.
To update the subscription plan for a user, you should
pass the Paddle plan's identifier to the subscription's
swap
method:After
a user has subscribed to your application, they may
occasionally want to change to a new subscription
plan. To update the subscription plan for a user,
you should pass the Paddle plan's identifier to the
subscription's swap
method:
use App\Models\User;
$user = User::find(1);
$user->subscription('default')->swap($premium = 34567);
If you would like to swap plans and immediately invoice
the user instead of waiting for their next billing
cycle, you may use the swapAndInvoice
method:If you would like to
swap plans and immediately invoice the user instead
of waiting for their next billing cycle, you may use
the swapAndInvoice
method:
$user = User::find(1);
$user->subscription('default')->swapAndInvoice($premium = 34567);
Paddle documentation.Warning
Warning! Plans may not be swapped when a trial is active. For additional information regarding this limitation, please see the
Plans may not be swapped when a trial is active. For additional information regarding this limitation, please see the Paddle documentation[https://developer.paddle.com/api-reference/subscription-api/users/updateuser#usage-notes].
ProrationsProrations
By default, Paddle prorates charges when swapping between
plans. The noProrate
method may be used to
update the subscriptions without prorating the
charges:By default, Paddle
prorates charges when swapping between plans. The
noProrate
method may be used to update
the subscriptions without prorating the
charges:
$user->subscription('default')->noProrate()->swap($premium = 34567);
Subscription QuantitySubscription Quantity
Sometimes subscriptions are affected by
"quantity". For example, a project management
application might charge $10 per month per project. To
easily increment or decrement your subscription's
quantity, use the incrementQuantity
and
decrementQuantity
methods:Sometimes subscriptions are
affected by "quantity". For example, a
project management application might charge $10 per
month per project. To easily increment or decrement
your subscription's quantity, use the
incrementQuantity
and
decrementQuantity
methods:
$user = User::find(1);
$user->subscription('default')->incrementQuantity();
// Add five to the subscription's current quantity...
$user->subscription('default')->incrementQuantity(5);
$user->subscription('default')->decrementQuantity();
// Subtract five from the subscription's current quantity...
$user->subscription('default')->decrementQuantity(5);
Alternatively, you may set a specific quantity using the
updateQuantity
method:Alternatively, you may set a
specific quantity using the
updateQuantity
method:
$user->subscription('default')->updateQuantity(10);
The noProrate
method may be used to update
the subscription's quantity without prorating the
charges:The
noProrate
method may be used to update
the subscription's quantity without prorating the
charges:
$user->subscription('default')->noProrate()->updateQuantity(10);
Subscription ModifiersSubscription Modifiers
Subscription modifiers allow you to implement metered billing or extend subscriptions with add-ons.Subscription modifiers allow you to implement metered billing[https://developer.paddle.com/guides/how-tos/subscriptions/metered-billing#using-subscription-price-modifiers] or extend subscriptions with add-ons.
For example, you might want to offer a "Premium Support" add-on with your standard subscription. You can create this modifier like so:For example, you might want to offer a "Premium Support" add-on with your standard subscription. You can create this modifier like so:
$modifier = $user->subscription('default')->newModifier(12.99)->create();
The example above will add a $12.99 add-on to the
subscription. By default, this charge will recur on
every interval you have configured for the subscription.
If you would like, you can add a readable description to
the modifier using the modifier's
description
method:The example above will add a
$12.99 add-on to the subscription. By default, this
charge will recur on every interval you have
configured for the subscription. If you would like,
you can add a readable description to the modifier
using the modifier's description
method:
$modifier = $user->subscription('default')->newModifier(12.99)
->description('Premium Support')
->create();
To illustrate how to implement metered billing using modifiers, imagine your application charges per SMS message sent by the user. First, you should create a $0 plan in your Paddle dashboard. Once the user has been subscribed to this plan, you can add modifiers representing each individual charge to the subscription:To illustrate how to implement metered billing using modifiers, imagine your application charges per SMS message sent by the user. First, you should create a $0 plan in your Paddle dashboard. Once the user has been subscribed to this plan, you can add modifiers representing each individual charge to the subscription:
$modifier = $user->subscription('default')->newModifier(0.99)
->description('New text message')
->oneTime()
->create();
As you can see, we invoked the oneTime
method when creating this modifier. This method will
ensure the modifier is only charged once and does not
recur every billing interval.As
you can see, we invoked the oneTime
method when creating this modifier. This method will
ensure the modifier is only charged once and does
not recur every billing interval.
Retrieving ModifiersRetrieving Modifiers
You may retrieve a list of all modifiers for a
subscription via the modifiers
method:You may retrieve a list of all
modifiers for a subscription via the
modifiers
method:
$modifiers = $user->subscription('default')->modifiers();
foreach ($modifiers as $modifier) {
$modifier->amount(); // $0.99
$modifier->description; // New text message.
}
Deleting ModifiersDeleting Modifiers
Modifiers may be deleted by invoking the
delete
method on a
Laravel\Paddle\Modifier
instance:Modifiers may be deleted by
invoking the delete
method on a
Laravel\Paddle\Modifier
instance:
$modifier->delete();
Multiple SubscriptionsMultiple Subscriptions
Paddle allows your customers to have multiple subscriptions simultaneously. For example, you may run a gym that offers a swimming subscription and a weight-lifting subscription, and each subscription may have different pricing. Of course, customers should be able to subscribe to either or both plans.Paddle allows your customers to have multiple subscriptions simultaneously. For example, you may run a gym that offers a swimming subscription and a weight-lifting subscription, and each subscription may have different pricing. Of course, customers should be able to subscribe to either or both plans.
When your application creates subscriptions, you may
provide the name of the subscription to the
newSubscription
method. The name may be any
string that represents the type of subscription the user
is initiating:When your
application creates subscriptions, you may provide
the name of the subscription to the
newSubscription
method. The name may be
any string that represents the type of subscription
the user is initiating:
use Illuminate\Http\Request;
Route::post('/swimming/subscribe', function (Request $request) {
$request->user()
->newSubscription('swimming', $swimmingMonthly = 12345)
->create($request->paymentMethodId);
// ...
});
In this example, we initiated a monthly swimming
subscription for the customer. However, they may want to
swap to a yearly subscription at a later time. When
adjusting the customer's subscription, we can simply
swap the price on the swimming
subscription:In this example,
we initiated a monthly swimming subscription for the
customer. However, they may want to swap to a yearly
subscription at a later time. When adjusting the
customer's subscription, we can simply swap the
price on the swimming
subscription:
$user->subscription('swimming')->swap($swimmingYearly = 34567);
Of course, you may also cancel the subscription entirely:Of course, you may also cancel the subscription entirely:
$user->subscription('swimming')->cancel();
Pausing SubscriptionsPausing Subscriptions
To pause a subscription, call the pause
method on the user's subscription:To pause a subscription, call the
pause
method on the user's
subscription:
$user->subscription('default')->pause();
When a subscription is paused, Cashier will automatically
set the paused_from
column in your
database. This column is used to know when the
paused
method should begin returning
true
. For example, if a customer pauses a
subscription on March 1st, but the subscription was not
scheduled to recur until March 5th, the
paused
method will continue to return
false
until March 5th. This is done because
a user is typically allowed to continue using an
application until the end of their billing cycle.When a subscription is paused,
Cashier will automatically set the
paused_from
column in your database.
This column is used to know when the
paused
method should begin returning
true
. For example, if a customer pauses
a subscription on March 1st, but the subscription
was not scheduled to recur until March 5th, the
paused
method will continue to return
false
until March 5th. This is done
because a user is typically allowed to continue
using an application until the end of their billing
cycle.
You may determine if a user has paused their subscription
but are still on their "grace period" using
the onPausedGracePeriod
method:You may determine if a user has
paused their subscription but are still on their
"grace period" using the
onPausedGracePeriod
method:
if ($user->subscription('default')->onPausedGracePeriod()) {
//
}
To resume a paused a subscription, you may call the
unpause
method on the user's
subscription:To resume a paused
a subscription, you may call the
unpause
method on the user's
subscription:
$user->subscription('default')->unpause();
Warning
Warning! A subscription cannot be modified while it is paused. If you want to swap to a different plan or update quantities you must resume the subscription first.
A subscription cannot be modified while it is paused. If you want to swap to a different plan or update quantities you must resume the subscription first.
Cancelling SubscriptionsCancelling Subscriptions
To cancel a subscription, call the cancel
method on the user's subscription:To cancel a subscription, call
the cancel
method on the user's
subscription:
$user->subscription('default')->cancel();
When a subscription is cancelled, Cashier will
automatically set the ends_at
column in
your database. This column is used to know when the
subscribed
method should begin returning
false
. For example, if a customer cancels a
subscription on March 1st, but the subscription was not
scheduled to end until March 5th, the
subscribed
method will continue to return
true
until March 5th. This is done because
a user is typically allowed to continue using an
application until the end of their billing cycle.When a subscription is cancelled,
Cashier will automatically set the
ends_at
column in your database. This
column is used to know when the
subscribed
method should begin
returning false
. For example, if a
customer cancels a subscription on March 1st, but
the subscription was not scheduled to end until
March 5th, the subscribed
method will
continue to return true
until March
5th. This is done because a user is typically
allowed to continue using an application until the
end of their billing cycle.
You may determine if a user has cancelled their
subscription but are still on their "grace
period" using the onGracePeriod
method:You may determine if a
user has cancelled their subscription but are still
on their "grace period" using the
onGracePeriod
method:
if ($user->subscription('default')->onGracePeriod()) {
//
}
If you wish to cancel a subscription immediately, you may
call the cancelNow
method on the user's
subscription:If you wish to
cancel a subscription immediately, you may call the
cancelNow
method on the user's
subscription:
$user->subscription('default')->cancelNow();
Warning
Warning! Paddle's subscriptions cannot be resumed after cancellation. If your customer wishes to resume their subscription, they will have to subscribe to a new subscription.
Paddle's subscriptions cannot be resumed after cancellation. If your customer wishes to resume their subscription, they will have to subscribe to a new subscription.
Subscription TrialsSubscription Trials
With Payment Method Up FrontWith Payment Method Up Front
Warning
Warning! While trialing and collecting payment method details up front, Paddle prevents any subscription changes such as swapping plans or updating quantities. If you want to allow a customer to swap plans during a trial the subscription must be cancelled and recreated.
While trialing and collecting payment method details up front, Paddle prevents any subscription changes such as swapping plans or updating quantities. If you want to allow a customer to swap plans during a trial the subscription must be cancelled and recreated.
If you would like to offer trial periods to your
customers while still collecting payment method
information up front, you should use the
trialDays
method when creating your
subscription pay links:If you
would like to offer trial periods to your customers
while still collecting payment method information up
front, you should use the trialDays
method when creating your subscription pay
links:
use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$payLink = $request->user()->newSubscription('default', $monthly = 12345)
->returnTo(route('home'))
->trialDays(10)
->create();
return view('billing', ['payLink' => $payLink]);
});
This method will set the trial period ending date on the subscription record within your application's database, as well as instruct Paddle to not begin billing the customer until after this date.This method will set the trial period ending date on the subscription record within your application's database, as well as instruct Paddle to not begin billing the customer until after this date.
Warning
Warning! If the customer's subscription is not cancelled before the trial ending date they will be charged as soon as the trial expires, so you should be sure to notify your users of their trial ending date.
If the customer's subscription is not cancelled before the trial ending date they will be charged as soon as the trial expires, so you should be sure to notify your users of their trial ending date.
You may determine if the user is within their trial
period using either the onTrial
method of
the user instance or the onTrial
method of
the subscription instance. The two examples below are
equivalent:You may determine if
the user is within their trial period using either
the onTrial
method of the user instance
or the onTrial
method of the
subscription instance. The two examples below are
equivalent:
if ($user->onTrial('default')) {
//
}
if ($user->subscription('default')->onTrial()) {
//
}
To determine if an existing trial has expired, you may
use the hasExpiredTrial
methods:To determine if an existing trial
has expired, you may use the
hasExpiredTrial
methods:
if ($user->hasExpiredTrial('default')) {
//
}
if ($user->subscription('default')->hasExpiredTrial()) {
//
}
Defining Trial Days In Paddle / CashierDefining Trial Days In Paddle / Cashier
You may choose to define how many trial days your plan's
receive in the Paddle dashboard or always pass them
explicitly using Cashier. If you choose to define your
plan's trial days in Paddle you should be aware that new
subscriptions, including new subscriptions for a
customer that had a subscription in the past, will
always receive a trial period unless you explicitly call
the trialDays(0)
method.You may choose to define how many
trial days your plan's receive in the Paddle
dashboard or always pass them explicitly using
Cashier. If you choose to define your plan's trial
days in Paddle you should be aware that new
subscriptions, including new subscriptions for a
customer that had a subscription in the past, will
always receive a trial period unless you explicitly
call the trialDays(0)
method.
Without Payment Method Up FrontWithout Payment Method Up Front
If you would like to offer trial periods without
collecting the user's payment method information up
front, you may set the trial_ends_at
column
on the customer record attached to your user to your
desired trial ending date. This is typically done during
user registration:If you would
like to offer trial periods without collecting the
user's payment method information up front, you may
set the trial_ends_at
column on the
customer record attached to your user to your
desired trial ending date. This is typically done
during user registration:
use App\Models\User;
$user = User::create([
// ...
]);
$user->createAsCustomer([
'trial_ends_at' => now()->addDays(10)
]);
Cashier refers to this type of trial as a "generic
trial", since it is not attached to any existing
subscription. The onTrial
method on the
User
instance will return true
if the current date is not past the value of
trial_ends_at
:Cashier refers to this type of
trial as a "generic trial", since it is
not attached to any existing subscription. The
onTrial
method on the User
instance will return true
if the
current date is not past the value of
trial_ends_at
:
if ($user->onTrial()) {
// User is within their trial period...
}
Once you are ready to create an actual subscription for
the user, you may use the newSubscription
method as usual:Once you are
ready to create an actual subscription for the user,
you may use the newSubscription
method
as usual:
use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$payLink = $user->newSubscription('default', $monthly = 12345)
->returnTo(route('home'))
->create();
return view('billing', ['payLink' => $payLink]);
});
To retrieve the user's trial ending date, you may use the
trialEndsAt
method. This method will return
a Carbon date instance if a user is on a trial or
null
if they aren't. You may also pass an
optional subscription name parameter if you would like
to get the trial ending date for a specific subscription
other than the default one:To
retrieve the user's trial ending date, you may use
the trialEndsAt
method. This method
will return a Carbon date instance if a user is on a
trial or null
if they aren't. You may
also pass an optional subscription name parameter if
you would like to get the trial ending date for a
specific subscription other than the default
one:
if ($user->onTrial()) {
$trialEndsAt = $user->trialEndsAt('main');
}
You may use the onGenericTrial
method if you
wish to know specifically that the user is within their
"generic" trial period and has not created an
actual subscription yet:You may
use the onGenericTrial
method if you
wish to know specifically that the user is within
their "generic" trial period and has not
created an actual subscription yet:
if ($user->onGenericTrial()) {
// User is within their "generic" trial period...
}
Warning
Warning! There is no way to extend or modify a trial period on a Paddle subscription after it has been created.
There is no way to extend or modify a trial period on a Paddle subscription after it has been created.
Handling Paddle WebhooksHandling Paddle Webhooks
Paddle can notify your application of a variety of events via webhooks. By default, a route that points to Cashier's webhook controller is registered by the Cashier service provider. This controller will handle all incoming webhook requests.Paddle can notify your application of a variety of events via webhooks. By default, a route that points to Cashier's webhook controller is registered by the Cashier service provider. This controller will handle all incoming webhook requests.
By default, this controller will automatically handle cancelling subscriptions that have too many failed charges (as defined by your Paddle dunning settings), subscription updates, and payment method changes; however, as we'll soon discover, you can extend this controller to handle any Paddle webhook event you like.By default, this controller will automatically handle cancelling subscriptions that have too many failed charges (as defined by your Paddle dunning settings[https://vendors.paddle.com/recover-settings#dunning-form-id]), subscription updates, and payment method changes; however, as we'll soon discover, you can extend this controller to handle any Paddle webhook event you like.
To ensure your application can handle Paddle webhooks, be
sure to configure
the webhook URL in the Paddle control panel. By
default, Cashier's webhook controller responds to the
/paddle/webhook
URL path. The full list of
all webhooks you should enable in the Paddle control
panel are:To ensure your
application can handle Paddle webhooks, be sure to
configure the webhook URL in the Paddle control
panel[https://vendors.paddle.com/alerts-webhooks].
By default, Cashier's webhook controller responds to
the /paddle/webhook
URL path. The full
list of all webhooks you should enable in the Paddle
control panel are:
- Subscription CreatedSubscription Created
- Subscription UpdatedSubscription Updated
- Subscription CancelledSubscription Cancelled
- Payment SucceededPayment Succeeded
- Subscription Payment SucceededSubscription Payment Succeeded
webhook signature verification middleware.Warning
Warning! Make sure you protect incoming requests with Cashier's included
Make sure you protect incoming requests with Cashier's included webhook signature verification[/docs/{{version}}/cashier-paddle#verifying-webhook-signatures] middleware.
Webhooks & CSRF ProtectionWebhooks & CSRF Protection
Since Paddle webhooks need to bypass
Laravel's CSRF
protection, be sure to list
the URI as an exception in your
App\Http\Middleware\VerifyCsrfToken
middleware or list the route outside
of the web
middleware
group:Since
Paddle webhooks need to bypass
Laravel's CSRF
protection[/docs/{{version}}/csrf],
be sure to list the URI as an
exception in your
App\Http\Middleware\VerifyCsrfToken
middleware or list the route
outside of the web
middleware group:
protected $except = [
'paddle/*',
];
Webhooks & Local DevelopmentWebhooks & Local Development
For Paddle to be able to send your application webhooks during local development, you will need to expose your application via a site sharing service such as Ngrok or Expose. If you are developing your application locally using Laravel Sail, you may use Sail's site sharing command.For Paddle to be able to send your application webhooks during local development, you will need to expose your application via a site sharing service such as Ngrok[https://ngrok.com/] or Expose[https://expose.dev/docs/introduction]. If you are developing your application locally using Laravel Sail[/docs/{{version}}/sail], you may use Sail's site sharing command[/docs/{{version}}/sail#sharing-your-site].
Defining Webhook Event HandlersDefining Webhook Event Handlers
Cashier automatically handles subscription cancellation on failed charges and other common Paddle webhooks. However, if you have additional webhook events you would like to handle, you may do so by listening to the following events that are dispatched by Cashier:Cashier automatically handles subscription cancellation on failed charges and other common Paddle webhooks. However, if you have additional webhook events you would like to handle, you may do so by listening to the following events that are dispatched by Cashier:
Laravel\Paddle\Events\WebhookReceived
Laravel\Paddle\Events\WebhookReceived
Laravel\Paddle\Events\WebhookHandled
Laravel\Paddle\Events\WebhookHandled
Both events contain the full
payload of the Paddle
webhook. For example, if you
wish to handle the
invoice.payment_succeeded
webhook, you may register a
listener
that will handle the
event:Both
events contain the full
payload of the Paddle
webhook. For example, if
you wish to handle the
invoice.payment_succeeded
webhook, you may
register a
listener[/docs/{{version}}/events#defining-listeners]
that will handle the
event:
<?php
namespace App\Listeners;
use Laravel\Paddle\Events\WebhookReceived;
class PaddleEventListener
{
/**
* Handle received Paddle webhooks.
*
* @param \Laravel\Paddle\Events\WebhookReceived $event
* @return void
*/
public function handle(WebhookReceived $event)
{
if ($event->payload['alert_name'] === 'payment_succeeded') {
// Handle the incoming event...
}
}
}
Once your listener has been
defined, you may register it
within your application's
EventServiceProvider
:Once
your listener has been
defined, you may
register it within your
application's
EventServiceProvider
:
<?php
namespace App\Providers;
use App\Listeners\PaddleEventListener;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Laravel\Paddle\Events\WebhookReceived;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
WebhookReceived::class => [
PaddleEventListener::class,
],
];
}
Cashier also emit events dedicated to the type of the received webhook. In addition to the full payload from Paddle, they also contain the relevant models that were used to process the webhook such as the billable model, the subscription, or the receipt:Cashier also emit events dedicated to the type of the received webhook. In addition to the full payload from Paddle, they also contain the relevant models that were used to process the webhook such as the billable model, the subscription, or the receipt:
Laravel\Paddle\Events\PaymentSucceeded
Laravel\Paddle\Events\PaymentSucceeded
Laravel\Paddle\Events\SubscriptionPaymentSucceeded
Laravel\Paddle\Events\SubscriptionPaymentSucceeded
Laravel\Paddle\Events\SubscriptionCreated
Laravel\Paddle\Events\SubscriptionCreated
Laravel\Paddle\Events\SubscriptionUpdated
Laravel\Paddle\Events\SubscriptionUpdated
Laravel\Paddle\Events\SubscriptionCancelled
Laravel\Paddle\Events\SubscriptionCancelled
You can
also
override
the
default,
built-in
webhook
route by
defining
the
CASHIER_WEBHOOK
environment
variable
in your
application's
.env
file.
This
value
should
be the
full URL
to your
webhook
route
and
needs to
match
the URL
set in
your
Paddle
control
panel:You
can
also
override
the
default,
built-in
webhook
route
by
defining
the
CASHIER_WEBHOOK
environment
variable
in
your
application's
.env
file.
This
value
should
be
the
full
URL
to
your
webhook
route
and
needs
to
match
the
URL
set
in
your
Paddle
control
panel:
CASHIER_WEBHOOK=https://example.com/my-paddle-webhook-url
Verifying Webhook SignaturesVerifying Webhook Signatures
To secure your webhooks, you may use Paddle's webhook signatures. For convenience, Cashier automatically includes a middleware which validates that the incoming Paddle webhook request is valid.To secure your webhooks, you may use Paddle's webhook signatures[https://developer.paddle.com/webhook-reference/verifying-webhooks]. For convenience, Cashier automatically includes a middleware which validates that the incoming Paddle webhook request is valid.
To enable
webhook
verification,
ensure
that the
PADDLE_PUBLIC_KEY
environment
variable
is
defined
in your
application's
.env
file.
The
public
key may
be
retrieved
from
your
Paddle
account
dashboard.To
enable
webhook
verification,
ensure
that
the
PADDLE_PUBLIC_KEY
environment
variable
is
defined
in
your
application's
.env
file.
The
public
key
may
be
retrieved
from
your
Paddle
account
dashboard.
Single ChargesSingle Charges
Simple ChargeSimple Charge
If you
would
like to
make a
one-time
charge
against
a
customer,
you may
use the
charge
method
on a
billable
model
instance
to
generate
a pay
link for
the
charge.
The
charge
method
accepts
the
charge
amount
(float)
as its
first
argument
and a
charge
description
as its
second
argument:If
you
would
like
to
make
a
one-time
charge
against
a
customer,
you
may
use
the
charge
method
on a
billable
model
instance
to
generate
a
pay
link
for
the
charge.
The
charge
method
accepts
the
charge
amount
(float)
as
its
first
argument
and
a
charge
description
as
its
second
argument:
use Illuminate\Http\Request;
Route::get('/store', function (Request $request) {
return view('store', [
'payLink' => $user->charge(12.99, 'Action Figure')
]);
});
After
generating
the pay
link,
you may
use
Cashier's
provided
paddle-button
Blade
component
to allow
the user
to
initiate
the
Paddle
widget
and
complete
the
charge:After
generating
the
pay
link,
you
may
use
Cashier's
provided
paddle-button
Blade
component
to
allow
the
user
to
initiate
the
Paddle
widget
and
complete
the
charge:
<x-paddle-button :url="$payLink" class="px-8 py-4">
Buy
</x-paddle-button>
The
charge
method
accepts
an array
as its
third
argument,
allowing
you to
pass any
options
you wish
to the
underlying
Paddle
pay link
creation.
Please
consult
the
Paddle
documentation
to learn
more
about
the
options
available
to you
when
creating
charges:The
charge
method
accepts
an
array
as
its
third
argument,
allowing
you
to
pass
any
options
you
wish
to
the
underlying
Paddle
pay
link
creation.
Please
consult
the
Paddle
documentation[https://developer.paddle.com/api-reference/product-api/pay-links/createpaylink]
to
learn
more
about
the
options
available
to
you
when
creating
charges:
$payLink = $user->charge(12.99, 'Action Figure', [
'custom_option' => $value,
]);
Charges
happen
in the
currency
specified
in the
cashier.currency
configuration
option.
By
default,
this is
set to
USD. You
may
override
the
default
currency
by
defining
the
CASHIER_CURRENCY
environment
variable
in your
application's
.env
file:Charges
happen
in
the
currency
specified
in
the
cashier.currency
configuration
option.
By
default,
this
is
set
to
USD.
You
may
override
the
default
currency
by
defining
the
CASHIER_CURRENCY
environment
variable
in
your
application's
.env
file:
CASHIER_CURRENCY=EUR
You can also override prices per currency using Paddle's dynamic pricing matching system. To do so, pass an array of prices instead of a fixed amount:You can also override prices per currency[https://developer.paddle.com/api-reference/product-api/pay-links/createpaylink#price-overrides] using Paddle's dynamic pricing matching system. To do so, pass an array of prices instead of a fixed amount:
$payLink = $user->charge([
'USD:19.99',
'EUR:15.99',
], 'Action Figure');
Charging ProductsCharging Products
If you
would
like to
make a
one-time
charge
against
a
specific
product
configured
within
Paddle,
you may
use the
chargeProduct
method
on a
billable
model
instance
to
generate
a pay
link:If
you
would
like
to
make
a
one-time
charge
against
a
specific
product
configured
within
Paddle,
you
may
use
the
chargeProduct
method
on a
billable
model
instance
to
generate
a
pay
link:
use Illuminate\Http\Request;
Route::get('/store', function (Request $request) {
return view('store', [
'payLink' => $request->user()->chargeProduct($productId = 123)
]);
});
Then, you
may
provide
the pay
link to
the
paddle-button
component
to allow
the user
to
initialize
the
Paddle
widget:Then,
you
may
provide
the
pay
link
to
the
paddle-button
component
to
allow
the
user
to
initialize
the
Paddle
widget:
<x-paddle-button :url="$payLink" class="px-8 py-4">
Buy
</x-paddle-button>
The
chargeProduct
method
accepts
an array
as its
second
argument,
allowing
you to
pass any
options
you wish
to the
underlying
Paddle
pay link
creation.
Please
consult
the
Paddle
documentation
regarding
the
options
that are
available
to you
when
creating
charges:The
chargeProduct
method
accepts
an
array
as
its
second
argument,
allowing
you
to
pass
any
options
you
wish
to
the
underlying
Paddle
pay
link
creation.
Please
consult
the
Paddle
documentation[https://developer.paddle.com/api-reference/product-api/pay-links/createpaylink]
regarding
the
options
that
are
available
to
you
when
creating
charges:
$payLink = $user->chargeProduct($productId, [
'custom_option' => $value,
]);
Refunding OrdersRefunding Orders
If you
need to
refund a
Paddle
order,
you may
use the
refund
method.
This
method
accepts
the
Paddle
order ID
as its
first
argument.
You may
retrieve
the
receipts
for a
given
billable
model
using
the
receipts
method:If
you
need
to
refund
a
Paddle
order,
you
may
use
the
refund
method.
This
method
accepts
the
Paddle
order
ID
as
its
first
argument.
You
may
retrieve
the
receipts
for
a
given
billable
model
using
the
receipts
method:
use App\Models\User;
$user = User::find(1);
$receipt = $user->receipts()->first();
$refundRequestId = $user->refund($receipt->order_id);
You may optionally specify a specific amount to refund as well as a reason for the refund:You may optionally specify a specific amount to refund as well as a reason for the refund:
$receipt = $user->receipts()->first();
$refundRequestId = $user->refund(
$receipt->order_id, 5.00, 'Unused product time'
);
Note:You can use the
$refundRequestId
as a reference for the refund when contacting Paddle support.Note
You can use the$refundRequestId
as a reference for the refund when contacting Paddle support.
ReceiptsReceipts
You may
easily
retrieve
an array
of a
billable
model's
receipts
via the
receipts
property:You
may
easily
retrieve
an
array
of a
billable
model's
receipts
via
the
receipts
property:
use App\Models\User;
$user = User::find(1);
$receipts = $user->receipts;
When listing the receipts for the customer, you may use the receipt instance's methods to display the relevant receipt information. For example, you may wish to list every receipt in a table, allowing the user to easily download any of the receipts:When listing the receipts for the customer, you may use the receipt instance's methods to display the relevant receipt information. For example, you may wish to list every receipt in a table, allowing the user to easily download any of the receipts:
<table>
@foreach ($receipts as $receipt)
<tr>
<td>{{ $receipt->paid_at->toFormattedDateString() }}</td>
<td>{{ $receipt->amount() }}</td>
<td><a href="{{ $receipt->receipt_url }}" target="_blank">Download</a></td>
</tr>
@endforeach
</table>
Past & Upcoming PaymentsPast & Upcoming Payments
You may
use the
lastPayment
and
nextPayment
methods
to
retrieve
and
display
a
customer's
past or
upcoming
payments
for
recurring
subscriptions:You
may
use
the
lastPayment
and
nextPayment
methods
to
retrieve
and
display
a
customer's
past
or
upcoming
payments
for
recurring
subscriptions:
use App\Models\User;
$user = User::find(1);
$subscription = $user->subscription('default');
$lastPayment = $subscription->lastPayment();
$nextPayment = $subscription->nextPayment();
Both of
these
methods
will
return
an
instance
of
Laravel\Paddle\Payment
;
however,
nextPayment
will
return
null
when the
billing
cycle
has
ended
(such as
when a
subscription
has been
cancelled):Both
of
these
methods
will
return
an
instance
of
Laravel\Paddle\Payment
;
however,
nextPayment
will
return
null
when
the
billing
cycle
has
ended
(such
as
when
a
subscription
has
been
cancelled):
Next payment: {{ $nextPayment->amount() }} due on {{ $nextPayment->date()->format('d/m/Y') }}
Handling Failed PaymentsHandling Failed Payments
Subscription payments fail for various reasons, such as expired cards or a card having insufficient funds. When this happens, we recommend that you let Paddle handle payment failures for you. Specifically, you may setup Paddle's automatic billing emails in your Paddle dashboard.Subscription payments fail for various reasons, such as expired cards or a card having insufficient funds. When this happens, we recommend that you let Paddle handle payment failures for you. Specifically, you may setup Paddle's automatic billing emails[https://vendors.paddle.com/subscription-settings] in your Paddle dashboard.
Alternatively,
you can
perform
more
precise
customization
by listening
for the
subscription_payment_failed
Paddle
event
via the
WebhookReceived
event
dispatched
by
Cashier.
You
should
also
ensure
the
"Subscription
Payment
Failed"
option
is
enabled
in the
Webhook
settings
of your
Paddle
dashboard:Alternatively,
you
can
perform
more
precise
customization
by
listening[/docs/{{version}}/events]
for
the
subscription_payment_failed
Paddle
event
via
the
WebhookReceived
event
dispatched
by
Cashier.
You
should
also
ensure
the
"Subscription
Payment
Failed"
option
is
enabled
in
the
Webhook
settings
of
your
Paddle
dashboard:
<?php
namespace App\Listeners;
use Laravel\Paddle\Events\WebhookReceived;
class PaddleEventListener
{
/**
* Handle received Paddle webhooks.
*
* @param \Laravel\Paddle\Events\WebhookReceived $event
* @return void
*/
public function handle(WebhookReceived $event)
{
if ($event->payload['alert_name'] === 'subscription_payment_failed') {
// Handle the failed subscription payment...
}
}
}
Once your
listener
has been
defined,
you
should
register
it
within
your
application's
EventServiceProvider
:Once
your
listener
has
been
defined,
you
should
register
it
within
your
application's
EventServiceProvider
:
<?php
namespace App\Providers;
use App\Listeners\PaddleEventListener;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Laravel\Paddle\Events\WebhookReceived;
class EventServiceProvider extends ServiceProvider
{
protected $listen = [
WebhookReceived::class => [
PaddleEventListener::class,
],
];
}
TestingTesting
While testing, you should manually test your billing flow to make sure your integration works as expected.While testing, you should manually test your billing flow to make sure your integration works as expected.
For automated tests, including those executed within a CI environment, you may use Laravel's HTTP Client to fake HTTP calls made to Paddle. Although this does not test the actual responses from Paddle, it does provide a way to test your application without actually calling Paddle's API.For automated tests, including those executed within a CI environment, you may use Laravel's HTTP Client[/docs/{{version}}/http-client#testing] to fake HTTP calls made to Paddle. Although this does not test the actual responses from Paddle, it does provide a way to test your application without actually calling Paddle's API.