Making a recurring payment with Stripe, manage subscriptions and more

Stripe is a powerful payment service designed for developers, but it can be difficult to use for newcomers even if the documentation is really good. For a simple integration (credit card payment) see our previous post Making a simple payment with Stripe (it’s with a PHP backend but you’ll get the idea).

Here we will go over the following subjects:

  • Subscriptions/recurring payments
  • Lifecycle of subscriptions
  • Coupons
  • SEPA payments

Prerequisites

Prerequisites are the same as for the previous post. You need a configured stripe account and once it’s done you will need your api keys.

Subscriptions

Subscription workflow

First thing to know: a subscription is a recurring payment handled by Stripe. This means that Stripe needs to know about your customer and a valid payment method. To detail the process, here is what you should do:

  • Create a Stripe Customer
  • Create a Stripe Source (payment method)
  • Create a Stripe Subscription

Customer creation

If for some reason you already created a Stripe Customer and/or Source it is a best practice to reuse it by fetching it using the cus_id/src_id provided by Stripe at the creation time.

A bit of node.js code for a getOrCreateStripeCustomer function using the stripe npm package:

import stripePackage from 'stripe';
   const stripe = stripePackage(process.env.STRIPE_SECRET_KEY);
    const getOrCreateStripeCustomer = async (user) => {
let customer;
const customerId = user.stripeCustomerId;
if (customerId) {
customer = await stripe.customers.retrieve(customerId);
} else {
customer = await stripe.customers.create({
email: user.email,
});
await user.update({ stripeCustomerId: customer.id });
}
    return customer;
};

In this piece of code, user represent the user in my application and it has a stripeCustomerId attribute. It is also worth noting that my STRIPE_SECRET_KEY is pushed into the environment but you can manage this part however you like. It is important to mention that the key is secret and should not appear in any commit or public file.

Source creation

Now that we have a method to create a customer we must attach a source to this customer. The source creation usually happens in the frontend with Stripe. It’s a best practice to keep it that way so that no sensible information goes through your servers, it reduces the risk of having your clients banking data stolen and let Stripe handle all the risky data. In our case we were in a ReactJS environment so we used the react-stripe-elements package. We also included the iban package to check that provided IBAN numbers for SEPA payments are valid.

The payment UI looks like this:

import IBAN from 'iban';
import { flowRight } from 'lodash/fp';
import PropTypes from 'prop-types';
import React from 'react';
import { reduxForm, formValues } from 'redux-form';
import { CardElement, injectStripe } from 'react-stripe-elements';
const PaymentForm = ({
stripe, paymentMedium, iban, owner,
}) => {
const stripeSourceCreation = async (event) => {
event.preventDefault();
let source;
if ('card' === paymentMedium) {
// Stripe Elements magic gets the only card elements available and create the source
source = await stripe.createSource();
} else {
source = await stripe.createSource({
type: 'sepa_debit',
sepa_debit: { iban },
currency: 'eur',
owner,
});
}
if (source) {
const postBody = {
stripeSourceId: source.source.id,
paymentMedium,
};
// Here you POST the above body to link source to customer in the backend (see below)
}
};
const checkIBAN = (event) => {
const { target } = event;
target.classList.remove('valid');
target.classList.remove('invalid');
if (IBAN.isValid(iban)) {
target.classList.add('valid');
} else {
target.classList.add('invalid');
}
};
return (
<form onSubmit={stripeSourceCreation}>
<p>Payment by { 'card' === paymentMedium ? 'card' : 'SEPA' }</p>
{ 'card' === paymentMedium ? (
<CardElement />
) : (
<Fragment>
<span>IBAN Number:</span>
<input
placeholder="ex: FR68539007547034"
onBlur={checkIBAN}
name="iban"
/>
<span>Account owner:</span>
<input placeholder="ex: Marie Dupont" name="owner" />
<p>
Here you should include the SEPA Mandate provided by Stripe
</p>
</Fragment>
)}
</form>
);
};
PaymentForm.propTypes = {
stripe: PropTypes.shape().isRequired,
paymentMedium: PropTypes.string.isRequired,
iban: PropTypes.string,
owner: PropTypes.string,
};
export default flowRight([
reduxForm({
form: 'create-stripe-source-form',
type: 'simulation-form',
}),
formValues({
iban: 'iban',
}),
injectStripe,
])(PaymentForm);

Once created in the frontend you should get the sourceId returned by stripe and link it to your client (this you probably should do in your backend). It’s a simple Stripe API call:

stripe.customers.createSource(stripeCustomerId, { source: stripeSourceId });

Finally you can create the subscription. The first step for this happens in the Stripe dashboard where you must create your “plans”. Go to the Billing > Products page and create you product and then inside the product, create your pricing plan. When creating the plan you set an ID which you will use in your subscription code:

const subscription = await stripe.subscriptions.create({
customer: stripeCustomerId,
items: [{ plan: stripeSubscriptionPlanId }],
});
// then update you user to save the fact that the plan is activated!

