8 Tips For Building A Recurring Billing Workflow On Stripe

Are you crafting a SaaS (Software As A Service) product? So you’ll need to setup a payment gateway and handle recurrent revenues with tools such as Stripe, Recurly or Braintree.

Samuel Berthe
5 min readJan 19, 2018

🙊 Short Promo Intro 🙊

We are bienavous.io, the easiest way to make anyone (yes, even outside your organization) update and install your Gmail signature banner in 1 click.

All you need is a link.

You can create your very first one for free here with 10 free installs credits.

For your MVP (Minimum Viable Product), only accepting credit cards payments would be suitable for 90% of your customers.

Stripe also supports SEPA Transfer, Alipay and even Bitcoins!

But workflows are more complex so we won’t address them in this article.

Alright, ready?

Let’s get started!

Get some 🍕 and some ☕… Here are my 8 tips.

⭐ 1. Event/Webhook driven — Truth is in Stripe.

Every action on the Stripe side triggers webhooks to your API.

Lot of Stripe actions are asynchronous.

Then, I highly recommend to only listen events from this channel instead of creating cron jobs that pull Stripe state regularly.

Events I used to listen to support a basic lifecycle:

  • customer.subscription.created + “customer.subscription.updated
    // sent when customer subscription change
    // sent with Plan metadata
  • invoice.upcoming
    // sent days before end of subscription
  • invoice.payment_succeeded” + “charge.succeeded
    // time to send a PDF invoice
  • invoice.payment_failed” + “charge.failed
    // mostly for Slack push and to alert the customer
    // in case of failed subscription renewal, Stripe will send a
    // “customer.subscription.updated” event with status field set to “unpaid”
  • charge.dispute.created” + “charge.dispute.closed
    // Slack alert

If you don’t reply to HTTP webhook with a 2xx status code, Stripe will retry every hour up to 3 days! \o/

Don’t forget to check the secret token of each webhook request, with the Stripe-Signature header!

You can find your very own here: https://dashboard.stripe.com/account/webhooks

More details about webhooks on Stripe documentation!

⭐ 2. Use Ngrok or Ultrahook for receiving webhook on localhost.

Webhook are sent from Stripe servers to your API.

For local development, you will need to receive requests on localhost.

Because it’s impossible for Stripe to contact a local address, you will need to create a tunnel between a publicly available server to your local machine.

Ngrok and Ultrahook do that for you.

I‘d rather use Ultrahook because it offers persistant DNS endpoints, for free.

$ docker run -d -e ULTRAHOOK_TARGET_PORT=http://api:8080 \
-e ULTRAHOOK_DOMAIN=dev-samber \
-e ULTRAHOOK_KEY=very-random-secret-key \
pantinor/ultrahook_alpine

⚠ Ultrahook always reply 200 OK to requests, without waiting for your API response.

Regarding multiple environments and testing mode:

Unfortunately, Stripe does not offer unlimited test environments 😡

Then, after receiving a new webhook, you will need to check if it was meant for your local instance or a staging environment (or any dev of your team).

For every different environment of production, I used to check if the customer ID, when provided in the payload ($event->data->object->customer), matches with a user in my database.

Following events have the customer ID field:

  • customer
  • invoice
  • subscription
  • invoiceitem
  • card
  • charge
  • order

Following events have not the customer ID field:

  • coupon
  • payout
  • plan
  • product
  • source,
  • recipient

⭐ 3. Create a “Free” plan.

This will force you to create a Customer and a Subscription for each new user.

Then, you will be able to manage user subscription, upgrade plan, add discount… right from the Stripe dashboard.

Because the user is already visible in Stripe, it will be much easier to manage subscriptions, based on Stripe events.

⭐ 4. Set plan limits into metadata.

Stripe allows many ressources (like Plans) to bring a context, set in Stripe Dashboard.

When you receive an event related to this resource (such as Plan upgrade), the payload provides you with these metadata.

In the future, if somebody of your sales team needs a custom Plan, for a group of customers, you won’t need to set allowed features with dirty conditions in your code 🤮, but only from Stripe Dashboard.

⭐ 5. You will love fake cards.

Stripe provides you with many fake cards to test your app in testing mode.

Cards I often use:

  • Valid card: 4242424242424242
  • Valid card and 3D secure required: 4000000000003063
  • Wrong CVC: 4000000000000101
  • Valid card but failed to charge: 4000000000000341
  • Charged declined: 4000000000000002
  • Expired card: 4000000000000069

For most of those cards, a random CVC is ok.

Enjoy the full list here: https://stripe.com/docs/testing

⭐ 6. Testing failing subscription renewal

Stripe never made it easy to test!

  • Create a plan with a trial period.
  • Add the 4000000000000069 card to a fake customer.
  • Create a subscription with the plan you just created.
  • Manually end the trial period 30 seconds in the future with the following script:
$ export CUSTOMER_ID=cus_1234
$ export SUBSCRIPTION_ID=sub_1234
$ export STRIPE_TEST_TOKEN=sk_test_qwertyuiop
$ curl -u ${STRIPE_TEST_TOKEN}: \
-d trial_end=$(date -v +30S +%s) \
https://api.stripe.com/v1/customers/${CUSTOMER_ID}/subscriptions/${SUBSCRIPTION_ID}

And you will receive webhooks reporting the failed payment 😋

⭐ 7. Upgrade and downgrade plans in the same subscription.

Stripe is able to prorate subscription costs when you switch plan in a running subscription, before the end of a billing cycle.

⚠ This is only available when you keep the same subscription and update items inside.

⭐ 8. Frontend library is Stripe.js.

Stripe offers a great frontend library with additional security checks and simple billing forms.

You have to inject the following lines at the end of your index.html.

<html>
<head>
...
</head>
<body>
...
<script type="text/javascript" src="https://js.stripe.com/v3/">
</script>
<script type="text/javascript">
var stripe = Stripe('pk_test_qwertyuiop');
</script>
</body>
</html>

Library features:

  • stripe.elements(): UI components
  • stripe.createToken(): to convert card informations into a token
  • I basically send this token to my backend, which setups the premium user account and change subscription plan.

React and Angular have libraries that can abstract this layer.

Full documentation here: https://stripe.com/docs/stripe-js/reference

Voilà!

I really hope these 8 tips can help you build a recurring billing system on your product. On my end, I wish I’d read this article before exploring Stripe 😅

3 little things before you go test bienavous.io

(1) Premium access to bienavous.io for only 49€ a year at the moment! Offer ending soon.

(2) “Bien à vous” means “Kind Regards” in French 🇫🇷

(3) If you enjoyed reading this, or if you do like our little product, do not hesitate to clap clap clap 💛

And why not even clap clap clap clap clap clap clap clap clap… ?

--

--