With the ever-increasing number of payment frauds it was only a matter of time before strict security measures would be imposed on online transactions as they had already been imposed on physical card payments via the “Chip and PIN”. For online payments that method of verification is “Strong Customer Authentication” which ensures that electronic payments are performed with multi-factor authentication, to increase the security of electronic payments. The factors involved depend on the policy of the customer’s bank. Inevitably the European Commission updated the Payment Services Directive which has made strong customer authentication a legal requirement for electronic payments and credit cards in the European Economic Area starting from September 14, 2019.

In order to become compliant with these updated regulations Stripe has also implemented strong customer authentication via 3D Secure 2. In practice, this means that a new payment step has been added where the customers must confirm their payment using an authentication method which varies depending on the customer’s bank.


The very first requirement for implementing SCA is that the Stripe API version be ‘2019-03-14’ or newer. The major update in this version is the creation of subscriptions with an ‘incomplete’ status. Subscriptions with an ‘incomplete’ status will change their status to ‘incomplete_expired’ if they remain in ‘incomplete’ state for 23 hours.

Stripe allows for SCA to be implemented in every payment method it provides. In this Article we will talk about the following:

1. Creating charges.

2. Creating Subscriptions on your platform.

3. Creating Subscriptions on a Connected account.

Note: During this article, “on-session” means that the user has the website opened in a browser. Similarly, “off-session” means the opposite.

Creating Charges

Traditionally, in order to create a one-time charge from a card on your platform, you would create a card by mounting the stripe form elements and then tokenize that card. That token would then be used as a source for the charge.

Step 1 – client side

var stripe = Stripe("platform's publishable key");
var cardElements = stripe.elements();
var card = elements.create('card');

var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {

  stripe.createToken(card).then(function(result) {
    if (result.error) {
      // Inform the customer that there was an error.
    } else {
      form.append($('<input type="hidden" name="stripeToken" />').val(token.id))
      // Submit form to send the token to your server, to be used while creating Charge.

Step 2 – server side:

token = params[:stripeToken]

charge = Stripe::Charge.create({
    amount: 999,
    currency: 'usd',
    source: token,

Step 3 – webhook (optional):

Upon successful creation of the charge, you can wait for the webhook for “invoice.payment_succeeded” and complete you functionality then.

Alternatively when the charge is created on Stripe (Step 2), you can check whether the charge has succeeded by checking the charge object returned in the response, right then.

Migration to SCA

Migrating to SCA requires the introduction of “Payment Intents”. Payment intents, as their name suggests, are created when a payment is intended to be made. The PaymentIntent object contains all relevant information related to the payment, including charges and invoice. It also contains a “client_secret” which is essential for processing payments through SCA.

So, the workflow for implementing charges becomes the following:

Step 1 – server side: Create Payment Intent

The first step is to create a PaymentIntent on Stripe. Later we will process this payment intent only. The charges and invoices will be handled automatically with this payment intent.

intent = Stripe::PaymentIntent.create({
    amount: 199,
    currency: 'usd',
    description: 'Example charge'

The “client_secret” can be extracted through the ‘intent’ object

Step 2 – client side: Handle Card Payment

The entire process now reduces to just one call provided by Stripe js, i.e. handleCardPayment.

var stripe = Stripe("platform's publishable key");
var cardElements = stripe.elements();
var card = elements.create('card');

stripe.handleCardPayment(clientSecret, card).then(function(result) {
  if (result.error)
    // Inform the customer that there was an error.
    // Inform the customer that payment was successful.

The authentication process is automatically handled by the “handleCardPayment” method by analysing the card information and checking whether that card requires authentication on payments.

Note: This approach needs the user to remain on-session.

Stripe also send webhooks “payment_intent.payment_failed” or  “payment_intent.succeeded” depending on the outcome.

Creating Subscriptions on your Platform

For subscriptions we will be discussing particularly where there are initial on-session payments and then the recurring payments are made off-session.

The subscription is created for a customer on the default payment source of that customer. If it is a new customer, you will have to, firstly, create a customer, and secondly add a default source (commonly a card) before starting the subscription process. Once that’s done, you can perform the following steps.

Initial Payment (on-session):

stripe_subscription = Stripe::Subscription.create({
  customer: stripe_id_customer,
  items: [
      plan: stripe_plan_id,
  expand: ['latest_invoice.payment_intent']

NOTE: It is useful to have “expand” option so that the subscription object returned already has latest invoice and payment intent objects, instead of just their ids. Payment intent object is needed to get its status. Otherwise we would have to make 2 separate calls to Stripes to fetch the invoice and then to fetch the payment intent.

If the initial payment requires authentication, the subscription will be created with status “incomplete”, the latest invoice will have status “open” and the payment intent will have status “requires_action”. i.e.

> "incomplete"
> "open"
> "requires_source"

This will also trigger webhooks “invoice.payment_failed” and “invoice.payment_action_required”.

In this scenario we use the same Stripe js method: “handleCardPayment”, to handle this payment. The “client_secret” needed for that method is extracted from `stripe_subscription.latest_invoice.payment_intent.client_secret`.

var stripe = Stripe("platform's publishable key");

stripe.handleCardPayment(client_secret).then(function(result) {
  if (result.error)
    // Inform the customer that there was an error.
    // Inform the customer that payment was successful.

As before this method will take care of the authentication automatically and relevant webhooks will be triggered. In case of success, they are: “invoice.payment_succeeded” and “payment_intent.succeeded”

Recurring Payment (off-session):

Recurring Payments are fairly straight-forward to implement. The settings for configuring this process can be changed by going to your Stripe dashboard > Settings > Billing,  section “Manage payments that require 3D Secure”.

When the billing cycle ends, your platform initiates a payment for the next billing cycle. If that payment requires authentication, your server will receive a “invoice.payment_action_required” webhook. The status of the subscription can be updated at that point until the payment is authenticated. Furthermore, Stripe can be configured to send Email reminders to the customer which allows the customer to authenticate the payment while remaining off-session.

You may also, authenticate the recurring payment on-session by the following steps:

  1. Fetch the subscription from Stripe.
  2. Fetch the latest invoice of that subscription from Stripe.
  3. Fetch the payment intent of that invoice from Stripe.
  4. Use the client_secret from that payment intent and use it in “handleCardPayment” method, just like the initial payment.

Subscriptions on Connected Accounts

To implement SCA on connected accounts you must save the “publishable key” of the connected account in your database, during the onboarding process. After that there is no way to get that “publishable key”. Once you have that, the entire process is the same as for subscriptions on your platform with only one difference.

When using the “handleCardPayment” method you must first initialize the stripe object like:

var stripe = Stripe(“platform’s publishable key”);

In case of connected account you just need to use the publishable key of the connected account, instead of your own.

var stripe = Stripe(“connected account’s publishable key”);

Happy coding 🙂

Article by Asad Imtiaz – Software Engineer (Ruby on Rails)