Using Stripe for subscription services in a Flutter app
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/Podfile
to 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:
- The Kotlin version should be set to at least 1.5.0
- Android Gradle build tools version and Gradle version should be set to a working up-to-date version
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:
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 SetupIntent
s.
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.
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.
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.
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.
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.
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.