Basic guide to accepting payments with stripe

damilola jolayemi
Damilola Jolayemi
Published in
8 min readAug 27, 2017

A year ago i worked on a project where i had to do payment integration, it was first time, and looking back to how i handled it, i realize i made a few mistakes on the project. This post can serve as a guide to any beginner looking to integrate payments in their application.

I’ll be using using php/Laravel and the Stripe api in this post. Even though i call this a beginner tutorial, a basic understanding of Laravel is required. The knowledge shared in this post should be transferable to other languages and frameworks too.

We are gonna create an application called grape-shop, users will be able to subscribe to our services on it. This post will cover the moment a user lands our product page to when he/she gets to the success page due to successful charge or redirect back to product page due to an unsuccessful charge.

Get full code here:

Configure Laravel and cashier.

Create a new laravel 5.4 project: grape-shop and configure your database in .env file

composer create-project --prefer-dist laravel/laravel grape-shop

We are gonna use a Laravel cashier to work with Stripe.

Laravel Cashier provides an expressive interface to Stripe’s services. With it we can handle subscriptions management, coupons, cancellation grace periods, and generate invoice PDFs. It also can be used with Braintree from paypal.

composer require "laravel/cashier":"~7.0"

Register laravel cashier service provider. Paste line below in config/app.php

Laravel\Cashier\CashierServiceProvider::class

We are going to add some more columns to your users table

$table->string('stripe_id')->nullable();
$table->string('card_brand')->nullable();
$table->string('card_last_four')->nullable();
$table->timestamp('trial_ends_at')->nullable();

and also create a subscriptions table to keep all of our users subscriptions.

php artisan make:migration create_subscriptions_table --create=subscriptions

then … remember to have set your database in .env file

php artisan migrate

Update your user model to use the Billable trait provided by Laravel cashier

<?phpnamespace App;use Laravel\Cashier\Billable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable, Billable;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
}

Update your .env file to contain two new configuration values needed to work with Stripe. To get this values, create or login to your stripe account, go to account settings panel, api keys tab.

STRIPE_KEY= stripe_secret_key_goes_here
STRIPE_SECRET= stripe_secret_key_goes_here

Create authentication because we require users to have an account before they can subscribe to our grape-shop.

php artisan make:auth

Create payment page.

Add a new menu to app.blade.php

<li><a href="{{ route('subscription.new') }}">Subscribe</a></li>

Update web.php to contain the 3 routes below. The first route will display the payment page, the second route will process the payment and the third route links to the success page.

Route::group(['middleware' => 'auth'], function() {    Route::get('/subscription/index', 'SubscriptionsController@index')->name('subscription.index');    Route::post('/subscription/create', 'SubscriptionsController@create')->name('subscription.create');    Route::get('/subscription/success', 'SubscriptionsController@success')->name('subscription.success');});

Also create a dedicated controller for handling the subscriptions

php artisan make:controller SubscriptionsController

Add the code below to the SubscriptionsController.php file just created. It’s an index method that returns a page where the user will make payment to subscribe.

public function index()
{
return view('subscriptions.index');
}

Next, create index.blade.php file at resources/subscriptions and paste the code below into it. The code below contains two inputs (but it can contain as many as you require), phone number input and card input.

The card input is provided by Stripe.js using Stripe elements . It makes it easy for us to collect sensitive user information without letting it touch our server. We will be able to send user info directly to Stripe from the user’s browser.

This is the recommended way to go about it, it is not advisable to post sensitive user information to Stripe from your server.

Browser -> Stripe server -> Browser -> Your server -> Charge user.

@extends('subscriptions.layouts.app')@section('content')<div id="img-column" class="column img-column">
<img src="/products/grapes.jpg">
</div>
<div class="column has-text-centered product-info">
<p>
Our grape shop subscritption offers great service that enables you to get the maximum enjoyment from your choice fruit.
</p>
</div>
<div id="input-column" class="column has-text-centered">
<form action="{{ route('subscription.create') }}" method="POST">
{{ csrf_field() }}
<input name="stripe_token" type="hidden" type="text"/><div class="field">
<div class="control has-icons-left">
<input class="input {{ $errors->has('phone') ? ' is-danger' : '' }}" type="phone" name="phone" required autofocus>
<span class="icon is-left">
<i class="fa fa-phone-square"></i>
</span>
</div>
</div>
<div id="card-element" class="field"></div><hr><div id="session-messages" class="column has-text-centered _session-messages">
<div class="error" role="alert"></div>
@include('subscriptions.partials._session-messages')
</div>
<div class="field">
<button type="submit" id="pay-button" class="button is-primary is-fullwidth">
<strong>Pay $100/Month</strong>
</button>
</div>
</form>
</div>
@endsection('content')
payment page

