Integrate Stripe payment into your PHP projects
June 17, 2024
/
14 minutes
TL;DR
Complete functional code repository here (with cart simulation) Simple example on how to implement Stripe PaymentIntent (SCA compliant) in your project to enable payment.
Introduction
If you're developing websites, SaaS, web applications, or pretty much anything else, you've probably needed certain payment features at some point for a project. Most of the time, if you're building your project on a CMS, you will have access to contributory projects (such as WooCommerce for WordPress or Commerce for Drupal) that will handle the payment process for you, and you'll just need to provide API keys to enable payments. This is also handled for you if your project is built on a platform like Sylius, PrestaShop, or other e-commerce platforms.
But in some cases, your project is not built on one of the solutions mentioned just before, so you will need to implement your own payment system. (Or maybe you're just curious about how to do it) There are many payment solutions available on the market (PayPal, Apple Pay, Six Payment, ...) and some projects try to aggregate these payment gateways, like Omnipay.
To keep things simple, we will focus today only on a payment solution which is Stripe that gives you the ability to accept card payments on your project.
Implementing Stripe
First of all, please sign up on Stripe to access your secret and public keys and make them accessible to your application.
Please DO NOT store your customers' cards in your application/database, send card data DIRECTLY to Stripe, NEVER store card credential information!
Front
You will need to create a form for your users that will contain all the information you wish as well as the user's card information.
STRIPE_PUBLIC_KEY
represents your public key and STRIPE_CURRENCY_SYMBOL
represents the symbol in which you will charge the customer. (for the example, let's assume it's in euros, so the symbol is €
) Additionally, $price
contains the amount that will be charged, this should be understandable by your customers. (something like 12.99
)
The main element here is #card-element
which will be managed by Stripe.js to display nice inputs for your users to fill in the card information. For this to work, include the Stripe script in your head
tag.
Then, you will also need to write Javascript to mount the Stripe card behaviors on your form, create a script.js
file and load it at the end of your body
tag. Here is the beginning of your file : (only vanilla javascript, adapt it to your needs / your framework)
This is essentially about initialization, setting up Stripe card behaviors, listening for card information, and listening during form submission. The important part is the call to the pay()
function, it will start the payment process when you submit the form. Here is the pay function:
It manages the entire payment process but also calls different functions to achieve this; - changeLoadingState()
manages the behavior of the spinner
showError()
displays the error if necessaryhandleAction()
manages additional actions if necessary (which will be dictated by the client's card bank)orderComplete()
called when the payment is successful and complete
It toggles the spinner to inform the user of the pending process.
Informs the user with the given error message.
The payment is completed, adapt it to your needs.
In some cases, the user's card bank will require an additional step to validate the transaction, this function handles it (stripe.handleCardAction(clientSecret)
) it is represented by a Stripe pop-up window that asks the user to confirm the payment or to confirm their validation in 2 steps to validate the entire transaction. This function will reach the same endpoint (/pay.php
) of our server to resubmit the payment but this time it will provide the paymentIntentId
that was built during the first step of our payment process (when we called the pay()
function).
That's it, the only Javascript we'll need to make payments!
Return
We now need to handle requests addressed to our endpoints, but first, don't forget to install the Stripe PHP SDK.
(Currently, I am using the latest version v7.27.0 of the SDK)
And to better organize ourselves and keep it simple we will create a few files:
public/pay.php
endpoint to receive payment requestssrc/StripeHelper.php
Helper class to manage the different steps of our payment processsrc/BodyException.php
Exception thrown when the request $body is not formatted correctly
So here is the endpoint pay.php
:
We have broken down the payment process into smaller steps contained in our StripeHelper
class (for the needs of the tutorial, we do not care about SOLID principles) for simplicity, so that we can clearly read how the process is executed through our endpoint.
We can first see that our endpoint will only return JSON
We only allow POST requests
We set our Stripe secret key
We retrieve our cart, it can be anything, an array, an object, or whatever you want, it’s your own implementation. Stripe doesn’t care, Stripe only needs a total amount that will be linked to the
PaymentIntent
to know how much will be charged to the customerWe extract the total amount from our cart, again, this method will need to be replaced to work with your own implementation of your cart. Also note that the total amount MUST be in cents
We get our body information from the request (request made by our
script.js
that provides the payment intent data in its body)We build our
PaymentIntent
object from the body and the amountIf the payment intent is fully realized, it's a success! We can do business logic from here (record the payment, redirect the user to the order completion page, prepare the carrier, ...)
We return a response to the client
Also note that we have wrapped the entire process in a try/catch block, indeed, during the process, many issues can arise (the body of the request was not formatted correctly, the Stripe API did not respond to our calls, ...) so we will detect all these errors to prevent even more problems.
I will not cover the subject of the cart as it is not the purpose of this article, but you can find a mockup of a cart in the demo example.
See how the steps have been broken down into small pieces:
From there, we obtain a stdClass object that contains either the key paymentMethodId
(from the pay()
function in script.js
) or the key paymentIntentId
(from the handleAction()
function in script.js
). Of course, it could contain much more if you gave additional keys to the body of your request.
Here we try to build our PaymentIntent
object, in the first condition (if (!empty($body->paymentMethodId))
) it will create a brand new PaymentIntent
object (which would be initiated by the pay()
function from the front) to which a unique identifier will be assigned.
In the case where the client card bank does not need additional validation, that’s fine, the payment will be successful from the first request! But in some cases, the bank will ask the user to confirm the payment (2-step validation) or to secure it (3D secure). These validations are part of the SCA protocol.
Thus, in the second scenario, our process will indicate to the front that it needs additional actions from the client to complete the payment process. It will go through the handleAction()
function on our front and then resubmit the payment request but this time with the paymentIntentId
that was created in our first step (since that is the payment intent we wanted to confirm).
This second request will go into our second condition (else if (!empty($body->paymentIntentId))
) to be retrieved by Stripe and confirmed.
If for any reason this process fails, we will throw exceptions to prevent the payment flow from continuing.
Generate the response returned to the client. Note that we specify requiresAction
if the status of the payment intent indicates that additional action is needed to be completed. This is the key that triggers our pay()
function to call the handleAction()
function.
Returns simply if the payment intent is completed.
Solution
Well, I think this tutorial is simple enough to get started with the Stripe API and create your own custom implementation from it. I haven’t used any front-end or back-end framework, so it’s easier for everyone to understand plain PHP / JS.
Also remember that you can do a lot with Stripe; recurring payments, customer storage, sending invoices, ...
You can find the official Stripe example here](https://github.com/stripe-samples/accept-a-card-payment/tree/master/without-webhooks/server/php) from which I built this solution. (Please note that the original example does not work if you install it, you will need to modify it a bit to make it work)
Here is the entire functional code repository for the example project
Learn more about Stripe Payments and its documentation here
Feel free to give me your feedback or correct me if you see any mistakes
Thank you for reading!