Embed stripe checkout button and record data using webhooks for recurring payments in laravel/php

Bikash Katwal
6 min readDec 9, 2019

Embedding checkout button would be an easy and safest way to handle payments. I am trying to help someone who is struggling to understand stripe payment and its integration on their website as I did. This will not cover all the stripe features but it helps you to integrate the checkout button and make use of webhooks. The document provided by stripe.com is easily understandable but because of a lot of features, it was very difficult for me in the first place. However, following a lot of tutorials on youtube, I have collected some ideas which I can share with you. I hope it may help you in escalating your knowledge. Please refer to https://stripe.com/docs/payments/checkout/subscriptions

You can configure client-only integration and client-server integration for one-time payments and recurring payments. In client-only integration, you can do everything in the stripe dashboard. Only the admin has access to control all the features. For eg: customer does not have access to cancel the subscriptions, he needs to inform the admin to cancel the subscriptions, but in client-server we can fetch all the data using webhooks and store it in our database, giving the privilege to control the actions. Webhooks can be created by adding a different event. For eg: customer.created is an event set up to fetch the response when a customer is created. So, when a new customer is created it will return a response which we can store in our local database.

  1. Following stripe.com, install stripe in your project or you can also download the source from GitHub and integrate it into your project.
composer require stripe/stripe-php

2. When you create an account switch to developers mode in your dashboard. In developers mode, you will see a publishable key(PK) and a secret key(SK). Add both keys to .env files to as

STRIPE_PK=pk_test_bgmxxxxxxxxxxxx
STRIPE_SK=sk_test_TWzxxxxxxxx
STRIPE_WEBHOOK_KEY=whsec_umbxxxxx

For STRIPE_WEBHOOK_KEY, please see step 4.

3. Download ngrok https://ngrok.com/ and follow the documents to create an https link.

ngrok http 8000Forwarding https://38cf87be.ngrok.io -> http://localhost:8000

Remember that we need to add https://38cf87be.ngrok.io in webhook.

4. Under Developers menu we can see Webhooks and inside it, we can see the “Add endpoint” button. Click the button and add the https URL generated using ngrok in “Endpoint URL” i.e https://38cf87be.ngrok.io/stripe/webhook. (append stripe/webhook). Choose the version you need and select the events as per your need. I have here selected a few events which you can see below:

checkout.session.completed
customer.created
customer.deleted
product.created
plan.created
plan.deleted
customer.subscription.created
customer.subscription.deleted

After adding the events you can see Signing secret. Please add the secret in .env.

Adding these events means when these events are hit then it will send a response which we can store in the database. For Eg: When a customer is created in stripe using a stripe dashboard or any action from our web application, then it customer.created will be fired and it will send a response with a JSON response containing customer_id and other information.

5. Test it by using “Send test webhook”, but before that let's see what we need to do in server-side.

First, create a database table with your required columns, For Eg:

public function up()
{
Schema::create('checkouts', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
$table->string('customer_id')->nullable();
$table->string('subscription_id')->nullable();
$table->string('product_id')->nullable();
$table->string('plan_id')->nullable();
$table->boolean('status')->nullable();
$table->timestamps();
});
}

In web.php, please add the following code:

Route::post('stripe/webhook','WebhookController@handleWebhook');

In VerifyCsrfToken.php, add

protected $except = [
'stripe/*',
];

Create WebhookController and a handleWebhook function inside it.

<?php

namespace App\Http\Controllers;