Also included in the code above is a partial for handling Stripe error responses. Create a file name _session-message.blade.php in resources/subscriptions/partials. It should contain code below:

@if (session('cardError'))<div class="error-stripe" role="alert">
{{ session('cardError') }}
</div>
@endif@if (session('invalidRequest'))<div class="error-stripe" role="alert">
{{ session('invalidRequest') }}
</div>
@endif@if (session('apiConnectionError'))<div class="error-stripe" role="alert">
{{ session('apiConnectionError') }}
</div>
@endif@if (session('generalError'))<div class="error-stripe" role="alert">
{{ session('generalError') }}
</div>
@endif

We need to create the layout file our pages are extending from. Make a new file app.blade.php in resources/subscriptions/layouts

<!DOCTYPE html>
<html lang="{{ config('app.locale') }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Grape-shop Subscription</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" />
<link rel="stylesheet" href="{{ asset('css/bulma.css') }}">
<link rel="stylesheet" href="{{ asset('css/stripe.css') }}">
</head>
<body class="app"><div class="container"><section class="section is-fullheight">
<div class="container">
<div class="column box is-5">
@yield('content') </div>
</div>
</section>
</div> <script src="https://js.stripe.com/v3/"></script>
<script src="{{ asset('js/stripe-config.js') }}"></script>
</body>
</html>

Create stripe.css file and paste the code below in it:

@import url('https://fonts.googleapis.com/css?family=Maven+Pro');
@import url('https://fonts.googleapis.com/css?family=Roboto:100,400');
#session-messages {
font-size: 12px !important;
}
div.box {
margin: auto;
padding: 0px;
}
#img-column {
padding: 0px;
max-height: 260px;
}
img {
height: 260px;
width: 100%;
border-top-right-radius: 5px;
border-top-left-radius: 5px;
}
hr {
margin: 0px;
}
.product-info {
background-color: #e8e8e8;
padding: 0px;
}
.product-info > p {
padding: 12px;
padding-top: 30px;
padding-bottom: 30px;
font-family: 'Maven Pro', sans-serif;
color: #00D1B2;
}
#input-column {
padding-top: 30px;
padding-bottom: 30px;
}
.error {
display: none;
font-size: 13px;
}
.error.visible {
display: inline;
}
.error {
color: #E4584C;
}
/***********
SUCCESS PAGE
*************/
#thank-you h1 {
font-family: 'Roboto', sans-serif;
padding-top: 30px;
margin-bottom: 0px;
color: #00D1B2;
}
#thank-you > p {
font-family: 'Maven Pro', sans-serif;
padding-bottom: 20px;
}
#thank-you {
background-color: #e8e8e8;
padding: 0px;
}
.big {
font-size: 200px;
color: #00D1B2;
}
.sub-title {
font-family: 'Maven Pro', sans-serif;
font-size: 40px;
}
#message {
background-color: #e8e8e8;
padding: 0px;
}
#message p {
font-family: 'Maven Pro', sans-serif;
padding: 30px 20px 30px 20px;
color: #00D1B2;
}

Create stripe-config.js file and paste the code below in it. If you are using es2016 remember to transpile it down. It’s easy to do with Laravel. If you are not yet using es2016, please start using it.

In the stripe-config.js code below, when the user fills in the info requested, we collect the card data and send it to Stripe, Stripe returns a token to us, we then submit this token along with the other inputs we collected in the payment page to our server to charge the user. You’ll need to put your api key in the code for this to work.

