Guides & tutorials

EU VAT compliant SaaS subscription flows with Stripe

Table of contents

Stripe is a great tool for processing payments although, it still lacks mechanisms to collect taxes based on the buyer’s country. In this tutorial, we will cover how to build a whole payment flow you can embed in your application, to enrol users into subscription and comply with your EU VAT requirements.

Previous requirements

To build this use case you will need:

  • A Stripe account.
  • An Arengu account.

Overview of the flow

A quick overview of the flow will be:

  1. The user selects a pricing plan.
  2. We ask the user for billing information which includes the VAT number and the country.
  3. If the user is based in Europe, we will check if they have a valid VAT number and if we should apply a reverse charge or collect taxes.
  4. We will ask for payment details and enrol the user into a paid plan.

1. Configure your tax rates in Stripe

When you create a customer in Stripe and you define the tax status (none, reverse or exempt), Stripe will automatically charge the VAT in your subscriptions based on the users’ country. For this, you will need to configure you tax rates for the different countries in your Stripe dashboard under Products > Tax rates:

2.Building the payment or checkout form

Build a form with three steps. In the the first one, we will let the users to choose a plan that fits their needs:

  • Add a Card field and change its ID to plan.
  • Add your desired plan options (eg. startup plan and growth plan) and modify its internal values by adding the proper Stripe pricing IDs. You will find your Stripe price IDs under the products menu in your Stripe dashboard.

In the second step, we will ask for billing information:

  • Add an Email field and change its ID to email.
  • Add a Dropdown field with all available countries, their 2-digit country codes (eg. Spain;ES) and change its ID to countryCode.
  • Add a Text field for the company name and change its ID to name.
  • Add another Text field for the VAT number or tax ID, and change its ID to vatNumber.
  • You can add more fields like: postal code, street name, and city.

In the third step, we will add a Payment field with the following settings:

  • Provider: choose Stripe and add your Publishable and Secret keys.
  • Payment type: choose “Subscription”.
  • Payment action: choose “Create subscription”.
  • Price ID: add {{fields.plan}} to reference the first step field with the selected plan.
  • Customer action: choose “Update customer”.
  • Customer ID: add {{state.customerId}} to reference the ID of the customer we will create in our flow.

Now, let’s add the flow logic to the second step to handle the Stripe customer creation and the taxes:

3. Create the customer in Stripe and validate the VAT against VIES

This flow will check the VAT number against the VIES public API if our client is based in Europe and then, create the customer in Stripe with the proper information and tax status:

3.1 Create a list with all EU countries

Add a Create JSON object action and include an object with a list of all EU country codes. We will use this to compare if the user has chosen an EU country or not.

3.2 Check if the company is non-EU

Add an If/then condition with the following settings:

  • First condition: choose “includes” and add {{countryCodes.result.list}} to the first input and {{input.body.countryCode}} to the second one.
  • Second condition: we will exclude our own country because we need to collect taxes. Select AND, choose “is different to” and add {{input.body.countryCode}} to the first input and add your country code to the second one.

3.2 Validate VAT against VIES 

Let’s build the true branch. Add an HTTP request action to validate the VAT against the VIES XML WebService:

  • ID: change it to checkVies
  • URL: add “http://ec.europa.eu/taxation_customs/vies/services/checkVatService”
  • Method: choose “POST”
  • Content type: choose “application/xml”
  • Body:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:urn="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
  <soapenv:Header/>
  <soapenv:Body>
    <urn:checkVat>
      <urn:countryCode>{{input.body.countryCode}}</urn:countryCode>
      <urn:vatNumber>{{input.body.vatNumber}}</urn:vatNumber>
    </urn:checkVat>
  </soapenv:Body>
</soapenv:Envelope>

This WebService will return a response like this one:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <checkVatResponse xmlns="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
      <countryCode>ES</countryCode>
      <vatNumber>B94172202</vatNumber>
      <requestDate>2021-04-29+02:00</requestDate>
      <valid>true</valid>
      <name>---</name>
      <address>---</address>
    </checkVatResponse>
  </soap:Body>
</soap:Envelope>

So you can build business logic based on the validity of the VAT number (eg. apply tax rates or reverse charge).

3.3 Map VIES response with Stripe tax status

We need to map the VIES response to a valid Stripe tax status, in this case: none or reverse. Add an Input value mapping with the following settings:

  • ID: change it to mapTaxStatusA
  • Input value: add {{checkVies.body['soap:Envelope']['soap:Body'][0].checkVatResponse[0].valid[0]}} to reference the valid property of the VIES API response.
  • Mapping table: first row, add true to the first input and reverse to the second one; second row, add false to the first input and none to the second one
  • Default output: we will add none in case the validation fails.

3.4 Create customer in Stripe

Now that we have all the needed information, let’s add the action to create a customer in Stripe:

  • ID: change it to createCustomerA
  • Secret key: add your Secret key.
  • Email: add {{input.body.email}}
  • Name: add {{input.body.name}}
  • Tax status: enable “Allow references” and add {{mapViesTaxStatus.result}}
  • Tax ID: enable save Tax, choose “European - eu_vat” in the first input and add {{input.body.countryCode}}{{input.body.vatNumber}} to the second one.

3.5 Store customer ID state variable

As the previous action creates a customer in Stripe and returns the customer ID, we will need to store it as a state variable so we can use it in our payment field settings. Add the “Store state variable” action with the following settings:

  • Data fields: add “customerId” to the first input and {{createCustomerA.body.id}} to the second one.

Then, close this branch with a “Go to the next form step” flow action.

For brevity, we will omit the false branch of this flow as it’s the same as previous actions but omitting the HTTP request action. You will only need to map your tax status based on the users’ country.

Now, you have a whole payment flow you can embed in your application to enrol users into subscription and comply with your VAT requirements.


You might like to read

Getting started with Arengu

Arengu allows you to build all your user flows connected to your current stack, and avoids coding all the UI, complex integrations, validations or logic from scratch. Try it for free and start building faster and scaling your application needs as they grow.