Using Stripe for subscription services in a Flutter app

David Djordjevic
Prime Holding JSC
Published in
7 min readOct 18, 2022

Payment integration has always been a hot topic in Flutter. Monetising the app or allowing the purchase of services within the app are very common challenges that may take some time to implement.

There are different ways to monetise our app. Many packages support different types of payment services, in-app purchases, as well as ad monetization. One such service is Stripe which serves as a payment processing gateway with built-in protection against fraudulent transactions.

The goal of this article is to familiarise the reader with the flutter_stripe package and discuss one of the many ways we can use it to speed up implementation. For this article, we’ll assume that our app uses a backend service with an implemented Stripe functionality, so we can focus on the mobile side of things.

Before we can actually use any of Stripe’s features, we need to manually configure some parts of our application. For the iOS project, we just need to define a global platform by changing a line of code in the ios/Podfileto platform :ios, ‘12.0’ .

The changes that need to be made to the Android project can be found on the packages main page in the install section. However, there are a few things to note here. Make sure to rebuild the project after updating the Kotlin and Gradle versions to the correct ones:

When addressing the point mentioning Theme.AppCompat, remember to create two new directories in the android/app/src/main/res directory, one called values and the other values-night. Both directories should contain a styles.xml file with the following content:

Content of the styles.xml file for both light and dark mode

Before we actually start writing any code, we need to talk about how the package and Stripe actually work.

For a payment to be made, a payment method should first be determined. Each payment method can be added through the widgets provided by the flutter_stripe package or through the API supplied by Stripe. When we create a payment method, we link it with a stripe user id which uniquely identifies each user.

If we’re creating a subscription service, we’d want to automatically charge the added card at the beginning of each new subscription period. In this way, the user has to add their card once and it will be reused for future payments. This can be achieved by the use of SetupIntents.

Once you configure a SetupIntent, the user may be asked to authenticate or Stripe may need to validate card details after which everything is set up. The next time a payment is charged, there will be no need for the user to authenticate or confirm any kind of validations (if 3DS is available, for example).

Now that we have this out of the way, let’s start with some code. The first thing we have to do is assign the Stripe.publishableKey in a place before we actually launch our app. This can be done in the main function or some app configuration function that is called before the runApp function.

The value to be assigned is a string generated by Stripe. It can be found on the Stripe dashboard under the section called Publishable Key. This key is used on the client side to tokenize payment information communicated to Stripe services.

The next step will be assigning a card to our user. Since we plan on to reuse it for our subscription, we can easily link our card to the user by requesting a SetupIntent client secret from the BE and confirming it in our app by calling a special method called, Stripe.instance.confirmSetupIntent. If everything is okay, the returned SetupIntent should have the status field populated with a success value.

Note: Even though there are Stripe APIs for creating new payment methods, it is not recommended to create them this way due to security concerns.

Example of the Stripe card creation logic

In the line of code where we request SetupIntent data, a new SetupIntent is created on the backend for the provided user and it contains a client secret which is used for confirmation and linking with them. After we successfully confirm the SetupIntent, our payment method will be linked to the Stripe user ID that was provided when the SetupIntent was created in the backend. Now we should have a working card. But where does the card information come from?

The security of card credentials is a very important aspect of any system that deals with payments. To ensure that users’ payment credentials are not processed or stolen with or without their knowledge, we must comply with PCI standards that ensure user safety. When working with widgets that are part of the flutter_stripe package, we are sure that they are PCI compliant out of the box and no additional setup is needed. This allows us to simply add one of these as part of our workflow for adding new payment methods and we’re good to go.

Screenshots of two Card Field widgets in a modal sheet and one Card From Field in a modal sheet.
Different types of payment widgets in a modal sheet

The widgets work in such a way that they allow users to enter their card number and other details without compromising their security. Once the details are entered into the appropriate fields, they are stored internally inside the Stripe.instance singleton. Calling any of the Stripe related functions is done against those details (where applicable). So calling the confirmSetupIntent function with valid SetupIntent details and proper card details will result in success, while missing card details will result in an error.

Although credit and debit cards are protected by many security measures (either through the bank or different verification systems such as CVC and AVS), there can still be a risk of fraud. This is where 3D Secure (aka 3DS) technology comes in, which in turn provides an additional layer of security when making purchases. Almost all of the most popular credit card brands support 3DS. In some regions, such as Europe, 3DS is required and enforced by the SCA (Strong Customer Authentication) regulation.

If you’ve ever bought something online and after confirming your card details a screen appeared asking you to input a password or a one-time code, you’ve probably encountered 3DS in action. It is worth mentioning that the verification methods using 3DS are dependant on the version of 3DS itself. 3DS version 2 is a slightly newer version of the 3DS protocol with the aim to fix shortcomings that are present in the first version.

Screenshot of a 3DS example in a testing environment allowing the user to complete the validation successfully or fail it
3DS example in a testing environment

When it comes to setting up 3DS in our project, we don’t have to do anything! The flutter_stripe package supports it out of the box. Based on the card details the user provides it may trigger 3DS where they have to validate the card either by entering a one-time code or by using some other way of validation. If they fail to authenticate, the whole process fails and the user has to retry again. If the 3DS validation succeeds, the workflow continues as predicted.

Now that we have an idea of how things work and how we can add payment methods to a user, we can cover retrieving and removing cards from their Stripe user ID. For this we will use the publicly available Stripe API. So let’s take a look and define some of the APIs that we’ll use in a moment.

PaymentInfoApi implementation example using Retrofit

The PaymentMethodResponse is modelled by the PaymentMethod object mentioned in the API. The same could be said about the StripePaginatedList model. In the PaymentMethodResponse we can find a card field that contains useful properties such as the last four digits of the card, expiry month and year. Such information can be used for presentation and custom logic without compromising its security. Now, we can combine the PaymentInfoApi with our previous logic into a repository.

Repository example combining the createCard method with our PaymentInfoApi

At this point, we can use the defined PaymentInfoRepository in our app, either through a state management solution or directly in the UI.

We have everything set up, but how do we test the workflows with and without 3DS enabled? Fortunately, Stripe has a page dedicated to testing. As long as test mode is enabled in the Stripe dashboard, we can use the test credit card numbers provided. Most of the testing cards can be used for regular workflows when adding cards. If a 3DS workflow needs to be tested, you can use cards specific to testing that type of workflow.

Now that we’ve set up every necessary part in our app, the only thing left is to call our backend and provide the payment method ID for the card we want to use. When selecting an item in our app, for example, we can provide a user interface where the user would select one of their available cards and provide its ID when the request is initiated.

This leaves us at the end of our journey. Hopefully, this shows how easy it is to incorporate parts of the Stripe payment system as part of a mobile app with the help of a fantastic community package.

--

--