(function() {var stripe = Stripe('stripe_api_key_goes_here');
var elements = stripe.elements();
var card = elements.create('card', {
style: {
base: {
iconColor: '#666EE8',
color: '#31325F',
lineHeight: '40px',
fontWeight: 300,
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSize: '15px',
'::placeholder': {
color: '#CFD7E0',
},
},
}
});
card.mount('#card-element');
function setOutcome(result) {
var form = document.querySelector('form');
var errorElement = document.querySelector('.error');
errorElement.classList.remove('visible');
if (result.token) {
form.querySelector('input[name=stripe_token]').value = result.token.id;
form.submit();
} else if (result.error) {
errorElement.textContent = result.error.message;
errorElement.classList.add('visible');
document.querySelector('#pay-button').disabled = false;
document.querySelector('#pay-button').textContent = initialSubmitText;
}
}
card.on('change', function(event) {
setOutcome(event);
});
document.querySelector('form').addEventListener('submit', function(e) {
e.preventDefault();
document.querySelector('#pay-button').disabled = true;
var initialSubmitText = document.querySelector('#pay-button').textContent;
document.querySelector('#pay-button').textContent = "Processing...";
stripe.createToken(card).then(setOutcome);
});
})();

Next, lets add a create method to our SubscriptionsController.php where we would charge the user. But first, before a user can subscribe there must be a plan to subscribe to. You can create a plan from your Stripe dashboard or using code below. Read more on plans.

Plan::create(array(    'id' => $plan_id,
'name' => $plan_name,
'amount' => $plan_amount,
'currency' => $plan_currency,
'interval' => $plan_interval
));

Update SubscriptionsController.php to contain the two methods below.

In the first method, create, we are subscribing the authenticated user using the newSubscription method provided by the billable trait we added earlier to the user model. The first argument passed is the plan name, the second argument is the specific plan identifier of the plan. The create method chained will initiate the transaction and also update the database with the billing information.

If the charge is successful we are redirected to the success page, if not, we flash the message gotten from Stripe to session and return back to the payment page.

You can read more on stripe subscriptions here and here.

The second method, success, returns the success page for our subscription.

use Session;
use Illuminate\Support\Facades\Auth;

public function create(Request $request)
{
//validate request
try {
Auth::user()->newSubscription('grape shop', 'grape-shop')->create($request->stripe_token);
//save other inputs to db
//do some other stuffs
return redirect()->route('subscription.success');
} catch (Card $e) {
$err = $e->getJsonBody()['error'];
Session::flash('cardError', $err['message']);
return back();
} catch (\Stripe\Error\InvalidRequest $e) {
$err = $e->getJsonBody()['error'];
Session::flash('invalidRequest', $err['message']);
return back();
} catch (\Stripe\Error\ApiConnection $e) {
$err = $e->getJsonBody()['error'];
Session::flash('apiConnectionError', $err['message']);
return back();
} catch (\Stripe\Error\Base $e) {
Session::flash('generalError', 'There was an error processing your payment.');
return back();
};
} return back();
};
}
public function success()
{
return view('subscriptions.success');
}

Finally, the success page, make a file named success.blade.php in resources/subscriptions

@extends('subscriptions.layouts.app')
@section('content')
<div id="thank-you" class="column has-text-centered">
<h1 class="title is-3">
Thank You
</h1>
<p>
{{ Auth::user()->email }}
</p>
</div>
<div class="column has-text-centered"><i class="fa fa-check-circle-o big"></i>
<p class="sub-title">
Succesful Payment
</p>
</div><div id="message" class="column has-text-centered">
<p>
You have been successfully charged for this transaction. A receipt for this purchase has been sent to your email.
</p>
</div>
@endsection('content')
success page

Charge but no subscription

If we choose to only charge users in our grape-shop and not subscribe them to any plan. We will import the Stripe and Charge classes.

use Stripe\Charge;
use Stripe\Stripe;

then create a controller method to perform the charge action. Read more on charging.

Stripe::setApiKey(env('STRIPE_KEY'));$charge = Charge::create(
array(
"amount" => $price,
"source" => $stripe_token,
"currency" => "$currency",
"description" => 'Charge for Grape shop',
)
);

Sending Receipts

Stripe allows you to send email receipts automatically on a successful charge or when a payment is refunded. You can do this manually from your stripe dashboard or by providing an email address to the charge request. Read more here

Stripe::setApiKey(env('STRIPE_KEY'));$charge = Charge::create(
array(
"amount" => $price,
"source" => $stripe_token,
"currency" => "$currency",
"description" => 'Charge for Grape shop',
'receipt_email' => Auth::user()->email
)
);

For receipts on subscriptions, set additional user detail for the customer email and also ensure to enable the option email customers for successful payments in your account settings.

Auth::user()->newSubscription('plan name', 'plan-id')->create($stripeToken, [
'email' => Auth::user()->email,
]);

Done!

If you have any questions or corrections, please leave a comment for me.

Gracias.

--

--