Laravel

Laravel Cashier Stripe Idempotency key to avoid duplicate payments (Examples + Source code)

Share your learning

Some time ago, I was working on a project which needed to use a laravel cashier (stripe). After finishing the basic setup and building transaction flow, I encountered the issue of duplicate payments. If a user clicked twice on the pay button or something happened in the networks and the user clicked again to pay then the user can be charged twice

After a little bit of research about it, I reached idempotency. So, what is this idempotency and what is the role of the idempotency key in the stripe transactions?

What is an Idempotency key in stripe?

In general, Idempotency is a web API design principle and it helps to apply the same operation multiple times without changing the outcome. So, this term is useful to prevent duplicate payments.

Idempotency in stripe simply stores the result of the first request made by the particular idempotency key. After the first request, other requests with the same key return the same result, including 500 errors. Hence it prevents the duplicate payments or accidentally charges the user twice.

Idempotency key can be up-to 255 chars long and it should be unique like UUID v4 as suggested by the stripe.

Example of laravel cashier (stripe)

Take an example of an e-commerce store.

  1. Users select the product and click on the buy button.
  2. It will open the cart page, users can add more qty to it and then click on the checkout button.
  3. Now In the checkout controller, we will generate the idempotency key and pass it to the payment form.
  4. On the payment form, the user will confirm the shipping address, product amount, other charges and enter the card details. Then he/she clicks on the proceed payment button.
  5. Now, with this payment request we simply pass that idempotency key to the stripe charges API.
  6. If a user accidentally clicks twice or thrice on the payment button even then the idempotency key will be the same and hence the user will charge only once.

Laravel Cashier (stripe) solution for idempotency key

As I was not able to find the solution to use idempotency key with laravel cashier package. This package has the Billable trait to use with the User model and this trait enables us to charge the user.

$user->charge($amount, $paymentMethod, $options);

As per stripe documentation we can pass an idempotency key in the second array parameter of the charge object.

$stripe = new \Stripe\StripeClient("sk_test_your_key");
$charge = $stripe->charges->create([
  'amount' => 2000,
  'currency' => 'usd',
  'source' => 'tok_mastercard', // obtained with Stripe.js
  'description' => 'My First Test Charge (created for API docs at https://www.stripe.com/docs/api)'
], [
  'idempotency_key' => 'ZvAFIiqjW9nGvrmC'
]);

So, I decided to override the charge method of billable trait, but after digging a little deeper, I found out that this charge method of billable trait uses stripe payment Intent to create and confirm the charge.

Now, again I reached the stripe documentation and understand how to pass idempotency key to the stripe payment intent.

Extend the billable trait of laravel cashier

It’s time to create our own trait and extend the Laravel cashier billable trait inside our custom billable trait. So, the charge method of billable trait uses the createPayment method to execute the payment with stripe payment Intent and we need to override this method.

public function charge($amount, $paymentMethod, array $options = [])
    {
        $options = array_merge([
            'confirmation_method' => 'automatic',
            'confirm' => true,
        ], $options);

        $options['payment_method'] = $paymentMethod;

        $payment = $this->createPayment($amount, $options);

        $payment->validate();

        return $payment;
    }

As per Stripe\Service\PaymentIntentService::create we can pass two parameters to stripe payment Intent and in the second parameter, we can pass idempotency key.

<?php

namespace App\Traits;

use Laravel\Cashier\Billable;
use InvalidArgumentException;
use Stripe\Charge as StripeCharge;
use Laravel\Cashier\Payment;

/**
 * extend Laravel Cashier Billable
 */trait SbsharmaBillable
{
    use Billable;

    public function createPayment($amount, array $options = [])
    {
        $options = array_merge([
            'currency' => $this->preferredCurrency(),
        ], $options);

        $options['amount'] = $amount;

        if ($this->hasStripeId()) {
            $options['customer'] = $this->stripe_id;
        }

        $params = [];
        if ( array_key_exists('idempotency_key', $options) ) {
            $params['idempotency_key'] = $options['idempotency_key'];
            unset($options['idempotency_key']);
        }

        return new Payment(
            $this->stripe()->paymentIntents->create($options, $params)
        );
    }
}

Then I used this trait in the User model (billable model) instead of the Laravel cashier Billable trait, like given below.

class User {
 use SbsharmaBillable;  //Billable
}

Sum-up the solution for duplicate payment with laravel stripe

Let’s charge the user with an idempotency key like given below in the example code.

$options = [
    ‘description’ => ‘’,
    ‘metadata’ => [],
    ‘shipping’ => [ ],
               'idempotency_key' => $request->idempotency_key
            ]

$user->charge($amount, $payment_method, $options);

That’s it, the charge method will pass the idempotency key to createPayment method and then it will create the payment Intent with idempotency key to prevent duplicate charges.

I hope you like it. See you in the next laravel learning corner.

Satpal

View Comments

Recent Posts

How to Switch PHP Versions in XAMPP Easily: Managing Multiple PHP Versions on Ubuntu

Today we are going to learn about managing multiple PHP versions on ubuntu with xampp.…

1 year ago

How to Use Coding to Improve Your Website’s SEO Ranking?

Let's understand about how to use coding to improve your website's SEO. In today’s computerized…

1 year ago

Most Important Linux Commands for Web Developers

Let's understand the most important linux commands for web developers. Linux, as an open-source and…

1 year ago

Top 75+ Laravel Interview Questions Asked by Top MNCs

Today we are going to discuss top 75+ Laravel interview questions asked by top MNCs.Laravel,…

1 year ago

Mailtrap Integration for Email Testing with Laravel 10

Today we will discuss about the Mailtrap integration with laravel 10 .Sending and receiving emails…

1 year ago

Firebase Cloud Messaging (FCM) with Ionic 6: Push Notifications

Today we are going to integrate FCM (Firebase Cloud Messaging) push notifications with ionic application.Firebase…

1 year ago