Guide to Integrate Stripe Elements

James Castro
8 min readJul 28, 2020

--

This article is an in-depth guide to integrating subscriptions and one-off payments using Stripe’s API, specifically using Stripe Elements with the PHP SDK.

All the examples implement Strong Customer Authentication (SCA). The front-end code examples are written in JavaScript. The backend code examples are written in PHP but the code is simple and can easily be transposed into other languages.

The three transactions:
1. An ongoing subscription payment that bills every month.
2. A subscription/payment in instalments billing monthly for 12 months and then cancelled automatically.
3. A one-off payment using Stripe’s Invoice option.

As I will be using the same front-end implementation for all three transactions I will begin by breaking down the JavaScript that will be used. I will then show you how to implement the backend in PHP using the various strategies.

Front-end Implementation (JavaScript)

We will start by initialising Stripe Elements

Include Stripe.js:

<script src="https://js.stripe.com/v3/">

In JavaScript set the element styles

let styles = {
base: {
color: '#32325D',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#AAB7C4'
}
},
invalid: {
color: '#CC0000',
iconColor: '#FA755A'
}
};

Create the card instance and mount it to a div which should be in the place you want to display the element `<div id=”card-element”></div>`

let card = Stripe.elements.create('card', {
hidePostalCode: true,
style: styles,
});

card.mount('#card-element');

If you wish to collect additional data to send along with the token, set a listener on the card object which will fire when the element is completely filled. We call the “Stripe.createToken” method to generate the token for you to charge. The token can be saved on a hidden input element or JavaScript variable until the rest of the data has been collected.

card.on('change', event => {
if (event.complete) {
Stripe.createToken(card).then(result => {
if (result.error) {
// Display your errors (result.error.message)
} else {
// let stripeToken = result.token.id OR
// document.getElementById('INPUT_ID').value = result.token.id
}
});
} else if (event.error) {
// process error messages
}
});

Alternatively we can just call the “createToken” method once the payment form is submitted.

Stripe.createToken(card).then(result => {
if (result.error) {
// Display your errors (result.error.message)
} else {
// Submit the token (result.token.id)
}
});

To submit the token I am going to use the Axios library to send the request as the interceptors will allow for a very clean implementation of SCA.

let axios = Axios.create();

axios.post('/pay', { token: result.token.id }).then(response => {
alert('You have successfully sent me loads of money!!!');
}).catch(error => {
// manage your errors here!!!
});

Now the problem is when I get the “requires_action” response. I can manage it manually in the response above but wouldn’t it be great to intercept the response, manage the stripe SCA and then resend back to the server to finish the payment if required.

That’s where the Axios interceptors come into their own. I have extracted the code into a couple of smaller functions to make the code more readable.

The first part checks the response coming back from the server. If the response contains “requires_action”, I will send that to Stripe’s “confirmPayment” function along with the “paymentIntentSecret”. Then Stripe will show the modal in which to manage the additional authentication. If the response from the server is “succeeded”, it will just continue to the “then” part of the Axios request and ignore the “confirmCard” functionality.

axios.interceptors.response.use(response => {
if (response.data.hasOwnProperty('paymentIntentClientSecret') && response.data.paymentIntentClientSecret !== '') {
return new Promise((resolve, reject) => {
confirmPayment(
response.data.paymentIntentClientSecret,
() => {
resolve(axios.request(updateResponseConfig(response)));
},
error => {
reject({ response: { data: error }});
}
);
})
} else {
return response;
}
});

I will use a promise, making it easier to extract my code which will reject the promise if the “confirmPayment” fails and the code in the Axios “catch” block will be executed.

confirmPayment(paymentIntentClientSecret, success, error) {
stripe.confirmCardPayment(paymentIntentClientSecret).then(result => {
if (result.error) {
error({
errors: [
result.error.message
]
});
} else {
success();
}
});
}

If the “confirmCard” response is successful the “updateResponseConfig” function will inject the “subscriptionId” that’s returned into the request parameters and resolving the promise re-sends the request to the server to finalise the transaction. Once the server has finalised the transaction the response will be sent back to the original Axios request “then” block.

updateResponseConfig(response) {
let data = JSON.parse(response.config.data);
data['subscriptionId'] = response.data.subscriptionId;
response.config.data = JSON.stringify(data);

return response.config;
}

The beauty about this implementation is that your original Axios request simply returns whether the whole transaction is a success or a failure and you can display the results accordingly.

Back-end Implementation (PHP)

Before we start with the integration we will install Stripe’s SDK. Run the following command to install using Composer.

composer require stripe/stripe-php

* To install the SDK without composer you can find the instructions here

Subscription that bills every month until it is manually cancelled.

  1. Start by creating a customer object
$customer = \Stripe\Customer::create([
'email' => 'example@example.com',
'name' => 'James Castro',
'source' => 'tok_123456' // Generated from the JavaScript token function
], ['api_key' => 'pk_test_123456789']);

2. Create the subscription. There are two options for this (You will need the customer object above).

Option 1

$customer->subscriptions->create([
'items' => [
[
'price' => 'price_123456789' // Optionally you can use the 'price_data' instead and create on the fly.
]
]
'expand' => ['subscription.latest_invoice.payment_intent']
], ['api_key' => 'pk_test_123456789']);

Option 2

$subscription = \Stripe\Subscription::create([
'customer' => $customer,
'items' => [
[
'price' => 'price_123456789'
]
],
'expand' => ['subscription.latest_invoice.payment_intent']
], ['api_key' => 'pk_test_123456789']);

