Cashier Trading Toolkit


Laravel Cashier

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.

##Configuration

Stripe

Composer

First, add Stripe’s Cashier package to your project dependencies:

composer require laravel/cashier

Database migration

Before 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 Model

Next, add the

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 Keys

Finally, 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 and decrement 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 Subscription

If 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 the

trial_ends_at date modifier to the model definition.

Cashier calls this type of reference a "generic experience" because it is not associated with any existing subscription. If the current date does not exceed the

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);

Customer

Creating a Customer

Sometimes 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 the

createAsBraintreeCustomer method.

bank card

Receive Credit Cards

The

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 verification

In order to protect the webhook, you need to use

Stripe'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 of

stripe.webhook.secret is set in the services configuration file. The secret of the webhook can be found from the Stripe User Control Panel.

Handling Braintree Webhooks

Both Stripe and Braintree can notify applications of various events through webhooks. To handle Braintree webhooks, you need to define a route to the Cashier webhook controller. This controller handles all incoming webhook requests and dispatches them to the appropriate router method:

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 Protection

Because 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 into charge 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',    
         ]);
     });
This article was first published on the LearnKu.com website.