IntroductionIntroduction
Warning!
This documentation is for Cashier Paddle 2.x's integration with Paddle Billing. If you're still using Paddle Classic, you should use Cashier Paddle 1.x.[!WARNING]
This documentation is for Cashier Paddle 2.x's integration with Paddle Billing. If you're still using Paddle Classic, you should use Cashier Paddle 1.x[https://github.com/laravel/cashier-paddle/tree/1.x].
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: swapping subscriptions, subscription "quantities", subscription pausing, cancelation 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: swapping subscriptions, subscription "quantities", subscription pausing, cancelation grace periods, and more.
Before digging into Cashier Paddle, we recommend you also review Paddle's concept guides and API documentation.Before digging into Cashier Paddle, we recommend you also review Paddle's concept guides[https://developer.paddle.com/concepts/overview] and API documentation[https://developer.paddle.com/api-reference/overview].
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
Next, you should publish the Cashier migration files
using the vendor:publish
Artisan
command:Next, you should
publish the Cashier migration files using the
vendor:publish
Artisan
command:
php artisan vendor:publish --tag="cashier-migrations"
Then, you should run your application's database
migrations. The Cashier migrations will create a new
customers
table. In addition, new
subscriptions
and
subscription_items
tables will be created
to store all of your customer's subscriptions. Lastly, a
new transactions
table will be created to
store all of the Paddle transactions associated with
your customers:Then, you should
run your application's database migrations. The
Cashier migrations will create a new
customers
table. In addition, new
subscriptions
and
subscription_items
tables will be
created to store all of your customer's
subscriptions. Lastly, a new
transactions
table will be created to
store all of the Paddle transactions associated with
your customers:
php artisan migrate
Warning!
To ensure Cashier properly handles all Paddle events, remember to set up Cashier's webhook handling.[!WARNING]
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://sandbox-login.paddle.com/signup]. 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/concepts/payment-methods/credit-debit-card] 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.
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 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
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_CLIENT_SIDE_TOKEN=your-paddle-client-side-token
PADDLE_API_KEY=your-paddle-api-key
PADDLE_RETAIN_KEY=your-paddle-retain-key
PADDLE_WEBHOOK_SECRET="your-paddle-webhook-secret"
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.
The PADDLE_RETAIN_KEY
is optional and should
only be set if you're using Paddle with Retain.The
PADDLE_RETAIN_KEY
is optional and
should only be set if you're using Paddle with
Retain[https://developer.paddle.com/paddlejs/retain].
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
You can specify a locale to be used when formatting money
values for display on invoices. Internally, Cashier
utilizes PHP's
NumberFormatter
class class to set
the currency locale:You can
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]
class to set the currency locale:
CASHIER_CURRENCY_LOCALE=nl_BE
Warning!
In order to use locales other thanen
, 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\Subscription;
use App\Models\Cashier\Transaction;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Cashier::useSubscriptionModel(Subscription::class);
Cashier::useTransactionModel(Transaction::class);
}
QuickstartQuickstart
Selling ProductsSelling Products
configure Paddle's webhook handling.[!NOTE]Before utilizing Paddle Checkout, you should define Products with fixed prices in your Paddle dashboard. In addition, you should configure Paddle's webhook handling[#handling-paddle-webhooks].
Note: Before utilizing Paddle Checkout, you should define Products with fixed prices in your Paddle dashboard. In addition, you should
Offering product and subscription billing via your application can be intimidating. However, thanks to Cashier and Paddle's Checkout Overlay, you can easily build modern, robust payment integrations.Offering product and subscription billing via your application can be intimidating. However, thanks to Cashier and Paddle's Checkout Overlay[https://www.paddle.com/billing/checkout], you can easily build modern, robust payment integrations.
To charge customers for non-recurring, single-charge products, we'll utilize Cashier to charge customers with Paddle's Checkout Overlay, where they will provide their payment details and confirm their purchase. Once the payment has been made via the Checkout Overlay, the customer will be redirected to a success URL of your choosing within your application:To charge customers for non-recurring, single-charge products, we'll utilize Cashier to charge customers with Paddle's Checkout Overlay, where they will provide their payment details and confirm their purchase. Once the payment has been made via the Checkout Overlay, the customer will be redirected to a success URL of your choosing within your application:
use Illuminate\Http\Request;
Route::get('/buy', function (Request $request) {
$checkout = $request->user()->checkout('pri_deluxe_album')
->returnTo(route('dashboard'));
return view('buy', ['checkout' => $checkout]);
})->name('checkout');
As you can see in the example above, we will utilize
Cashier's provided checkout
method to
create a checkout object to present the customer the
Paddle Checkout Overlay for a given "price
identifier". When using Paddle, "prices"
refer to defined
prices for specific products.As you can see in the example
above, we will utilize Cashier's provided
checkout
method to create a checkout
object to present the customer the Paddle Checkout
Overlay for a given "price identifier".
When using Paddle, "prices" refer to
defined prices for specific
products[https://developer.paddle.com/build/products/create-products-prices].
If necessary, the checkout
method will
automatically create a customer in Paddle and connect
that Paddle customer record to the corresponding user in
your application's database. After completing the
checkout session, the customer will be redirected to a
dedicated success page where you can display an
informational message to the customer.If necessary, the
checkout
method will automatically
create a customer in Paddle and connect that Paddle
customer record to the corresponding user in your
application's database. After completing the
checkout session, the customer will be redirected to
a dedicated success page where you can display an
informational message to the customer.
In the buy
view, we will include a button to
display the Checkout Overlay. The
paddle-button
Blade component is included
with Cashier Paddle; however, you may also manually
render an overlay checkout:In the buy
view, we
will include a button to display the Checkout
Overlay. The paddle-button
Blade
component is included with Cashier Paddle; however,
you may also manually render an overlay
checkout[#manually-rendering-an-overlay-checkout]:
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Buy Product
</x-paddle-button>
Providing Meta Data to Paddle CheckoutProviding Meta Data to Paddle Checkout
When selling products, it's common to keep track of
completed orders and purchased products via
Cart
and Order
models defined
by your own application. When redirecting customers to
Paddle's Checkout Overlay to complete a purchase, you
may need to provide an existing order identifier so that
you can associate the completed purchase with the
corresponding order when the customer is redirected back
to your application.When
selling products, it's common to keep track of
completed orders and purchased products via
Cart
and Order
models
defined by your own application. When redirecting
customers to Paddle's Checkout Overlay to complete a
purchase, you may need to provide an existing order
identifier so that you can associate the completed
purchase with the corresponding order when the
customer is redirected back to your
application.
To accomplish this, you may provide an array of custom
data to the checkout
method. Let's imagine
that a pending Order
is created within our
application when a user begins the checkout process.
Remember, the Cart
and Order
models in this example are illustrative and not provided
by Cashier. You are free to implement these concepts
based on the needs of your own application:To accomplish this, you may
provide an array of custom data to the
checkout
method. Let's imagine that a
pending Order
is created within our
application when a user begins the checkout process.
Remember, the Cart
and
Order
models in this example are
illustrative and not provided by Cashier. You are
free to implement these concepts based on the needs
of your own application:
use App\Models\Cart;
use App\Models\Order;
use Illuminate\Http\Request;
Route::get('/cart/{cart}/checkout', function (Request $request, Cart $cart) {
$order = Order::create([
'cart_id' => $cart->id,
'price_ids' => $cart->price_ids,
'status' => 'incomplete',
]);
$checkout = $request->user()->checkout($order->price_ids)
->customData(['order_id' => $order->id]);
return view('billing', ['checkout' => $checkout]);
})->name('checkout');
As you can see in the example above, when a user begins
the checkout process, we will provide all of the cart /
order's associated Paddle price identifiers to the
checkout
method. Of course, your
application is responsible for associating these items
with the "shopping cart" or order as a
customer adds them. We also provide the order's ID to
the Paddle Checkout Overlay via the
customData
method.As you can see in the example
above, when a user begins the checkout process, we
will provide all of the cart / order's associated
Paddle price identifiers to the
checkout
method. Of course, your
application is responsible for associating these
items with the "shopping cart" or order as
a customer adds them. We also provide the order's ID
to the Paddle Checkout Overlay via the
customData
method.
Of course, you will likely want to mark the order as "complete" once the customer has finished the checkout process. To accomplish this, you may listen to the webhooks dispatched by Paddle and raised via events by Cashier to store order information in your database.Of course, you will likely want to mark the order as "complete" once the customer has finished the checkout process. To accomplish this, you may listen to the webhooks dispatched by Paddle and raised via events by Cashier to store order information in your database.
To get started, listen for the
TransactionCompleted
event dispatched by
Cashier. Typically, you should register the event
listener in the boot
method of your
application's AppServiceProvider
:To get started, listen for the
TransactionCompleted
event dispatched
by Cashier. Typically, you should register the event
listener in the boot
method of your
application's
AppServiceProvider
:
use App\Listeners\CompleteOrder;
use Illuminate\Support\Facades\Event;
use Laravel\Paddle\Events\TransactionCompleted;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Event::listen(TransactionCompleted::class, CompleteOrder::class);
}
In this example, the CompleteOrder
listener
might look like the following:In this example, the
CompleteOrder
listener might look like
the following:
namespace App\Listeners;
use App\Models\Order;
use Laravel\Cashier\Cashier;
use Laravel\Cashier\Events\TransactionCompleted;
class CompleteOrder
{
/**
* Handle the incoming Cashier webhook event.
*/
public function handle(TransactionCompleted $event): void
{
$orderId = $event->payload['data']['custom_data']['order_id'] ?? null;
$order = Order::findOrFail($orderId);
$order->update(['status' => 'completed']);
}
}
Please refer to Paddle's documentation for more
information on the data
contained by the transaction.completed
event.Please refer to
Paddle's documentation for more information on the
data contained by the
transaction.completed
event[https://developer.paddle.com/webhooks/transactions/transaction-completed].
Selling SubscriptionsSelling Subscriptions
Note:
Before utilizing Paddle Checkout, you should define Products with fixed prices in your Paddle dashboard. In addition, you should configure Paddle's webhook handling.[!NOTE]
Before utilizing Paddle Checkout, you should define Products with fixed prices in your Paddle dashboard. In addition, you should configure Paddle's webhook handling[#handling-paddle-webhooks].
Offering product and subscription billing via your application can be intimidating. However, thanks to Cashier and Paddle's Checkout Overlay, you can easily build modern, robust payment integrations.Offering product and subscription billing via your application can be intimidating. However, thanks to Cashier and Paddle's Checkout Overlay[https://www.paddle.com/billing/checkout], you can easily build modern, robust payment integrations.
To learn how to sell subscriptions using Cashier and
Paddle's Checkout Overlay, let's consider the simple
scenario of a subscription service with a basic monthly
(price_basic_monthly
) and yearly
(price_basic_yearly
) plan. These two prices
could be grouped under a "Basic" product
(pro_basic
) in our Paddle dashboard. In
addition, our subscription service might offer an Expert
plan as pro_expert
.To learn how to sell
subscriptions using Cashier and Paddle's Checkout
Overlay, let's consider the simple scenario of a
subscription service with a basic monthly
(price_basic_monthly
) and yearly
(price_basic_yearly
) plan. These two
prices could be grouped under a "Basic"
product (pro_basic
) in our Paddle
dashboard. In addition, our subscription service
might offer an Expert plan as
pro_expert
.
First, let's discover how a customer can subscribe to our
services. Of course, you can imagine the customer might
click a "subscribe" button for the Basic plan
on our application's pricing page. This button will
invoke a Paddle Checkout Overlay for their chosen plan.
To get started, let's initiate a checkout session via
the checkout
method:First, let's discover how a
customer can subscribe to our services. Of course,
you can imagine the customer might click a
"subscribe" button for the Basic plan on
our application's pricing page. This button will
invoke a Paddle Checkout Overlay for their chosen
plan. To get started, let's initiate a checkout
session via the checkout
method:
use Illuminate\Http\Request;
Route::get('/subscribe', function (Request $request) {
$checkout = $request->user()->checkout('price_basic_monthly')
->returnTo(route('dashboard'));
return view('subscribe', ['checkout' => $checkout]);
})->name('subscribe');
In the subscribe
view, we will include a
button to display the Checkout Overlay. The
paddle-button
Blade component is included
with Cashier Paddle; however, you may also manually
render an overlay checkout:In the subscribe
view, we will include a button to display the
Checkout Overlay. The paddle-button
Blade component is included with Cashier Paddle;
however, you may also manually render an overlay
checkout[#manually-rendering-an-overlay-checkout]:
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Subscribe
</x-paddle-button>
Now, when the Subscribe button is clicked, the customer will be able to enter their payment details and initiate their subscription. To know when their subscription has actually started (since some payment methods require a few seconds to process), you should also configure Cashier's webhook handling.Now, when the Subscribe button is clicked, the customer will be able to enter their payment details and initiate their subscription. To know when their subscription has actually started (since some payment methods require a few seconds to process), you should also configure Cashier's webhook handling[#handling-paddle-webhooks].
Now that customers can start subscriptions, we need to
restrict certain portions of our application so that
only subscribed users can access them. Of course, we can
always determine a user's current subscription status
via the subscribed
method provided by
Cashier's Billable
trait:Now that customers can start
subscriptions, we need to restrict certain portions
of our application so that only subscribed users can
access them. Of course, we can always determine a
user's current subscription status via the
subscribed
method provided by Cashier's
Billable
trait:
@if ($user->subscribed())
<p>You are subscribed.</p>
@endif
We can even easily determine if a user is subscribed to specific product or price:We can even easily determine if a user is subscribed to specific product or price:
@if ($user->subscribedToProduct('pro_basic'))
<p>You are subscribed to our Basic product.</p>
@endif
@if ($user->subscribedToPrice('price_basic_monthly'))
<p>You are subscribed to our monthly Basic plan.</p>
@endif
Building a Subscribed MiddlewareBuilding a Subscribed Middleware
For convenience, you may wish to create a middleware which determines if the incoming request is from a subscribed user. Once this middleware has been defined, you may easily assign it to a route to prevent users that are not subscribed from accessing the route:For convenience, you may wish to create a middleware[/docs/{{version}}/middleware] which determines if the incoming request is from a subscribed user. Once this middleware has been defined, you may easily assign it to a route to prevent users that are not subscribed from accessing the route:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class Subscribed
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next): Response
{
if (! $request->user()?->subscribed()) {
// Redirect user to billing page and ask them to subscribe...
return redirect('/subscribe');
}
return $next($request);
}
}
Once the middleware has been defined, you may assign it to a route:Once the middleware has been defined, you may assign it to a route:
use App\Http\Middleware\Subscribed;
Route::get('/dashboard', function () {
// ...
})->middleware([Subscribed::class]);
Allowing Customers to Manage Their Billing PlanAllowing Customers to Manage Their Billing Plan
Of course, customers may want to change their subscription plan to another product or "tier". In our example from above, we'd want to allow the customer to change their plan from a monthly subscription to a yearly subscription. For this you'll need to implement something like a button that leads to the below route:Of course, customers may want to change their subscription plan to another product or "tier". In our example from above, we'd want to allow the customer to change their plan from a monthly subscription to a yearly subscription. For this you'll need to implement something like a button that leads to the below route:
use Illuminate\Http\Request;
Route::put('/subscription/{price}/swap', function (Request $request, $price) {
$user->subscription()->swap($price); // With "$price" being "price_basic_yearly" for this example.
return redirect()->route('dashboard');
})->name('subscription.swap');
Besides swapping plans you'll also need to allow your customers to cancel their subscription. Like swapping plans, provide a button that leads to the following route:Besides swapping plans you'll also need to allow your customers to cancel their subscription. Like swapping plans, provide a button that leads to the following route:
use Illuminate\Http\Request;
Route::put('/subscription/cancel', function (Request $request, $price) {
$user->subscription()->cancel();
return redirect()->route('dashboard');
})->name('subscription.cancel');
And now your subscription will get cancelled at the end of its billing period.And now your subscription will get cancelled at the end of its billing period.
Note:
As long as you have configured Cashier's webhook handling, Cashier will automatically keep your application's Cashier-related database tables in sync by inspecting the incoming webhooks from Paddle. So, for example, when you cancel a customer's subscription via Paddle's dashboard, Cashier will receive the corresponding webhook and mark the subscription as "cancelled" in your application's database.[!NOTE]
As long as you have configured Cashier's webhook handling, Cashier will automatically keep your application's Cashier-related database tables in sync by inspecting the incoming webhooks from Paddle. So, for example, when you cancel a customer's subscription via Paddle's dashboard, Cashier will receive the corresponding webhook and mark the subscription as "cancelled" in your application's database.
Checkout SessionsCheckout Sessions
Most operations to bill customers are performed using "checkouts" via Paddle's Checkout Overlay widget or by utilizing inline checkout.Most operations to bill customers are performed using "checkouts" via Paddle's Checkout Overlay widget[https://developer.paddle.com/build/checkout/build-overlay-checkout] or by utilizing inline checkout[https://developer.paddle.com/build/checkout/build-branded-inline-checkout].
Before processing checkout payments using Paddle, you should define your application's default payment link in your Paddle checkout settings dashboard.Before processing checkout payments using Paddle, you should define your application's default payment link[https://developer.paddle.com/build/transactions/default-payment-link#set-default-link] in your Paddle checkout settings dashboard.
Overlay CheckoutOverlay Checkout
Before displaying the Checkout Overlay widget, you must generate a checkout session using Cashier. A checkout session will inform the checkout widget of the billing operation that should be performed:Before displaying the Checkout Overlay widget, you must generate a checkout session using Cashier. A checkout session will inform the checkout widget of the billing operation that should be performed:
use Illuminate\Http\Request;
Route::get('/buy', function (Request $request) {
$checkout = $user->checkout('pri_34567')
->returnTo(route('dashboard'));
return view('billing', ['checkout' => $checkout]);
});
Cashier includes a paddle-button
Blade component.
You may pass the checkout session to this component as a
"prop". Then, when this button is clicked,
Paddle's checkout widget will be displayed:Cashier includes a
paddle-button
Blade
component[/docs/{{version}}/blade#components].
You may pass the checkout session to this component
as a "prop". Then, when this button is
clicked, Paddle's checkout widget will be
displayed:
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Subscribe
</x-paddle-button>
By default, this will display the widget using Paddle's
default styling. You can customize the widget by adding
Paddle
supported attributes like the
data-theme='light'
attribute to the
component:By default, this will
display the widget using Paddle's default styling.
You can customize the widget by adding Paddle
supported
attributes[https://developer.paddle.com/paddlejs/html-data-attributes]
like the data-theme='light'
attribute
to the component:
<x-paddle-button :url="$payLink" class="px-8 py-4" data-theme="light">
Subscribe
</x-paddle-button>
The Paddle checkout widget is asynchronous. Once the user creates a subscription within the widget, Paddle will send your application a webhook so that you may properly update the subscription state in your application's 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 a subscription within the widget, Paddle will send your application a webhook so that you may properly update the subscription state in your application's database. Therefore, it's important that you properly set up webhooks[#handling-paddle-webhooks] to accommodate for state changes from Paddle.
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.[!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.
Manually Rendering an Overlay CheckoutManually Rendering an Overlay Checkout
You may also manually render an overlay checkout without using Laravel's built-in Blade components. To get started, generate the checkout session as demonstrated in previous examples:You may also manually render an overlay checkout without using Laravel's built-in Blade components. To get started, generate the checkout session as demonstrated in previous examples[#overlay-checkout]:
use Illuminate\Http\Request;
Route::get('/buy', function (Request $request) {
$checkout = $user->checkout('pri_34567')
->returnTo(route('dashboard'));
return view('billing', ['checkout' => $checkout]);
});
Next, you may use Paddle.js to initialize the checkout.
In this example, we will create a link that is assigned
the paddle_button
class. Paddle.js will
detect this class and display the overlay checkout when
the link is clicked:Next, you
may use Paddle.js to initialize the checkout. In
this example, we will create a link that is assigned
the paddle_button
class. Paddle.js will
detect this class and display the overlay checkout
when the link is clicked:
<?php
$items = $checkout->getItems();
$customer = $checkout->getCustomer();
$custom = $checkout->getCustomData();
?>
<a
href='#!'
class='paddle_button'
data-items='{!! json_encode($items) !!}'
@if ($customer) data-customer-id='{{ $customer->paddle_id }}' @endif
@if ($custom) data-custom-data='{{ json_encode($custom) }}' @endif
@if ($returnUrl = $checkout->getReturnUrl()) data-success-url='{{ $returnUrl }}' @endif
>
Buy Product
</a>
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 checkout session: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 checkout
session[#overlay-checkout]:
use Illuminate\Http\Request;
Route::get('/buy', function (Request $request) {
$checkout = $user->checkout('pri_34567')
->returnTo(route('dashboard'));
return view('billing', ['checkout' => $checkout]);
});
Then, you may pass the checkout session to the
component's checkout
attribute:Then, you may pass the checkout
session to the component's checkout
attribute:
<x-paddle-checkout :checkout="$checkout" 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 :checkout="$checkout" class="w-full" height="500" />
Please consult Paddle's guide on Inline Checkout and available checkout settings for further details on the inline checkout's customization options.Please consult Paddle's guide on Inline Checkout[https://developer.paddle.com/build/checkout/build-branded-inline-checkout] and available checkout settings[https://developer.paddle.com/build/checkout/set-up-checkout-default-settings] for further details on the inline checkout's customization options.
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 checkout session 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 checkout session as demonstrated in previous examples[#inline-checkout]:
use Illuminate\Http\Request;
Route::get('/buy', function (Request $request) {
$checkout = $user->checkout('pri_34567')
->returnTo(route('dashboard'));
return view('billing', ['checkout' => $checkout]);
});
Next, you may use Paddle.js to initialize the checkout. In this example, we will demonstrate this using Alpine.js; however, you are free to modify this example for your own frontend stack:Next, you may use Paddle.js to initialize the checkout. In this example, we will demonstrate this using Alpine.js[https://github.com/alpinejs/alpine]; however, you are free to modify this example for your own frontend stack:
<?php
$options = $checkout->options();
$options['settings']['frameTarget'] = 'paddle-checkout';
$options['settings']['frameInitialHeight'] = 366;
?>
<div class="paddle-checkout" x-data="{}" x-init="
Paddle.Checkout.open(@json($options));
">
</div>
Guest CheckoutsGuest Checkouts
Sometimes, you may need to create a checkout session for
users that do not need an account with your application.
To do so, you may use the guest
method:Sometimes, you may need
to create a checkout session for users that do not
need an account with your application. To do so, you
may use the guest
method:
use Illuminate\Http\Request;
use Laravel\Paddle\Checkout;
Route::get('/buy', function (Request $request) {
$checkout = Checkout::guest('pri_34567')
->returnTo(route('home'));
return view('billing', ['checkout' => $checkout]);
});
Then, you may provide the checkout session to the Paddle button or inline checkout Blade components.Then, you may provide the checkout session to the Paddle button[#overlay-checkout] or inline checkout[#inline-checkout] Blade components.
Price PreviewsPrice Previews
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 these prices using the
previewPrices
method. This method accepts
the price IDs 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 these
prices using the previewPrices
method.
This method accepts the price IDs you wish to
retrieve prices for:
use Laravel\Paddle\Cashier;
$prices = Cashier::previewPrices(['pri_123', 'pri_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(['pri_123', 'pri_456'], ['address' => [
'country_code' => 'BE',
'postal_code' => '1234',
]]);
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['name'] }} - {{ $price->total() }}</li>
@endforeach
</ul>
You may also display the subtotal price and tax amount separately:You may also display the subtotal price and tax amount separately:
<ul>
@foreach ($prices as $price)
<li>{{ $price->product_title }} - {{ $price->subtotal() }} ( {{ $price->tax() }} tax)</li>
@endforeach
</ul>
For more information, checkout Paddle's API documentation regarding price previews.For more information, checkout Paddle's API documentation regarding price previews[https://developer.paddle.com/api-reference/pricing-preview/preview-prices].
Customer Price PreviewsCustomer Price Previews
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)->previewPrices(['pri_123', 'pri_456']);
Internally, Cashier will use the user's customer ID to retrieve the prices in their currency. So, for example, a user living in the United States will see prices in US dollars while a user in Belgium will see prices in Euros. 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 customer ID to retrieve the prices in their currency. So, for example, a user living in the United States will see prices in US dollars while a user in Belgium will see prices in Euros. 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.
DiscountsDiscounts
You may also choose to display prices after a discount.
When calling the previewPrices
method, you
provide the discount ID via the discount_id
option:You may also choose to
display prices after a discount. When calling the
previewPrices
method, you provide the
discount ID via the discount_id
option:
use Laravel\Paddle\Cashier;
$prices = Cashier::previewPrices(['pri_123', 'pri_456'], [
'discount_id' => 'dsc_123'
]);
Then, display the calculated prices:Then, display the calculated prices:
<ul>
@foreach ($prices as $price)
<li>{{ $price->product['name'] }} - {{ $price->total() }}</li>
@endforeach
</ul>
CustomersCustomers
Customer DefaultsCustomer Defaults
Cashier allows you to define some useful defaults for your customers when creating checkout sessions. Setting these defaults allow you to pre-fill a customer's email address and name 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 checkout sessions. Setting these defaults allow you to pre-fill a customer's email address and name 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 name to associate with Paddle.
*/
public function paddleName(): string|null
{
return $this->name;
}
/**
* Get the customer's email address to associate with Paddle.
*/
public function paddleEmail(): string|null
{
return $this->email;
}
These defaults will be used for every action in Cashier that generates a checkout session.These defaults will be used for every action in Cashier that generates a checkout session[#checkout-sessions].
Retrieving CustomersRetrieving Customers
You can retrieve a customer by their Paddle Customer ID
using the Cashier::findBillable
method.
This method will return an instance of the billable
model:You can retrieve a
customer by their Paddle Customer ID using the
Cashier::findBillable
method. This
method will return an instance of the billable
model:
use Laravel\Cashier\Cashier;
$user = Cashier::findBillable($customerId);
Creating CustomersCreating Customers
Occasionally, you may wish to create a Paddle customer
without beginning a subscription. You may accomplish
this using the createAsCustomer
method:Occasionally, you may
wish to create a Paddle customer without beginning a
subscription. You may accomplish this using the
createAsCustomer
method:
$customer = $user->createAsCustomer();
An instance of Laravel\Paddle\Customer
is
returned. Once the customer has been created in Paddle,
you may begin a subscription at a later date. You may
provide an optional $options
array to pass
in any additional customer
creation parameters that are supported by the Paddle
API:An instance of
Laravel\Paddle\Customer
is returned.
Once the customer has been created in Paddle, you
may begin a subscription at a later date. You may
provide an optional $options
array to
pass in any additional customer creation
parameters that are supported by the Paddle
API[https://developer.paddle.com/api-reference/customers/create-customer]:
$customer = $user->createAsCustomer($options);
SubscriptionsSubscriptions
Creating SubscriptionsCreating Subscriptions
To create a subscription, first retrieve an instance of
your billable model from your database, which will
typically be an instance of
App\Models\User
. Once you have retrieved
the model instance, you may use the
subscribe
method to create the model's
checkout session:To create a
subscription, first retrieve an instance of your
billable model from your database, which will
typically be an instance of
App\Models\User
. Once you have
retrieved the model instance, you may use the
subscribe
method to create the model's
checkout session:
use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$checkout = $request->user()->subscribe($premium = 12345, 'default')
->returnTo(route('home'));
return view('billing', ['checkout' => $checkout]);
});
The first argument given to the subscribe
method is the specific price the user is subscribing to.
This value should correspond to the price's identifier
in Paddle. The returnTo
method accepts a
URL that your user will be redirected to after they
successfully complete the checkout. The second argument
passed to the subscribe
method should be
the internal "type" of the subscription. If
your application only offers a single subscription, you
might call this default
or
primary
. This subscription type is only for
internal application usage and is not meant to be
displayed to users. In addition, it should not contain
spaces and it should never be changed after creating the
subscription.The first argument
given to the subscribe
method is the
specific price the user is subscribing to. This
value should correspond to the price's identifier in
Paddle. The returnTo
method accepts a
URL that your user will be redirected to after they
successfully complete the checkout. The second
argument passed to the subscribe
method
should be the internal "type" of the
subscription. If your application only offers a
single subscription, you might call this
default
or primary
. This
subscription type is only for internal application
usage and is not meant to be displayed to users. In
addition, it should not contain spaces and it should
never be changed after creating the
subscription.
You may also provide an array of custom meta data
regarding the subscription using the
customData
method:You may also provide an array of
custom meta data regarding the subscription using
the customData
method:
$checkout = $request->user()->subscribe($premium = 12345, 'default')
->customData(['key' => 'value'])
->returnTo(route('home'));
Once a subscription checkout session has been created,
the checkout session may be provided to the
paddle-button
Blade component that is
included with Cashier Paddle:Once a subscription checkout
session has been created, the checkout session may
be provided to the paddle-button
Blade component[#overlay-checkout] that is
included with Cashier Paddle:
<x-paddle-button :checkout="$checkout" 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].
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
valid 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 valid
subscription, even if the subscription is currently
within its trial period:
if ($user->subscribed()) {
// ...
}
If your application offers multiple subscriptions, you
may specify the subscription when invoking the
subscribed
method:If your application offers
multiple subscriptions, you may specify the
subscription when invoking the
subscribed
method:
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;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserIsSubscribed
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if ($request->user() && ! $request->user()->subscribed()) {
// 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()->onTrial()) {
// ...
}
The subscribedToPrice
method may be used to
determine if the user is subscribed to a given plan
based on a given Paddle price ID. In this example, we
will determine if the user's default
subscription is actively subscribed to the monthly
price:The
subscribedToPrice
method may be used to
determine if the user is subscribed to a given plan
based on a given Paddle price ID. In this example,
we will determine if the user's default
subscription is actively subscribed to the monthly
price:
if ($user->subscribedToPrice($monthly = 'pri_123', 'default')) {
// ...
}
The recurring
method may be used to
determine if the user is currently on an active
subscription and is no longer within their trial period
or on a grace period:The
recurring
method may be used to
determine if the user is currently on an active
subscription and is no longer within their trial
period or on a grace period:
if ($user->subscription()->recurring()) {
// ...
}
Canceled Subscription StatusCanceled Subscription Status
To determine if the user was once an active subscriber
but has canceled their subscription, you may use the
canceled
method:To
determine if the user was once an active subscriber
but has canceled their subscription, you may use the
canceled
method:
if ($user->subscription()->canceled()) {
// ...
}
You may also determine if a user has canceled 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. In addition, the subscribed
method will still return true
during this
time:You may also determine if
a user has canceled 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. In addition, the subscribed
method will still return true
during
this time:
if ($user->subscription()->onGracePeriod()) {
// ...
}
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()->pastDue()) {
// ...
}
When a subscription is past due, you should instruct the user to update their payment information.When a subscription is past due, you should instruct the user to update their payment information[#updating-payment-information].
If you would like subscriptions to still be considered
valid 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 valid 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.
*/
public function register(): void
{
Cashier::keepPastDueSubscriptionsActive();
}
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.[!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 valid subscriptions...
$subscriptions = Subscription::query()->valid()->get();
// Get all of the canceled subscriptions for a user...
$subscriptions = $user->subscriptions()->canceled()->get();
A complete list of available scopes is available below:A complete list of available scopes is available below:
Subscription::query()->valid();
Subscription::query()->onTrial();
Subscription::query()->expiredTrial();
Subscription::query()->notOnTrial();
Subscription::query()->active();
Subscription::query()->recurring();
Subscription::query()->pastDue();
Subscription::query()->paused();
Subscription::query()->notPaused();
Subscription::query()->onPausedGracePeriod();
Subscription::query()->notOnPausedGracePeriod();
Subscription::query()->canceled();
Subscription::query()->notCanceled();
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. You must provide one or multiple price
ID's when invoking the charge
method:Subscription single charges allow
you to charge subscribers with a one-time charge on
top of their subscriptions. You must provide one or
multiple price ID's when invoking the
charge
method:
// Charge a single price...
$response = $user->subscription()->charge('pri_123');
// Charge multiple prices at once...
$response = $user->subscription()->charge(['pri_123', 'pri_456']);
The charge
method will not actually charge
the customer until the next billing interval of their
subscription. If you would like to bill the customer
immediately, you may use the
chargeAndInvoice
method instead:The charge
method
will not actually charge the customer until the next
billing interval of their subscription. If you would
like to bill the customer immediately, you may use
the chargeAndInvoice
method
instead:
$response = $user->subscription()->chargeAndInvoice('pri_123');
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 redirect your customer to
Paddle's hosted payment method update page using the
redirectToUpdatePaymentMethod
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 redirect your customer to
Paddle's hosted payment method update page using the
redirectToUpdatePaymentMethod
method on
the subscription model:
use Illuminate\Http\Request;
Route::get('/update-payment-method', function (Request $request) {
$user = $request->user();
return $user->subscription()->redirectToUpdatePaymentMethod();
});
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 price'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 price's identifier to the
subscription's swap
method:
use App\Models\User;
$user = User::find(1);
$user->subscription()->swap($premium = 'pri_456');
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()->swapAndInvoice($premium = 'pri_456');
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 = 'pri_456');
If you would like to disable proration and invoice
customers immediately, you may use the
swapAndInvoice
method in combination with
noProrate
:If you
would like to disable proration and invoice
customers immediately, you may use the
swapAndInvoice
method in combination
with noProrate
:
$user->subscription('default')->noProrate()->swapAndInvoice($premium = 'pri_456');
Or, to not bill your customer for a subscription change,
you may utilize the doNotBill
method:Or, to not bill your customer for
a subscription change, you may utilize the
doNotBill
method:
$user->subscription('default')->doNotBill()->swap($premium = 'pri_456');
For more information on Paddle's proration policies, please consult Paddle's proration documentation.For more information on Paddle's proration policies, please consult Paddle's proration documentation[https://developer.paddle.com/concepts/subscriptions/proration].
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()->incrementQuantity();
// Add five to the subscription's current quantity...
$user->subscription()->incrementQuantity(5);
$user->subscription()->decrementQuantity();
// Subtract five from the subscription's current quantity...
$user->subscription()->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()->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()->noProrate()->updateQuantity(10);
Quantities for Subscriptions With Multiple ProductsQuantities for Subscriptions With Multiple Products
If your subscription is a subscription with multiple products, you should pass the ID of the price whose quantity you wish to increment or decrement as the second argument to the increment / decrement methods:If your subscription is a subscription with multiple products[#subscriptions-with-multiple-products], you should pass the ID of the price whose quantity you wish to increment or decrement as the second argument to the increment / decrement methods:
$user->subscription()->incrementQuantity(1, 'price_chat');
Subscriptions With Multiple ProductsSubscriptions With Multiple Products
Subscription with multiple products allow you to assign multiple billing products to a single subscription. For example, imagine you are building a customer service "helpdesk" application that has a base subscription price of $10 per month but offers a live chat add-on product for an additional $15 per month.Subscription with multiple products[https://developer.paddle.com/build/subscriptions/add-remove-products-prices-addons] allow you to assign multiple billing products to a single subscription. For example, imagine you are building a customer service "helpdesk" application that has a base subscription price of $10 per month but offers a live chat add-on product for an additional $15 per month.
When creating subscription checkout sessions, you may
specify multiple products for a given subscription by
passing an array of prices as the first argument to the
subscribe
method:When creating subscription
checkout sessions, you may specify multiple products
for a given subscription by passing an array of
prices as the first argument to the
subscribe
method:
use Illuminate\Http\Request;
Route::post('/user/subscribe', function (Request $request) {
$checkout = $request->user()->subscribe([
'price_monthly',
'price_chat',
]);
return view('billing', ['checkout' => $checkout]);
});
In the example above, the customer will have two prices
attached to their default
subscription.
Both prices will be charged on their respective billing
intervals. If necessary, you may pass an associative
array of key / value pairs to indicate a specific
quantity for each price:In the
example above, the customer will have two prices
attached to their default
subscription.
Both prices will be charged on their respective
billing intervals. If necessary, you may pass an
associative array of key / value pairs to indicate a
specific quantity for each price:
$user = User::find(1);
$checkout = $user->subscribe('default', ['price_monthly', 'price_chat' => 5]);
If you would like to add another price to an existing
subscription, you must use the subscription's
swap
method. When invoking the
swap
method, you should also include the
subscription's current prices and quantities as
well:If you would like to add
another price to an existing subscription, you must
use the subscription's swap
method.
When invoking the swap
method, you
should also include the subscription's current
prices and quantities as well:
$user = User::find(1);
$user->subscription()->swap(['price_chat', 'price_original' => 2]);
The example above will add the new price, but the
customer will not be billed for it until their next
billing cycle. If you would like to bill the customer
immediately you may use the swapAndInvoice
method:The example above will
add the new price, but the customer will not be
billed for it until their next billing cycle. If you
would like to bill the customer immediately you may
use the swapAndInvoice
method:
$user->subscription()->swapAndInvoice(['price_chat', 'price_original' => 2]);
You may remove prices from subscriptions using the
swap
method and omitting the price you want
to remove:You may remove prices
from subscriptions using the swap
method and omitting the price you want to
remove:
$user->subscription()->swap(['price_original' => 2]);
Warning!
You may not remove the last price on a subscription. Instead, you should simply cancel the subscription.[!WARNING]
You may not remove the last price on a subscription. Instead, you should simply cancel the subscription.
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 type of the subscription to the
subscribe
method as the second argument.
The type may be any string that represents the type of
subscription the user is initiating:When your application creates
subscriptions, you may provide the type of the
subscription to the subscribe
method as
the second argument. The type 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) {
$checkout = $request->user()->subscribe($swimmingMonthly = 'pri_123', 'swimming');
return view('billing', ['checkout' => $checkout]);
});
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 = 'pri_456');
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()->pause();
When a subscription is paused, Cashier will automatically
set the paused_at
column in your database.
This column is used to determine 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 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_at
column in your database. This
column is used to determine 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 because
a user is typically allowed to continue using an
application until the end of their billing
cycle.
By default, pausing happens at the next billing interval
so the customer can use the remainder of the period they
paid for. If you want to pause a subscription
immediately, you may use the pauseNow
method:By default, pausing
happens at the next billing interval so the customer
can use the remainder of the period they paid for.
If you want to pause a subscription immediately, you
may use the pauseNow
method:
$user->subscription()->pauseNow();
Using the pauseUntil
method, you can pause
the subscription until a specific moment in time:Using the pauseUntil
method, you can pause the subscription until a
specific moment in time:
$user->subscription()->pauseUntil(now()->addMonth());
Or, you may use the pauseNowUntil
method to
immediately pause the subscription until a given point
in time:Or, you may use the
pauseNowUntil
method to immediately
pause the subscription until a given point in
time:
$user->subscription()->pauseNowUntil(now()->addMonth());
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()->onPausedGracePeriod()) {
// ...
}
To resume a paused subscription, you may invoke the
resume
method on the subscription:To resume a paused subscription,
you may invoke the resume
method on the
subscription:
$user->subscription()->resume();
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.[!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.
Canceling SubscriptionsCanceling 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()->cancel();
When a subscription is canceled, Cashier will
automatically set the ends_at
column in
your database. This column is used to determine 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 canceled,
Cashier will automatically set the
ends_at
column in your database. This
column is used to determine 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 canceled their
subscription but are still on their "grace
period" using the onGracePeriod
method:You may determine if a
user has canceled their subscription but are still
on their "grace period" using the
onGracePeriod
method:
if ($user->subscription()->onGracePeriod()) {
// ...
}
If you wish to cancel a subscription immediately, you may
call the cancelNow
method on the
subscription:If you wish to
cancel a subscription immediately, you may call the
cancelNow
method on the
subscription:
$user->subscription()->cancelNow();
To stop a subscription on its grace period from
canceling, you may invoke the
stopCancelation
method:To stop a subscription on its
grace period from canceling, you may invoke the
stopCancelation
method:
$user->subscription()->stopCancelation();
Warning!
Paddle's subscriptions cannot be resumed after cancelation. If your customer wishes to resume their subscription, they will have to create a new subscription.[!WARNING]
Paddle's subscriptions cannot be resumed after cancelation. If your customer wishes to resume their subscription, they will have to create a new subscription.
Subscription TrialsSubscription Trials
With Payment Method Up FrontWith Payment Method Up Front
If you would like to offer trial periods to your customers while still collecting payment method information up front, you should use set a trial time in the Paddle dashboard on the price your customer is subscribing to. Then, initiate the checkout session as normal:If you would like to offer trial periods to your customers while still collecting payment method information up front, you should use set a trial time in the Paddle dashboard on the price your customer is subscribing to. Then, initiate the checkout session as normal:
use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$checkout = $request->user()->subscribe('pri_monthly')
->returnTo(route('home'));
return view('billing', ['checkout' => $checkout]);
});
When your application receives the
subscription_created
event, Cashier 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.When your
application receives the
subscription_created
event, Cashier
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!
If the customer's subscription is not canceled 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.[!WARNING]
If the customer's subscription is not canceled 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()) {
// ...
}
if ($user->subscription()->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()) {
// ...
}
if ($user->subscription()->hasExpiredTrial()) {
// ...
}
To determine if a user is on trial for a specific
subscription type, you may provide the type to the
onTrial
or hasExpiredTrial
methods:To determine if a user
is on trial for a specific subscription type, you
may provide the type to the onTrial
or
hasExpiredTrial
methods:
if ($user->onTrial('default')) {
// ...
}
if ($user->hasExpiredTrial('default')) {
// ...
}
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 subscribe
method
as usual:Once you are ready to
create an actual subscription for the user, you may
use the subscribe
method as
usual:
use Illuminate\Http\Request;
Route::get('/user/subscribe', function (Request $request) {
$checkout = $user->subscribe('pri_monthly')
->returnTo(route('home'));
return view('billing', ['checkout' => $checkout]);
});
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 type 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 type parameter if
you would like to get the trial ending date for a
specific subscription other than the default
one:
if ($user->onTrial('default')) {
$trialEndsAt = $user->trialEndsAt();
}
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...
}
Extend or Activate a TrialExtend or Activate a Trial
You can extend an existing trial period on a subscription
by invoking the extendTrial
method and
specifying the moment in time that the trial should
end:You can extend an existing
trial period on a subscription by invoking the
extendTrial
method and specifying the
moment in time that the trial should end:
$user->subsription()->extendTrial(now()->addDays(5));
Or, you may immediately activate a subscription by ending
its trial by calling the activate
method on
the subscription:Or, you may
immediately activate a subscription by ending its
trial by calling the activate
method on
the subscription:
$user->subscription()->activate();
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 canceling subscriptions that have too many failed charges, 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 canceling subscriptions that have too many failed charges, 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:
- Customer UpdatedCustomer Updated
- Transaction CompletedTransaction Completed
- Transaction UpdatedTransaction Updated
- Subscription CreatedSubscription Created
- Subscription UpdatedSubscription Updated
- Subscription PausedSubscription Paused
- Subscription CanceledSubscription Canceled
Warning!
Make sure you protect incoming requests with Cashier's included webhook signature verification middleware.[!WARNING]
Make sure you protect incoming requests with Cashier's included webhook signature verification[/docs/{{version}}/cashier-paddle#verifying-webhook-signatures] middleware.
Webhooks and CSRF ProtectionWebhooks and CSRF Protection
Since Paddle webhooks need to
bypass Laravel's CSRF
protection, you
should ensure that Laravel
does not attempt to verify
the CSRF token for incoming
Paddle webhooks. To
accomplish this, you should
exclude
paddle/*
from
CSRF protection in your
application's
bootstrap/app.php
file:Since
Paddle webhooks need to
bypass Laravel's CSRF
protection[/docs/{{version}}/csrf],
you should ensure that
Laravel does not attempt
to verify the CSRF token
for incoming Paddle
webhooks. To accomplish
this, you should exclude
paddle/*
from CSRF protection in
your application's
bootstrap/app.php
file:
->withMiddleware(function (Middleware $middleware) {
$middleware->validateCsrfTokens(except: [
'paddle/*',
]);
})
Webhooks and Local DevelopmentWebhooks and 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 cancelation 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 cancelation 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
transaction_billed
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
transaction_billed
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.
*/
public function handle(WebhookReceived $event): void
{
if ($event->payload['alert_name'] === 'transaction_billed') {
// Handle the incoming event...
}
}
}
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\CustomerUpdated
Laravel\Paddle\Events\CustomerUpdated
Laravel\Paddle\Events\TransactionCompleted
Laravel\Paddle\Events\TransactionCompleted
Laravel\Paddle\Events\TransactionUpdated
Laravel\Paddle\Events\TransactionUpdated
Laravel\Paddle\Events\SubscriptionCreated
Laravel\Paddle\Events\SubscriptionCreated
Laravel\Paddle\Events\SubscriptionUpdated
Laravel\Paddle\Events\SubscriptionUpdated
Laravel\Paddle\Events\SubscriptionPaused
Laravel\Paddle\Events\SubscriptionPaused
Laravel\Paddle\Events\SubscriptionCanceled
Laravel\Paddle\Events\SubscriptionCanceled
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_WEBHOOK_SECRET
environment
variable
is
defined
in
your
application's
.env
file.
The
webhook
secret
may
be
retrieved
from
your
Paddle
account
dashboard.To
enable
webhook
verification,
ensure
that
the
PADDLE_WEBHOOK_SECRET
environment
variable
is
defined
in
your
application's
.env
file.
The
webhook
secret
may
be
retrieved
from
your
Paddle
account
dashboard.
Single ChargesSingle Charges
Charging for ProductsCharging for Products
If
you
would
like
to
initiate
a
product
purchase
for
a
customer,
you
may
use
the
checkout
method
on
a
billable
model
instance
to
generate
a
checkout
session
for
the
purchase.
The
checkout
method
accepts
one
or
multiple
price
ID's.
If
necessary,
an
associative
array
may
be
used
to
provide
the
quantity
of
the
product
that
is
being
purchased:If
you
would
like
to
initiate
a
product
purchase
for
a
customer,
you
may
use
the
checkout
method
on
a
billable
model
instance
to
generate
a
checkout
session
for
the
purchase.
The
checkout
method
accepts
one
or
multiple
price
ID's.
If
necessary,
an
associative
array
may
be
used
to
provide
the
quantity
of
the
product
that
is
being
purchased:
use Illuminate\Http\Request;
Route::get('/buy', function (Request $request) {
$checkout = $request->user()->checkout(['pri_tshirt', 'pri_socks' => 5]);
return view('buy', ['checkout' => $checkout]);
});
After
generating
the
checkout
session,
you
may
use
Cashier's
provided
paddle-button
Blade
component
to
allow
the
user
to
view
the
Paddle
checkout
widget
and
complete
the
purchase:After
generating
the
checkout
session,
you
may
use
Cashier's
provided
paddle-button
Blade
component[#overlay-checkout]
to
allow
the
user
to
view
the
Paddle
checkout
widget
and
complete
the
purchase:
<x-paddle-button :checkout="$checkout" class="px-8 py-4">
Buy
</x-paddle-button>
A
checkout
session
has
a
customData
method,
allowing
you
to
pass
any
custom
data
you
wish
to
the
underlying
transaction
creation.
Please
consult
the
Paddle
documentation
to
learn
more
about
the
options
available
to
you
when
passing
custom
data:A
checkout
session
has
a
customData
method,
allowing
you
to
pass
any
custom
data
you
wish
to
the
underlying
transaction
creation.
Please
consult
the
Paddle
documentation[https://developer.paddle.com/build/transactions/custom-data]
to
learn
more
about
the
options
available
to
you
when
passing
custom
data:
$checkout = $user->checkout('pri_tshirt')
->customData([
'custom_option' => $value,
]);
Refunding TransactionsRefunding Transactions
Refunding
transactions
will
return
the
refunded
amount
to
your
customer's
payment
method
that
was
used
at
the
time
of
purchase.
If
you
need
to
refund
a
Paddle
purchase,
you
may
use
the
refund
method
on
a
Cashier\Paddle\Transaction
model.
This
method
accepts
a
reason
as
the
first
argument,
one
or
more
price
ID's
to
refund
with
optional
amounts
as
an
associative
array.
You
may
retrieve
the
transactions
for
a
given
billable
model
using
the
transactions
method.Refunding
transactions
will
return
the
refunded
amount
to
your
customer's
payment
method
that
was
used
at
the
time
of
purchase.
If
you
need
to
refund
a
Paddle
purchase,
you
may
use
the
refund
method
on
a
Cashier\Paddle\Transaction
model.
This
method
accepts
a
reason
as
the
first
argument,
one
or
more
price
ID's
to
refund
with
optional
amounts
as
an
associative
array.
You
may
retrieve
the
transactions
for
a
given
billable
model
using
the
transactions
method.
For
example,
imagine
we
want
to
refund
a
specific
transaction
for
prices
pri_123
and
pri_456
.
We
want
to
fully
refund
pri_123
,
but
only
refund
two
dollars
for
pri_456
:For
example,
imagine
we
want
to
refund
a
specific
transaction
for
prices
pri_123
and
pri_456
.
We
want
to
fully
refund
pri_123
,
but
only
refund
two
dollars
for
pri_456
:
use App\Models\User;
$user = User::find(1);
$transaction = $user->transactions()->first();
$response = $transaction->refund('Accidental charge', [
'pri_123', // Fully refund this price...
'pri_456' => 200, // Only partially refund this price...
]);
The example above refunds specific line items in a transaction. If you want to refund the entire transaction, simply provide a reason:The example above refunds specific line items in a transaction. If you want to refund the entire transaction, simply provide a reason:
$response = $transaction->refund('Accidental charge');
For more information on refunds, please consult Paddle's refund documentation.For more information on refunds, please consult Paddle's refund documentation[https://developer.paddle.com/build/transactions/create-transaction-adjustments].
Warning!
Refunds must always be approved by Paddle before fully processing.[!WARNING]
Refunds must always be approved by Paddle before fully processing.
Crediting TransactionsCrediting Transactions
Just like refunding, you can also credit transactions. Crediting transactions will add the funds to the customer's balance so it may be used for future purchases. Crediting transactions can only be done for manually-collected transactions and not for automatically-collected transactions (like subscriptions) since Paddle handles subscription credits automatically:Just like refunding, you can also credit transactions. Crediting transactions will add the funds to the customer's balance so it may be used for future purchases. Crediting transactions can only be done for manually-collected transactions and not for automatically-collected transactions (like subscriptions) since Paddle handles subscription credits automatically:
$transaction = $user->transactions()->first();
// Credit a specific line item fully...
$response = $transaction->credit('Compensation', 'pri_123');
For more info, see Paddle's documentation on crediting.For more info, see Paddle's documentation on crediting[https://developer.paddle.com/build/transactions/create-transaction-adjustments].
Warning!
Credits can only be applied for manually-collected transactions. Automatically-collected transactions are credited by Paddle themselves.[!WARNING]
Credits can only be applied for manually-collected transactions. Automatically-collected transactions are credited by Paddle themselves.
TransactionsTransactions
You
may
easily
retrieve
an
array
of
a
billable
model's
transactions
via
the
transactions
property:You
may
easily
retrieve
an
array
of
a
billable
model's
transactions
via
the
transactions
property:
use App\Models\User;
$user = User::find(1);
$transactions = $user->transactions;
Transactions represent payments for your products and purchases and are accompanied by invoices. Only completed transactions are stored in your application's database.Transactions represent payments for your products and purchases and are accompanied by invoices. Only completed transactions are stored in your application's database.
When listing the transactions for a customer, you may use the transaction instance's methods to display the relevant payment information. For example, you may wish to list every transaction in a table, allowing the user to easily download any of the invoices:When listing the transactions for a customer, you may use the transaction instance's methods to display the relevant payment information. For example, you may wish to list every transaction in a table, allowing the user to easily download any of the invoices:
<table>
@foreach ($transactions as $transaction)
<tr>
<td>{{ $transaction->billed_at->toFormattedDateString() }}</td>
<td>{{ $transaction->total() }}</td>
<td>{{ $transaction->tax() }}</td>
<td><a href="{{ route('download-invoice', $transaction->id) }}" target="_blank">Download</a></td>
</tr>
@endforeach
</table>
The
download-invoice
route
may
look
like
the
following:The
download-invoice
route
may
look
like
the
following:
use Illuminate\Http\Request;
use Laravel\Cashier\Transaction;
Route::get('/download-invoice/{transaction}', function (Request $request, Transaction $transaction) {
return $transaction->redirectToInvoicePdf();
})->name('download-invoice');
Past and Upcoming PaymentsPast and 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();
$lastPayment = $subscription->lastPayment();
$nextPayment = $subscription->nextPayment();
Both
of
these
methods
will
return
an
instance
of
Laravel\Paddle\Payment
;
however,
lastPayment
will
return
null
when
transactions
have
not
been
synced
by
webhooks
yet,
while
nextPayment
will
return
null
when
the
billing
cycle
has
ended
(such
as
when
a
subscription
has
been
canceled):Both
of
these
methods
will
return
an
instance
of
Laravel\Paddle\Payment
;
however,
lastPayment
will
return
null
when
transactions
have
not
been
synced
by
webhooks
yet,
while
nextPayment
will
return
null
when
the
billing
cycle
has
ended
(such
as
when
a
subscription
has
been
canceled):
Next payment: {{ $nextPayment->amount() }} due on {{ $nextPayment->date()->format('d/m/Y') }}
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.