And now your subscription is in place, it will keep running until further notice. Every month (or whatever billing interval you choose) the customer will be charged with the subscription amount on the payment source you registered. Unless there is a payment failure… This is where webhooks arrive on scene.

Webhooks and subscriptions lifecycle

Webhooks allow Stripe to proactively warn you about events occurring in Stripe operations. It can be a payment failure, a revoked source, a deleted customer… All of these events you can be notified about (there is more than seventy different events, we will only talk about one as an example but they all work the same way).

First you must go in your Stripe dashboard, in the “Developers” section and “Webhooks” subsection. Click on the “+ Add endpoint” button and set your endpoint URL. You can decide to send all events to the endpoint or just a subset. Finally you must save the “Signing secret” to check for legitimate requests on your endpoint. Once this is done you have to create your endpoint. I let you choose your preferred way to handle it, just be careful to match the URL you put in the Stripe interface and to listen for the POST method.

In your endpoint you will need to do the following actions:

  • Identify the legitimacy of the request
  • Extract Stripe data
  • Do things in reaction to the event

The first two points are easy as pie. Given you are using express, here is the sample to extract data and validate request:

import stripePackage from 'stripe';
const { body, header } = req; // req is your express request object
const stripe = stripePackage(process.env.STRIPE_SECRET_KEY);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
const stripeEvent = stripe.webhooks.constructEvent(body, headers['stripe-signature'], webhookSecret);

And here you are, with the data sent by stripe webhooks!

Last step consists in doing things depending on the event type and/or content and this is totally up to you and the specificities of your processes. I just give you the link to the documentation describing the events.

While coding your webhooks you should keep in mind that a best pratice is to send a status 200 response to Stripe ensuring them you got the message before doing any heavy lifting.

Coupons management

Once your subscriptions are in place, you may want to add some coupon system to handle marketing operations, special events, or angry users.

First thing to know, Stripe coupons can be used for subscriptions only. No coupons for one time payments. If you want to do that, you are doomed to make the system in-house. Stripe sends you to a recipe to do it in rails but anyway you have to engineer that piece. It means manage a full CRUD for coupons and applying them on payment before interacting with Stripe.

If you are going for a standard subscription only coupons, you can manage those directly in the Stripe dashboard in the Billing > Coupons menu. Same as for subscriptions, you will set an ID for the coupon (which is the one users should enter when paying to get reductions). This ID entered by users you only have to push it to Stripe alongside the subscriptions like this:

await stripe.subscriptions.create({
customer: stripeCustomerId,
items: [{ plan: stripeSubscriptionPlanId }],
coupon: couponID,
});

This way the coupon will be applied on the subscription. You can also update an existing subscription to apply a coupon on it.

Last detail, if you need to create coupons on the fly, it’s possible using the Coupon API. An example is available in the Stripe doc about discounts.

Finally, for Europeans it’s common to use SEPA payments. Let’s dig into that!

Easy setup for SEPA… or not

SEPA (Single Euro Payments Area) is a mean to pay in some places in Europe. It has some drawbacks (which you will learn below) and also a processing workflow which is a bit more complicated than cards. The main reason being that SEPA needs to be accepted on the client side, and it can take up to 14 days. This asynchronous process adds some complexity to the whole thing.

Activate SEPA

To setup a SEPA payment method, all you have to do is prepare to lot of administrative process and a tiny little bit of code. SEPA payment method (named sepa_debit in the Stripe API) is not activated by default. You must go to Stripe website into the “payments → settings” panel and ask for “SEPA Direct Debit” activation. And… wait. SEPA debit can officially be activated only after 30 to 60 days of payments activity on cards. You can try and contact the Stripe team for an early activation and they will warn you about the usage of SEPA which has some drawbacks:

  • Users can cancel the payment through SEPA within 8 weeks after the withdrawal without providing any reason. This is considered pure loss by Stripe and you have no way to prevent or defend against this.
  • Users can dispute the payment 11 months after the 8 first weeks (this means your cash is secured only 13 months after payment). These disputes are decided at the bank levels, Stripe may ask more information from you. If the bank decides to cancel the payment, your stripe balance will be reduced accordingly and another 7.5 € will be withdrawed (the dispute process fee).
  • Any failed payment through SEPA will lead to a 7.5 € fee you will have to pay (for example if your client does not have enough cash on their account)

If after all that you still want to go on, be warned that the Stripe test API will stay closed to all your SEPA related calls until you are activated.

As for the setup, if you went this far, you already have the code to create the source in the UI code in the “Source Creation” section. And the subscription doesn’t really care for the source type, it just charge any source you gave. The real work is in the webhooks since SEPA is prone to more payment failures, but this too is already in your hand. I have nothing more to say, to your keyboards!

Conclusion

So that’s pretty much all you need to know to setup your subscriptions management with Stripe.

If you have other questions related to Stripe, don’t hesitate to comment down below, and we’ll try our best to answer quickly and if you are interested in other articles about Stripe be sure to tell us!