In this article you will learn:
-
How to configure Stripe with Laravel
-
How to make single charges through Stripe
The example project is a website where you can sell digital products, like ebooks or audiobooks. The user can sign in and pay through Stripe. An administrative area will show the order information.
The full code is hosted on Github.
Project Configuration
Create a new project with:
1 |
laravel new stripe-tutorial |
Or:
1 |
composer create-project --prefer-dist laravel/laravel stripe-tutorial |
In composer.json install Cashier:
1 2 3 4 5 6 7 8 9 |
"require": { "php": ">=5.5.9", "laravel/framework": "5.2.*", "laravel/cashier": "~6.0" }, |
In the terminal:
1 |
composer update --prefer-dist |
Go to config/app.php and add the Cashier Provider to the providers and alias sections.
1 2 3 4 5 6 7 8 9 |
'providers' => [ // Others providers Laravel\Cashier\CashierServiceProvider::class, // Others providers ], |
Despite using Cashier, there is no need to setup subscriptions right now. We only need a simple payment, so let’s use the default SDK instructions on the Stripe site.
We will be using SQLite for storing data. Create the database file in database/database.sqlite (assuming you are in the root of the project):
1 |
touch database/database.sqlite |
Set the environmental variables in the .env file, located in the root of the project:
1 |
DB_CONNECTION=sqlite |
You can delete the DB_DATABASE, DB_USERNAME, DB_PASSWORD, DB_HOST and DB_PORT variables.
Finally, change the line in config/database.php :
1 |
'default' => env('DB_CONNECTION', 'mysql'), |
To:
1 |
'default' => env('DB_CONNECTION', 'sqlite'), |
Launch your server and you should be able to see the project running in http://localhost:8000
1 |
php artisan serve |
Stripe Configuration
Secret Key
First, create a Stripe account in https://dashboard.stripe.com/register or login https://dashboard.stripe.com/login.
In your dashboard, click in Your Account → API Keys. You will find a set of test keys and live keys. Don’t bother with the live ones – you will only use them when you application is ready to charge users. For now, copy the Test Secret Key and the Test Publishable Key and paste in your .env file:
1 2 |
STRIPE_KEY=pk_test_5RgFyourstripekeyIRMGA3b STRIPE_SECRET=sk_test_xCAyourstripesecret36zaN |
We need to put the secret key in a secure place. The .env file is exactly what we need. This file, by default, are ignored by version control, so you can push in to the server without worrying.
IMPORTANT: If you are developing this in a production server you need to manually create an .env file. Generally, you want to copy the local/development one and just adapt to your server configuration.
After that, go to app/services.php and add the following:
1 2 3 4 5 |
'stripe' => [ 'model' => App\User::class, 'key' => env('STRIPE_KEY'), 'secret' => env('STRIPE_SECRET'), ], |
The env function allow us to get the values defined in the .env file. If you follow this instruction Laravel probably have already taken care of it.
Cashier Tables
Cashier needs a user and a subscription table. We’re only going to use the subscription table in later tutorials, but is good to have everything ready when the time comes.
For the users table, let’s use the default that comes with Laravel. Since we need an administrative area to see the orders, add the admin column in the up() function. It should be in app/database/migrations:
1 2 3 4 5 6 7 8 9 10 11 12 |
public function up() { Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->string('password'); $table->boolean('admin')->default(false); $table->rememberToken(); $table->timestamps(); }); } |
Create the following migration:
1 |
php artisan make:migration add_cashier_tables |
Your migration should look like app/database/migrations/2016_08_24_202103_add_cashier_tables.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class AddCashierTables extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('users', function ($table) { $table->string('stripe_id')->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->integer('user_id'); $table->string('name'); $table->string('stripe_id'); $table->string('stripe_plan'); $table->integer('quantity'); $table->timestamp('trial_ends_at')->nullable(); $table->timestamp('ends_at')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('subscriptions'); } } |
Here we are adding some fields to the users table and creating the subscription table. We are only going to use the subscription in the next tutorial, so don’ worry about. Don’t forget to drop the subscriptions table in the down() function!
Execute the migration:
1 |
php artisan migrate |
Configuring the User
Laravel also comes with a User model out of the box. In app/User.php add the Billable trait:
1 2 3 4 5 6 7 |
use Laravel\Cashier\Billable; class User extends Authenticatable { use Billable; //Protected etc. } |
This is another very important aspect of Cashier. The Billable trait is responsible to add some methods to the user model. Through then we can subscribe the user etc. Again, we’re not going to use it now, so don’t worry about.
Creating the Models and Migrations
In addition to the user model, we also need the product and order models. You can create both the models and migrations with the following commands:
1 |
php artisan make:model Product -m |
1 |
php artisan make:model Order -m |
Product Migration
In app/database/migration/2016_08_25_001434_create_products_table.php:
1 2 3 4 5 6 7 8 9 10 |
public function up() { Schema::create('products', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('description'); $table->integer('price'); $table->timestamps(); }); } |
Stripe calculate the price in cents. For example, if you need to charge $19.90, pass 1990 to the checkout form. The price must be in integer, not in decimal.
Order Migration
In app/database/migration/2016_08_25_001822_create_orders_table.php:
1 2 3 4 5 6 7 8 9 |
public function up() { Schema::create('orders', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id'); $table->integer('product_id'); $table->timestamps(); }); } |
Defing Relationships
We need to show the orders in the administrative area. The easiest way is to define the relationships in the Order model.
In app/Order.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?php namespace App; use Illuminate\Database\Eloquent\Model; class Order extends Model { protected $fillable = ['email', 'product']; public function user() { return $this->hasOne('App\User'); } public function product() { return $this->hasOne('App\Product'); } } |
Populating the Database
Let’s use Tinker to populate the database:
1 |
php artisan tinker |
Sometimes you wouldn’t be able to use some keys in Tinker, for example, the up key to return to a previously command. You can use rlwrap to solve this issue:
1 |
rlwrap php artisan tinker |
In tinker, enter the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$user = new App\User $user->name = "Admin" $user->email = "admin@admin.com" $user->password = bcrypt(123) $user->admin = true $user->save() $product = new App\Product() $product->name = "Laravel Book for Beginners" $product->description = "An introduction to Laravel." $product->price = 1990 $product->save() $product = new App\Product() $product->name = "Laravel Book for Experts" $product->description = "Increase your mastery of Laravel." $product->price = 3990 $product->save() |
Creating the Controller
With the auth command Laravel handles all the authentication and registration for us:
1 |
php artisan make:auth |
The product controller is responsible for showing all the products for the user:
1 |
php artisan make:controller ProductController |
In app/Http/Controllers/ProductController.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php namespace App\Http\Controllers; use App\Http\Requests; use Illuminate\Http\Request; class ProductController extends Controller { /** * Show the index page. * * @var App\Product $products * @return Illuminate\View\View */ public function index() { $products = \App\Product::all(); return view('index', compact('products')); } } |
This controller simply loads all our products from the database in to the template.
To handle our payments we need one more controller:
1 |
php artisan make:controller OrderController |
In app/Http/Controllers/OrderController.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
<?php namespace App\Http\Controllers; use Auth; use App\Order; use App\Http\Requests; use Illuminate\Http\Request; class OrderController extends Controller { /** * Get all orders. * * @var App\Order $orders * @return Illuminate\View\View */ public function getAllOrders() { $orders = Order::all(); return view('admin', compact('orders')); } /** * Make a Stripe payment. * * @param Illuminate\Http\Request $request * @param App\Product $product * @return chargeCustomer() */ public function postPayWithStripe(Request $request, \App\Product $product) { return $this->chargeCustomer($product->id, $product->price, $product->name, $request->input('stripeToken')); } /** * Charge a Stripe customer. * * @var Stripe\Customer $customer * @param integer $product_id * @param integer $product_price * @param string $product_name * @param string $token * @return createStripeCharge() */ public function chargeCustomer($product_id, $product_price, $product_name, $token) { \Stripe\Stripe::setApiKey(env('STRIPE_SECRET')); if (!$this->isStripeCustomer()) { $customer = $this->createStripeCustomer($token); } else { $customer = \Stripe\Customer::retrieve(Auth::user()->stripe_id); } return $this->createStripeCharge($product_id, $product_price, $product_name, $customer); } /** * Create a Stripe charge. * * @var Stripe\Charge $charge * @var Stripe\Error\Card $e * @param integer $product_id * @param integer $product_price * @param string $product_name * @param Stripe\Customer $customer * @return postStoreOrder() */ public function createStripeCharge($product_id, $product_price, $product_name, $customer) { try { $charge = \Stripe\Charge::create(array( "amount" => $product_price, "currency" => "brl", "customer" => $customer->id, "description" => $product_name )); } catch(\Stripe\Error\Card $e) { return redirect() ->route('index') ->with('error', 'Your credit card was been declined. Please try again or contact us.'); } return $this->postStoreOrder($product_name); } /** * Create a new Stripe customer for a given user. * * @var Stripe\Customer $customer * @param string $token * @return Stripe\Customer $customer */ public function createStripeCustomer($token) { \Stripe\Stripe::setApiKey(env('STRIPE_SECRET')); $customer = \Stripe\Customer::create(array( "description" => Auth::user()->email, "source" => $token )); Auth::user()->stripe_id = $customer->id; Auth::user()->save(); return $customer; } /** * Check if the Stripe customer exists. * * @return boolean */ public function isStripeCustomer() { return Auth::user() && \App\User::where('id', Auth::user()->id)->whereNotNull('stripe_id')->first(); } /** * Store a order. * * @param string $product_name * @return redirect() */ public function postStoreOrder($product_name) { Order::create([ 'email' => Auth::user()->email, 'product' => $product_name ]); return redirect() ->route('index') ->with('msg', 'Thanks for your purchase!'); } } |
This controller holds the core of our application:
-
getAllOrders() – simply return all orders and show in the admin area.
-
postPayWithStripe() – responsible for getting the parameters. We pass a product object and the stripeToken, which comes from the request. This token holds the credit information from the client.
-
chargeCustomer() – we need to make sure that the user is in the Stripe system. Thus, we check if the user is already a Stripe customer. If positive, we retrieve it. Otherwise, a function create a new customer. In the end, we will have created or retrieved a customer.
-
createStripeCharge() – we can pass values to create a charge using the Charge::create. An important detail is that the “customer” attribute is equal to the customer ID. This is not the user ID on our application, but the Stripe ID. Remember, when dealing the customers we are talking about Stripe.
-
createStripeCustomer() – creates a new Stripe Customer. We also set the stripe_id in our database as the customer ID. Note that we’re always referring to the user Auth::user().
-
isStripeCustomer() – to check if a user is a Stripe customer we only need to see if the stripe_id is not null. Remember that in the createStripeCustomer we set the stripe_id to the customer ID.
-
postStoreOrder() – finally, we create a new Order redirect the user to the index page.
At last, create a new middleware to only limit access to the administrative area:
1 |
php artisan make:middleware AdminMiddleware |
In app/Http/Middleare/AdminMiddleware.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?php namespace App\Http\Middleware; use Auth; use Closure; class AdminMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if (Auth::user() && Auth::user()->admin == false) { return redirect()->route('index'); } return $next($request); } } |
This is a very simply middleware. The handle function checks if the user is authenticate and if he is the administrator. If positive the request continues, otherwise we redirect the user to the index.
Register the admin middleware in app/Http/Kernel.php:
1 2 3 4 5 6 7 8 |
protected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'admin' => \App\Http\Middleware\AdminMiddleware::class, ]; |
Defining the Routes
In app/Http/Route.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<?php Route::auth(); // Product Routes Route::get('/', [ 'uses' => 'ProductController@index', 'as' => 'index', 'middleware' => 'auth' ]); // Order Routes Route::get('/admin', [ 'uses' => 'OrderController@getAllOrders', 'as' => 'admin', 'middleware' => 'admin' ]); Route::post('/pay/{product}', [ 'uses' => 'OrderController@postPayWithStripe', 'as' => 'pay', 'middleware' => 'auth' ]); Route::post('/store', [ 'uses' => 'OrderController@postPayWithStripe', 'as' => 'store', 'middleware' => 'auth' ]); |
Creating the Views
In resources/views/index.blade.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
@extends('layouts.app') @section('content') <div class="container"> <div class="col-md-10"> @if(session('msg')) <div class="alert alert-success" role="alert"> {{ session('msg') }} </div> @endif @if(session('error')) <div class="alert alert-danger" role="alert"> {{ session('error') }} </div> @endif <div class="row"> @foreach ($products as $product) <form action="{{ route('pay', $product->id) }}" method="POST"> {{ csrf_field() }} <div class="col-sm-5 col-md-5"> <div class="thumbnail"> <div class="caption"> <h3>{{ $product->name }}</h3> <p>{{ $product->description }}</p> <p>Buy for ${{ substr_replace($product->price, '.', 2, 0) }}</p> <p> <script src="https://checkout.stripe.com/checkout.js" class="stripe-button" data-key="{{ env('STRIPE_KEY') }}" data-amount="{{ $product->price }}" data-name="Stripe.com" data-description="Widget" data-locale="auto" data-currency="usd"> </script> </p> </div> </div> </div> </form> @endforeach </div> </div> </div> @endsection |
If you are familiar with Blade the only thing different here is the Stripe button. There is no need to set up a form since we’re using the checkout.js solution. When the user clicks on Pay With Stripe, our application sends a request to validate the credit card information and returns the request.
An important aspect of checkout.js is that we can pass various parameters. For example, here the amount is equal to the product price, as well as the data-description is the ebook name. Besides, we’re also passing the currency, which in this case is USD. It’s very important that you put the currency that your account supports. Otherwise, Stripe will throw an error.
A “stripeToken” is returned if everything is correct. We used this token to charge the user. Visit https://stripe.com/docs/checkout/tutorial for more information.
In resources/views/admin.blade.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
@extends('layouts.app') @section('content') <div class="container"> <div class="col-md-10"> <h3>Orders</h3> @foreach ($orders as $order) <ul class="list-group"> <li class="list-group-item"> {{ $order->email}} - {{ $order->product }} </li> </ul> @endforeach </div> </div> @endsection |
The administrative area only shows the user e-mail and product for each placed order. From here you can send your digital product, for example. We’re nothing going to add more functionality than this since this tutorial is about showing the Stripe payment process. This page is accessible through localhost:8000/admin only for the admin user.
Testing
For testing we can use some dummy credit card. Click on Pay With Stripe and put 4242 4242 4242 4242 as a credit card number as well any email. The expiration date can be any future date and the CVC any three or four digits. Click in Pay and you should be seeing a thanks message. Login as the administrator and head to localhost:8000/admin. Your order should be in place.
Conclusion
Stripe makes single payments very easy to handle. Remember that you also need to create a Stripe Customer for every user in your application. Without this, you probably are going to find errors with the token.
After creating or retrieving a customer, the Stripe::charge method takes care of charging the given credit card. Our application only starts to handle the payment after the confirmation. From here you could save the data in Orders table or send an email to the user. For more information visit https://stripe.com/docs.
Share you thoughts in the comment section below.
Thank you for this clean explanation of stripe integration in laravel.
I just have not seen how to handle subscription , something like $user->newSubscription ….
Another great tutorial. Keep it up. Like your topics and simple approach a lot.
What is the product_id for when charging the customer?
Seems like you passed it but didn’t make use of it..
Thanks
hi!
i have some problem with the display 🙁
this is an example of my admin.blade.php and i have the same issue in index.blade.php… (php:5.6.25,laravel 5.2,i just change sqlite for mysql)
@extends(‘layouts.app’) @section(‘content’)
Orders
@foreach ($orders as $order)
{{ $order->email}} – {{ $order->product }}
@endforeach
@endsection
sorry for my english i’ m from france ,how can help me please !
Hi
I’m trying to integrate this code into my project but it’s not working, then I debug it and found stripeToken “$request->input(‘stripeToken’)” always empty on OrderController postPayWithStripe method, can you please tell me is this problem or any other problems?
Thanks
Hi,
@Sam, @Sandy,
There is a few things to change to make this example working. Orders migration is out of date in this example. Orders table contain product_id and user_id keys but the OrderController try to save email and product name in the method postStoreOrder. The admin.blade view and Order model needs to be changed accordingly.
1) In Order model change hasOne to belongsTo relations for both products() and user() methods:
//in App\Order.php
class Order extends Model
{
protected $fillable = [‘user_id’, ‘product_id’];
public function user()
{
return $this->belongsTo(‘App\User’);
}
public function product()
{
return $this->belongsTo(‘App\Product’);
}
}
2) In orders view:
//in admin.blade.php
@foreach ($orders as $order)
{{ $order->user()->first()->email}} – {{ $order->product()->first()->name }}
@endforeach
3) in OrderController.php
public function createStripeCharge($product_id, $product_price, $product_name, $customer)
{
//…all good until last line
return $this->postStoreOrder($product_id);
}
…
//Change last method call to save product_id and user_id
public function postStoreOrder($product_id)
{
Order::create([
‘user_id’ => Auth::user()->id,
‘product_id’ => $product_id
]);
//…all good after this
}
Working example here : https://github.com/remichautemps/laravel-stripe-bitcoin