* If you are using an older version of the stripe api you will need to add `’payment_behaviour’ => ‘allow_incomplete’` to the options so that an error is not thrown if “requires_action” is returned by the “paymentIntent”.

* The `’expand’` option just allows the inclusion of related objects to be returned in the response. This gives you access to the full “latest_invoice” and “payment_intent” objects from the subscription.

The “paymentIntent” response will either return “succeeded” or a “requires_action”. If the response returns “requires_action” then we need to return the “paymentIntentClientSecret” and manage the additional security with the Axios interceptor explained above.

if ($subscription->latest_invoice->payment_intent->status === 'requires_action') {
return [
'subscriptionId' => $subscription->id,
'paymentIntentClientSecret' => $subscription->latest_invoice->payment_intent->client_secret
];
}

Subscription that runs for a fixed number of months using Stripe’s SubscriptionSchedule

This is a great option if you are spreading the payment of something over a number of months or specified iterations

We use the same JavaScript as before and manage the subscription details in a recurring price object in the Stripe Dashboard. (i.e. price, period, etc)

  1. Start by creating a customer object
$customer = \Stripe\Customer::create([
'email' => 'example@example.com',
'name' => 'James Castro',
'source' => 'tok_123456' // Generated from the JavaScript token function
], ['api_key' => 'pk_test_123456789']);

2. Create the “subscriptionSchedule”. This will automatically create the underlying subscription that is used for the transaction.

$subscriptionSchedule = \Stripe\SubscriptionSchedule::create([
'customer' => $customer,
'start_date' => 'now',
'end_behavior' => 'cancel',
'phases' => [
[
'plans' => [
['price' => 'price_123456789']
],
'iterations' => 12
]
],
'expand' => ['subscription.latest_invoice']
], ['api_key' => 'pk_test_123456789']);

* The `’start_date’` allows you to choose when the subscription begins, it could be in the past or future but as we want the subscription to start immediately we will use `’now’`

* The `’end_behaviour’` sets what happens once the schedule has finished. As we want to cancel the subscription once it’s completed we set it to `’cancel’’`. You could also let the underlying subscription continue at a different price.

* `’phases’’` are the way you control the underlying subscriptions, you can use this to create incredibly complex billing patterns if required. We will link to a pricing object which has been setup in the Stripe dashboard and then referenced here. Finally we want the subscription to be billed monthly for 12 months. The period (i.e., monthly, yearly, etc.) is set on the pricing object in the dashboard.

* `’expand’’` like before allows you to expand the objects so we have access to the “subscription”, “latest_invoice” and the “payment_intent”.

Unlike when creating the subscription it doesn’t automatically try to charge the invoice. It creates an invoice and will attempt to charge it in around one hour, which, according to Stripe’s documentation allows third party applications to update the invoice if required.

As we want to complete the checkout immediately while the customer is on the session, we have to manually finalise the invoice.

$subscriptionSchedule->subscription->latest_invoice->finalizeInvoice([
'expand' => ['payment_intent']
]);

All this does is move the invoice from “draft” to “open” and then creates the “PaymentIntent”. Calling confirm on the “PaymentIntent” will instruct Stripe to go ahead and attempt to charge the card.

$subscriptionSchedule->subscription->latest_invoice->payment_intent->confirm();

As with the normal subscription you will either get a “succeeded” or “requires_action” from the “PaymentIntent” which allows you to either complete the transaction or send it back to Axios to manage the additional authentication.

if ($subscription->latest_invoice->payment_intent->status === 'requires_action') {
return [
'subscriptionId' => $subscription->id,
'paymentIntentClientSecret' => $subscription->latest_invoice->payment_intent->client_secret
];
}

Fixed one-off payment

As with the previous implementations, the JavaScript stays the same.

  1. Start by creating a customer object
$customer = \Stripe\Customer::create([
'email' => 'example@example.com',
'name' => 'James Castro',
'source' => 'tok_123456' // Generated from the JavaScript token function
], ['api_key' => 'pk_test_123456789']);

Then we create an invoice item using a non-recurring price object which has been created in the Stripe Dashboard.

InvoiceItem::create([
'customer' => $customer,
'price' => 'price_123456789',
], ['api_key' => 'pk_test_123456789']);

Next we create the Invoice. Creating an invoice will look for orphaned invoice items for that customer in your Stripe account and add them to the object automatically, hence picking up the invoice item you created in the previous step. The invoice will be created as a “draft” invoice.

$invoice = StripeInvoice::create([
'customer' => $customer
], ['api_key' => 'pk_test_123456789']);

The process to pay the invoice follows the same logic as when paying the invoice using the “SubscriptionSchedule”. Finalise the invoice to convert it to “open” which in turn creates the “paymentIntent”.

$invoice->finalizeInvoice([
'expand' => ['payment_intent']
]);

Finally, confirm the “PaymentIntent” to charge the card and as before check the result.

$invoice->stripeInvoice->payment_intent->confirm();

if ($invoice->payment_intent->status === 'requires_action') {
return [
'subscriptionId' => $subscription->id,
'paymentIntentClientSecret' => $subscription->latest_invoice->payment_intent->client_secret
];
}

Conclusion

As these implementations are quite similar, it’s relatively easy to interchange them depending on the price object or other criteria you might have to satisfy your use case. One such case is if you have multiple options of paying for a product or service and there is one option to pay up front and another to spread the payment over several instalments. Depending on the selection you can direct the user through the same front-end flow and then using the various implementations on the back end to process the payment.

This will give you a great starting point from which to make the relevant adjustments needed to build yourself the exact implementation to fulfil your own requirements. The settings can be tweaked by using the Stripe API Documentation

--

--