use App\Checkout;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class WebhookController extends Controller
{
public function handleWebhook()
{
$payload = request()->all();
$this->connectWebhook($payload);
}

public function connectWebhook($payload)
{
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey(env('STRIPE_SK'));

// You can find your endpoint's secret in your webhook settings
$endpoint_secret = env('STRIPE_WEBHOOK_KEY');

$payload = @file_get_contents('php://input');
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
$event = null;

try {
$event = \Stripe\Webhook::constructEvent(
$payload, $sig_header, $endpoint_secret
);
} catch (\UnexpectedValueException $e) {
// Invalid payload
http_response_code(400);
exit();
} catch (\Stripe\Exception\SignatureVerificationException $e) {
// Invalid signature
http_response_code(400);
exit();
}

$this->checkEventType($event->type, $event);

http_response_code(200);
}

public function checkEventType($type, $event)
{
switch ($type) {
case 'checkout.session.completed':
$this->handle_checkout_session($event->data->object);
break;
case 'customer.created':
$this->handle_customer_created($event->data->object);
break;
case 'customer.deleted':
$this->handle_customer_deleted($event->data->object);
break;
case 'product.created':
$this->handle_product_created($event->data->object);
break;
case 'plan.created':
$this->handle_plan_created($event->data->object);
break;
case 'plan.deleted':
$this->handle_plan_deleted($event->data->object);
break;
case 'customer.subscription.created':
$this->handle_customer_subscription_created($event->data->object);
break;
case 'customer.subscription.deleted':
$this->handle_customer_subscription_deleted($event->data->object);
break;
}
}

public function handle_checkout_session($session)
{
die(var_dump($session->id));
}

public function handle_customer_created($customer)
{
DB::table('checkouts')
->where('user_id', 6)
->update(['customer_id' => $customer->id, 'status' => 1]);
die(var_dump("Customer created"));
}

public function handle_customer_deleted($customer)
{
DB::table('checkouts')
->where('customer_id', $customer->id)
->update(['status' => 0]);
die(var_dump("Customer deleted"));

}

public function handle_product_created($product)
{
DB::table('checkouts')
->where('user_id', 6)
->update(['product_id' => $product->id]);
die(var_dump("Product created"));
}

public function handle_plan_created($plan)
{
DB::table('checkouts')
->where('user_id', 6)
->update(['plan_id' => $plan->id]);
die(var_dump("Plan created"));
}

public function handle_plan_deleted($plan)
{
DB::table('checkouts')
->where('plan_id', $plan->id)
->update(['status' => 0]);
die(var_dump("Plan deleted"));

}

public function handle_customer_subscription_created($subscription)
{
DB::table('checkouts')
->where('user_id', 6)
->update(['subscription_id' => $subscription->id]);
die(var_dump("customer has been subscribed to a plan"));
}

public function handle_customer_subscription_deleted($subscription)
{
DB::table('checkouts')
->where('subscription_id', $subscription->id)
->update(['status' => 0]);
die(var_dump("Subscription deleted"));
}
}

In the code above, you can see a function connectWebhook, for the code inside this function please see https://stripe.com/docs/webhooks/build. Others functions with handle prepended are to update the table that we have created before. Now we have all the record stored in our database, this will make us easy to work with stripe APIs. Based on the Id stored we can manipulate the data according to our need. Stripe has provided good documentation on using APIs. Please refer to https://stripe.com/docs/api.

6. Now we have completed our setup and server-side code, let’s focus on the front-end/ client-side. On the client-side, we are just displaying “checkout” button which will navigate to stripe checkout page. Let's get back to work now. In the stripe dashboard when you create a product and plan, you will see “Use with checkout” button. Click the button, you can see javascript related to that specific plan.

<!-- Load Stripe.js on your website. -->
<script src="https://js.stripe.com/v3"></script>
<!-- Create a button that your customers click to complete their purchase. Customize the styling to suit your branding. -->
<button
style="background-color:#6772E5;color:#FFF;padding:8px 12px;border:0;border-radius:4px;font-size:1em"
id="checkout-button-plan_GI1dhi4ljEzk0R"
role="link"
>
Checkout
</button>
<div id="error-message"></div><script>
(function() {
var stripe = Stripe('pk_test_bgmxxxxxxxx');
var checkoutButton = document.getElementById('checkout-button-plan_GI1dhi4ljEzk0R');
checkoutButton.addEventListener('click', function () {
// When the customer clicks on the button, redirect
// them to Checkout.
stripe.redirectToCheckout({
items: [{plan: 'plan_GI1dhi4ljEzk0R', quantity: 1}],
// Do not rely on the redirect to the successUrl for fulfilling
// purchases, customers may not always reach the success_url after
// a successful payment.
// Instead use one of the strategies described in
// https://stripe.com/docs/payments/checkout/fulfillment
successUrl: 'https://your-website.com/success',
cancelUrl: 'https://your-website.com/canceled',
})
.then(function (result) {
if (result.error) {
// If `redirectToCheckout` fails due to a browser or network
// error, display the localized error message to your customer.
var displayError = document.getElementById('error-message');
displayError.textContent = result.error.message;
}
});
});
})();
</script>

This script will display a checkout button named “checkout-button-plan_GI1dhi4ljEzk0R”. You can get the plan id from your database and pass it in the script where plan is used. This will work on the basis of the plan id that you render from the database and provide on the script. There are two places where you need to change.

checkout-button-plan_GI1dhi4ljEzk0R -- button id
items: [{plan:<?php echo json_encode($plan_id), quantity: 1}]

So based on the plan_id provided it will navigate to the stripe checkout page and will display the amount related to the plan. You can now provide the card details and pay the amount. Remember here, we are especially focusing on recurring payments. So it will create a customer, subscribes the customer to the plan and save the card details. When all this process is done, webhooks comes into the play and update all the record in the database as we have linked in step 5.

I hope this will help you gain knowledge on stripe checkout and webhooks. For general information related to stripe payment please also refer to https://medium.com/@bikashkshetri/general-queries-related-to-stripe-before-integrating-4b4879a77c5e

--

--