Cashier Trading Toolkit
Laravel Cashier
- Introduction
- Upgrade Cashier
- Configuration
- Subscription
- ##Trial subscription ##Customer
- Bank card
- Processing Stripe Webhooks
- Handling Braintree Webhooks
- One-time charge
- Invoice
Introduction
Laravel Cashier provides an intuitive and smooth interface to access Stripe's and Braintree's paid subscription services. It can handle paid subscription codes that almost give you a headache. In addition to providing basic subscription management, Cashier can help you with coupons, exchange subscriptions, subscription "quantities", cancellation grace periods, and even generate PDF invoices.
{Note} If you only need a “one-time” charge and don’t offer a subscription, you shouldn’t use Cashier. It is recommended that you use the Stripe and Braintree SDKs.
Upgrading Cashier
When you upgrade from an old version to the latest version of Cashier, it is recommended that you Read firstCashier Upgrade Guide.
##ConfigurationStripeComposerFirst, add Stripe’s Cashier package to your project dependencies:
composer require laravel/cashierDatabase migrationBefore using Cashier, you need to prepare the database. Cashier will need to add a few columns to your
users table and create a new
subscriptions table to hold all your customers' subscriptions:
Schema::table('users', function ($table) { $table->string('stripe_id')->nullable()->collation('utf8mb4_bin'); $table->string('card_brand')->nullable(); $table->string('card_last_four', 4)->nullable(); $table->timestamp('trial_ends_at')->nullable(); }); Schema::create('subscriptions', function ($table) { $table->increments('id'); $table->unsignedInteger('user_id'); $table->string('name'); $table->string('stripe_id')->collation('utf8mb4_bin'); $table->string('stripe_plan'); $table->integer('quantity'); $table->timestamp('trial_ends_at')->nullable(); $table->timestamp('ends_at')->nullable(); $table->timestamps(); });Once the migration file is created After that, run Artisan's
migrate command.
Billable Trait to your model definition. This Trait provides multiple methods to perform common payment tasks, such as creating subscriptions, using coupons, and updating credit card information:
use Laravel\Cashier\Billable;class User extends Authenticatable{ use Billable; }API KeysFinally, in the configuration Configure Stripe's Key in the file
services.php. You can obtain these Stripe API Key information in the personal control panel of Stripe's official website:
'stripe' => [ 'model' => App\User::class, 'key' => env('STRIPE_KEY'), 'secret' => env('STRIPE_SECRET'), ],Braintree
Braintree Notes
In many cases, Stripe and Braintree implement Cashier functions the same way. Both provide the function of subscription payment through credit card, and Braintree also additionally supports payment through PayPal. But Braintree also lacks some features that Stripe supports. Before deciding to use Stripe or Braintree, you need to consider the following points:
- Braintree supports PayPal but Stripe does not.
- Braintree does not support the
increment
anddecrement
methods, this is a Braintree limitation, not a Cashier limitation. - Braintree does not support percentage-based discounts. This is a Braintree limitation, not a Cashier limitation.
Composer
First, add Braintree’s Cashier package to your project’s dependencies:
composer require "laravel/cashier-braintree":"~2.0"
Credit Card Discount Plan
Before using Cashier, you need to first define a plan-credit
discount in the Braintree control panel. This discount will match the appropriate discount ratio based on the payment option selected by the user, such as annual payment or monthly payment.
The total discount amount configured in the Braintree control panel can be filled in as desired, and Cashier will override the default value according to your configuration each time a coupon is used. This coupon is required as Braintree does not support using subscription frequency to match discount ratios.
Database migration
Before you start using Cashier, you need to prepare the database. Cashier will need to add a few new columns to your database's users
table, as well as create a new subscriptions
table to store the customer's subscription information:
Schema::table('users', function ($table) { $table->string('braintree_id')->nullable(); $table->string('paypal_email')->nullable(); $table->string('card_brand')->nullable(); $table->string('card_last_four')->nullable(); $table->timestamp('trial_ends_at')->nullable(); }); Schema::create('subscriptions', function ($table) { $table->increments('id'); $table->unsignedInteger('user_id'); $table->string('name'); $table->string('braintree_id'); $table->string('braintree_plan'); $table->integer('quantity'); $table->timestamp('trial_ends_at')->nullable(); $table->timestamp('ends_at')->nullable(); $table->timestamps(); });
Once migrated After the file is created, run Artisan's migrate
command.
Billable Model
Then, add the Billable
Trait to your model definition:
use Laravel\Cashier\Billable; class User extends Authenticatable{ use Billable; }
API Keys
Next, you should configure the following options in the services.php
file:
'braintree' => [ 'model' => App\User::class, 'environment' => env('BRAINTREE_ENV'), 'merchant_id' => env('BRAINTREE_MERCHANT_ID'), 'public_key' => env('BRAINTREE_PUBLIC_KEY'), 'private_key' => env('BRAINTREE_PRIVATE_KEY'), ],
Finally, you must provide the AppServiceProvider
service In the provider's boot
method, add the following Braintree SDK call:
\Braintree_Configuration::environment(config('services.braintree.environment')); \Braintree_Configuration::merchantId(config('services.braintree.merchant_id')); \Braintree_Configuration::publicKey(config('services.braintree.public_key')); \Braintree_Configuration::privateKey(config('services.braintree.private_key'));
Currency Configuration
Cashier uses United States Dollar (USD) as the default currency. You can change the default currency by calling the Cashier::useCurrency
method in the service provider's boot
method. This useCurrency
method accepts two string parameters: currency and currency symbol:
use Laravel\Cashier\Cashier; Cashier::useCurrency('eur', '€');##Subscribe
Create a subscription
To create a subscription, you first need to obtain a Billable model instance, which is usually an instance of App\User
. Once you have obtained a model instance, you can create a subscription to the model using the newSubscription
method:
$user = User::find(1); $user->newSubscription('main', 'premium')->create($stripeToken);
newSubscription
The first parameter to the method should be the name of the subscription. If your application only provides one subscription, you can set it to main
or primary
. The second parameter is the Stripe/Braintree plan the user is subscribed to. This value should correspond to an identifier in Stripe or Braintree. The
create
method accepts a Stripe credit card/source token, which will start the subscription and update the database with the customer ID and other relevant billing information.
Additional details of the user
If you want to specify additional details of the user, you can do so by passing them as the second argument to create
Method:
$user->newSubscription('main', 'monthly')->create($stripeToken, [ 'email' => $email, ]);
To learn more about the additional fields supported by Stripe or Braintree, check out Stripe's Content Creation Customer Documentation or the corresponding Braintree Documentation .
Coupon
If you want to use a coupon when creating a subscription, you can use the withCoupon
method:
$user->newSubscription('main', 'monthly') ->withCoupon('code') ->create($stripeToken);
Checking Subscription Status
Once a user has subscribed in your app, you can easily check their subscription using a variety of convenient methods Subscription status. First, if the user has an active subscription, then the subscribed
method will return true
even if the subscription is currently in the trial phase:
if ($user->subscribed('main')) { // }
thissubscribed
Method can also be used in routing middleware, allowing you to access routes and controllers based on the user's subscription status:
public function handle($request, Closure $next){ if ($request->user() && ! $request->user()->subscribed('main')) { // This user is not a paying customer... return redirect('billing'); } return $next($request); }
If you want to determine whether the user is still in the trial phase, you can use onTrial
method. This method is useful for showing a warning to the user that they are still in the trial period:
if ($user->subscription('main')->onTrial()) { // }
Based on a given Stripe/Braintree plan ID, the subscribedToPlan
method can be used to determine whether the user is subscribed the plan. In this example, we will determine if the user's main
subscription has the monthly
plan activated: The
if ($user->subscribedToPlan('monthly', 'main')) { // }
recurring
method can be used to determine if the user is currently Already subscribed and no longer in trial phase:
if ($user->subscription('main')->recurring()) { // }
Cancelled Subscription Status
To determine if a user was once subscribed, but has canceled their subscription, you can use the cancelled
method:
if ($user->subscription('main')->cancelled()) { // }
You can also determine Whether the user has canceled the subscription, but is still in the "grace period" of the subscription until the subscription completely expires. For example, if a user cancels a subscription on March 5th that was due to expire on March 10th, the user will have a "grace period" until March 10th. Note that the subscribed
method still returns true
during this time:
if ($user->subscription('main')->onGracePeriod()) { // }
If you want to determine if the time the user unsubscribed is no longer within their "grace period", you can Use ended
method:
if ($user->subscription('main')->ended()) { // }
Modify Subscription Plan
User in your application After subscribing, they may occasionally want to change to a new subscription plan. To switch a user to a new subscription, pass the subscription plan identifier to the swap
method:
$user = App\User::find(1); $user->subscription('main')->swap('provider-plan-id');
If the user is in a trial period, the duration of the trial period is retained. In addition, if there is a "share" for the number of subscriptions, that share will also be maintained.
If you want to cancel the trial period of the user's current subscription when changing the user's subscription plan, you can use the skipTrial
method:
$user->subscription('main') ->skipTrial() ->swap('provider-plan-id');
Subscriptions
{Note} Subscriptions are only supported by Cashier's Stripe. Braintree doesn't have a "quantity" feature that corresponds to Stripe.
Sometimes subscriptions are affected by "quantity". For example, your app might be billed at per account $10/month. You can easily increase or decrease your subscription using the incrementQuantity
and decrementQuantity
methods:
$user = User::find(1); $user->subscription('main')->incrementQuantity(); // 对当前的订阅量加5... $user->subscription('main')->incrementQuantity(5); $user->subscription('main')->decrementQuantity(); // 对当前的订阅量减5... $user->subscription('main')->decrementQuantity(5);
Alternatively, you can use the updateQuantity
method to set Set a specific quantity:
$user->subscription('main')->updateQuantity(10);
noProrate
method can be used to update the quantity of the subscription without pricing the charge:
$user->subscription('main')->noProrate()->updateQuantity(10);
To get more information about the subscription quantity For information, please refer to the Stripe documentation.
Subscription tax amount
In billing mode Implement the taxPercentage
method and return a number from 0 to 100 with no more than 2 decimal places, used to specify the tax rate percentage paid by the user in the subscription.
public function taxPercentage() { return 20; }
taxPercentage
method enables you to apply tax rates on a model basis, which may be helpful for a user base that spans multiple countries and tax rates.
{Note}
taxPercentage
method is only applicable to paid subscription model. If you use charges to make "one-time" charges, you need to manually specify the tax rate at the same time.
Synchronize Tax Percentage
When changing the hardcoded value returned by the taxPercentage
method, the tax rate settings for any existing subscriptions of the user will remain unchanged. If you want to update the tax rate for an existing subscription with the returned taxPercentage
value, you should call the syncTaxPercentage
method on the user's subscription instance:
$user->subscription('main')->syncTaxPercentage();
Subscription anchor date
{Note} Only Stripe in Cashier supports modifying the subscription anchor date.
By default, the billing cycle is anchored to the date the subscription was created, or if using a trial period, the date the trial ends. If you want to modify the billing anchor date, you can use the anchorBillingCycleOn
method:
use App\User;use Carbon\Carbon; $user = User::find(1); $anchor = Carbon::parse('first day of next month'); $user->newSubscription('main', 'premium') ->anchorBillingCycleOn($anchor->startOfDay()) ->create($stripeToken);
For more information about managing subscription billing cycles, see the Stripe Billing Cycle Documentation
Cancel subscription
Call the cancel
method on the user subscription to cancel the subscription:
$user->subscription('main')->cancel();
When a subscription is canceled, Cashier will automatically set the ends_at
column in your database. This column is often used to know when a subscribed
field should start returning false
. For example, if the customer cancels their subscription on March 1st, but the subscription plan does not end until March 5th, the subscribed
method will continue to return true
until March 5th.
You can use the onGracePeriod
method to determine whether the user is sure to subscribe, but there is still a "grace period":
if ($user->subscription('main')->onGracePeriod()) { // }
If you want to cancel the subscription immediately, please cancel the subscription in the user's Call cancelNow
method in subscription:
$user->subscription('main')->cancelNow();
Resume subscription
If a user has been canceled Subscription, you can use the resume
method when you wish to resume it. The user must still be within their grace period before they can resume their subscription:
$user->subscription('main')->resume();
If a user has canceled their subscription and then resumes it before the subscription grace period, they will not be immediately counted fee. Instead, their subscription will be reactivated and they will need to pay again according to the original payment process.
Trial Subscription
Subscribe by credit card
If you want to offer your customers a trial period while collecting payment method information, then you should use the trialDays
method when creating the subscription:
$user = User::find(1);$user->newSubscription('main', 'monthly') ->trialDays(10) ->create($stripeToken);
This method will set the end time of the subscription period on the database subscription record to tell Sripe/Braintree not to calculate the user's billing information before then.
{Note} Subscriptions are automatically billed if the customer does not cancel before the trial ends, so you should make sure to inform your users of the end of their trial. The
trialUntil
method allows specifying the trial end period by providing a DateTime
instance:
use Carbon\Carbon;$user->newSubscription('main', 'monthly') ->trialUntil(Carbon::now()->addDays(10)) ->create($stripeToken);
You can use onTrial# of the user instance ## method or the
onTrial method of the subscription instance determines whether the user is in the trial period. The following two examples are equivalent:
if ($user->onTrial('main')) { // } if ($user->subscription('main')->onTrial()) { // }Non-Credit Card SubscriptionIf you don’t want to have a trial period available To collect user payment method information, simply set the
trial_ends_at column of the user record to the desired trial end date, which is usually done during user registration:
$user = User::create([ // Populate other user properties... 'trial_ends_at' => now()->addDays(10), ]);
{Note } Make sure you have added theCashier calls this type of reference a "generic experience" because it is not associated with any existing subscription. If the current date does not exceed thetrial_ends_at
date modifier to the model definition.
trail_ends_at value, the
User instance's
onTrial method will return
true:
if ($user->onTrial()) { // 用户在他们的试用期内... }If you want to know explicitly that the user is in the "generic" trial period and has not yet created an actual subscription, then you can use the
onGenericTrial method:
if ($user->onGenericTrial()) { // 用户在他们「一般」试用期... }If you are going to create an actual subscription for the user To subscribe, usually you can use the
newSubsription method:
$user = User::find(1); $user->newSubscription('main', 'monthly')->create($stripeToken);CustomerCreating a CustomerSometimes you may want to create a Stripe customer without a subscription. You can do this using the
createAsStripeCustomer method:
$user->createAsStripeCustomer();Once the customer is created in Stripe, you can later start a subscription.
{Tip} To create a customer in Braintree, use thebank cardcreateAsBraintreeCustomer
method.
cards method on a billable model instance returns a collection of
Laravel\Cashier\Card instances:
$cards = $user->cards();To To retrieve the default card, you can use the
defaultCard method;
$card = $user->defaultCard();
Make sure the card number is on file
You can check if the customer has a credit card stored on their account using the hasCardOnFile
method:
if ($user->hasCardOnFile()) { // }
UpdateCard
updateCard
method can be used to update the user's credit card information. This method accepts a Stripe token and sets a new credit card as the default payment source. :
$user->updateCard($stripeToken);
To synchronize your card information with the customer's default card information via Stripe, you can use the updateCardFromStripe
method:
$user->updateCardFromStripe();
Delete Credit Card
To delete a card, you should first retrieve the customer's card using the cards
method. You can then call the delete
method on the card instance you want to delete:
foreach ($user->cards() as $card) { $card->delete(); }
{Note} If you want to delete the default card, make sure to use the
updateCardFromStripe
method Synchronize the new default card with the database.
deleteCards
method will delete all the user's card information stored by the application:
$user->deleteCards();
{Note} If the user already has a subscription, this should Consider preventing them from deleting the last remaining payment method.
Handling Stripe Webhooks
Both Stripe and Braintree can notify applications of various types through webhooks event. To handle Stripe webhooks, you need to define a route to Cashier's webhook controller. This controller handles all incoming webhook requests and dispatches them to the appropriate controller method:
Route::post( 'stripe/webhook', '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook' );
{Note} Once the route is registered, make sure to configure the webhook in your Stripe control panel URL.
By default, this controller will automatically cancel subscriptions that have too many failed payments (this number can be defined in Stripe settings); in addition, we will soon discover that you can Extend this controller to handle any webhook events you want to handle.
{Note} Please make sure to use Cashier's webhook signature verification middleware to protect incoming requests.
Webhooks & CSRF Protection
Because Stripe webhooks need to bypass Laraval's CSRF protection, please make sure to include it in your VerifyCsrfToken
middleware Contain the URI, or place it outside the web
middleware group:
protected $except = [ 'stripe/*', ];
Define Webhook Event Handler
Cashier automatically unsubscribes for failed payments, but if you have other Stripe webhook events you want to handle, you can extend the Webhook controller. Your method names should match the convention expected by Cashier, more specifically, the method you wish to handle the Stripe webhook should be prefixed with handle
and "camelCase" the name. For example, if you wish to handle the invoice.payment_succeeded
webhook, you should add the handleInvoicePaymentSucceeded
method to the controller:
<?php namespace App\Http\Controllers; use Laravel\Cashier\Http\Controllers\WebhookController as CashierController; class WebhookController extends CashierController{ /** * Handle invoice payment succeeded. * * @param array $payload * @return \Symfony\Component\HttpFoundation\Response */ public function handleInvoicePaymentSucceeded($payload) { // 此处处理事件 } }
Next, in routes The route of the Cashier controller is defined in the /web.php
file:
Route::post( 'stripe/webhook', '\App\Http\Controllers\WebhookController@handleWebhook' );
Subscription failed
If What should I do if the user’s credit card expires? Don’t worry – Cashier includes a webhook controller that can easily unsubscribe users for you. As mentioned above, all you need to do is point the route to the controller:
Route::post( 'stripe/webhook', '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook' );
That’s it! Failed payments will be captured and processed by the controller, which will cancel the user's subscription after Stripe determines that the subscription failed (usually 3 failed payment attempts or more).
##Webhook signature verificationIn order to protect the webhook, you need to useStripe's webhook signature . For convenience, Cashier includes a middleware that verifies that requests passed into the Stripe webhook are valid.
If you want to enable webhook authentication, make sure the value ofstripe.webhook.secret is set in the
services configuration file. The
secret of the webhook can be found from the Stripe User Control Panel.
Route::post( 'braintree/webhook', '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook' );
{Note} Once the route is registered, make sure the webhook URL is configured in the Braintree controller panel .By default, this controller will automatically cancel subscriptions that have too many failed payments (this number can be defined in the Braintree settings); in addition, we will soon discover that you can Extend this controller to handle any webhook events you want to handle. Webhooks & CSRF ProtectionBecause Braintree webhooks need to bypass Laravel's CSRF protection, please make sure to include it in your
VerifyCsrfToken middleware list URI, or place it outside the
web middleware group:
protected $except = [ 'braintree/*', ];
Define Webhook event handler
Cashier automatically unsubscribes for failed payments, but if you have other Braintree webhook events you want to handle, you can extend the Webhook controller. Your method names should match the convention expected by Cashier, more specifically, the method you wish to handle the Braintree webhook should be prefixed with handle
and "camelCase" the name. For example, if you wish to handle the dispute_opened
webhook, you should add the handleDisputeOpened
method to your controller:
<?php namespace App\Http\Controllers; use Braintree\WebhookNotification; use Laravel\Cashier\Http\Controllers\WebhookController as CashierController; class WebhookController extends CashierController{ /** * Handle a new dispute. * * @param \Braintree\WebhookNotification $webhook * @return \Symfony\Component\HttpFoundation\Responses */ public function handleDisputeOpened(WebhookNotification $webhook) { // 此处处理时事件... } }
Subscription failed
What if the user’s credit card expires? Don’t worry – Cashier includes a webhook controller that can easily unsubscribe users for you. Just point the route to the controller:
Route::post( 'braintree/webhook', '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook' );
That’s it! Failed payments will be captured and processed by the controller, which will cancel the user's subscription after Braintree determines that the subscription failed (usually 3 failed payment attempts or more). Don’t forget: configure the webhook URI in your Braintree controller panel.
One-time payment
Simple Payment
{Note} When using Stripe, the
charge
method accepts the amount you want to pay in the smallest unit of the currency used by the application. However, when using Braintree, you should pass the full dollar amount intocharge
Method:
If you want to charge a "one-time" fee to the subscriber's credit card, you can Use the charge
method on a billable model instance: The
// Stripe 接收分为单位的费用... $stripeCharge = $user->charge(100); // Braintree 接收美元为单位的费用... $user->charge(1);
charge
method accepts an array as its second parameter, allowing you to add any The options you want are passed to the underlying Stripe/Braintree. See the Stripe or Braintree documentation for the options available when creating a payment:
$user->charge(100, [ 'custom_option' => $value, ]);
The charge
method will throw an exception if the payment fails. If the payment is successful, this method will return the full Stripe / Braintree response:
try { $response = $user->charge(100); } catch (Exception $e) { // }
Fees and Invoices
Sometimes you may need to pay a one-time fee and also need to generate a fee invoice so that you can provide a receipt in PDF file format to your customers. The invoiceFor
method allows you to do this. For example, to invoice a customer for a "one-time fee" of $5.00:
// Stripe 接收分为单位的费用... $user->invoiceFor('One Time Fee', 500); // Braintree 接收美元为单位的费用... $user->invoiceFor('One Time Fee', 5);
The invoice is immediately charged to the user's credit card. invoiceFor
The method receives an array as the third parameter, allowing you to pass any options you want to the underlying Stripe/Braintree when creating the payment:
$user->invoiceFor('Stickers', 500, [ 'quantity' => 50, ], [ 'tax_percent' => 21, ]);
If you are using Braintree as Your billing provider, you must include the description
option when calling the invoiceFor
method:
$user->invoiceFor('One Time Fee', 500, [ 'description' => 'your invoice description here', ]);
{Note}
invoiceFor
method A Stripe invoice will be created, which will be retried if the payment fails. If you don't want to retry after a failure, you need to call the Stripe API to close it after the first payment fails.
About refunds
If you need to process a refund, you can use refund
method. This method accepts the Stripe charge ID as its only parameter:
$stripeCharge = $user->charge(100); $user->refund($stripeCharge->id);
Invoice
You can use invoices
Method to easily obtain the invoice array of the billing model:
$invoices = $user->invoices(); // 结果包含处理中的发票... $invoices = $user->invoicesIncludingPending();
When listing a list of customer invoices, you can use the invoice helper function to display related invoice information. For example, you might want to list each invoice in a table to make it easier for customers to download them:
<table> @foreach ($invoices as $invoice) <tr> <td>{{ $invoice->date()->toFormattedDateString() }}</td> <td>{{ $invoice->total() }}</td> <td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td> </tr> @endforeach </table>
Generate PDF Invoice
In a route or controller, use the downloadInvoice
method to generate a PDF download of an invoice. This method will automatically generate an appropriate HTTP download response to the browser:
use Illuminate\Http\Request; Route::get('user/invoice/{invoice}', function (Request $request, $invoiceId) { return $request->user()->downloadInvoice($invoiceId, [ 'vendor' => 'Your Company', 'product' => 'Your Product', ]); });