Flutter + Stripe Connect + 3DSecure (SCA)
How to integrate Stripe Connect \w 3DSecure (SCA) in your Flutter based app!
Recently I had to implement a payment system in a Flutter-based App.
In particular, the App business follows this simple scheme:
There is a merchant who has to sell their goods online. The App enables online sales to this merchant.
In this scenario, the App is acting as an intermediary between this merchant and their customers. Because of this service, the App charges a fee from the merchant.
Stripe Connect
There are different solutions and, after some researches, I found Stripe Connect the best fitting to my specific case.
Who is Who?
Applying the terminology of Stripe, we have that:
- Platform Account = App
- Connected Account = Merchant
So I got to work and started to read the API docs and…
- **Please! Please! Make Flutter supported!**
No Flutter.
Thus, I had two choices:
- Wrapping the REST API by myself.
- Find someone that has already done the job. ❤
Guess what my choice was.
Pub Dev
After some package test, I found the following one the most promising:
Before to start: Some Key Words in Stripe
- Payment Method
- Publishable / Secret API Key
- Account ID
- Payment Intent
- Client Secret
Payment Method
PaymentMethod objects represent your customer’s payment instruments.
For example the customer’s card.
Publishable / Secret API Key
Publishable API keys are meant solely to identify your account with Stripe, they aren’t secret. In other words, they can safely be published in places like your Stripe.js JavaScript code, or in an Android or iPhone app. (From Stripe)
Secret API keys should be kept confidential and only stored on your own servers. Your account’s secret API key can perform any API request to Stripe without restriction. (From Stripe)
Stripe provides two different pairs of Publishable/Secret API Key:
- Test: use this pair to test your code. Each payment is fake. Stripe provides a set of test cards here.
- Live: use this pair in production.
You can find these keys in your Stripe Dashboard.
Account ID
The Account ID represents the merchant Stripe Connected Account. Thanks to this ID, Stripe knows to who send the paid amount.
How Can I add a Connected Account?
Dashboard > Connected Account > Create (Standard Account should be enough)
You can find the Account ID in your Stripe Dashboard under Connected Accounts.
Payment Intent
A Payment Intent guides you through the process of collecting a payment from your customer. (From Stripe)
In particular, a Payment Intent object defines the amount ($) that a customer must spend to purchase a good sold by the merchant.
Since the App collects some fees for the service offered to the merchant, in the Payment Intent, it is necessary to define the application_fee_amount (i.e. how much I earn for each transaction).
Over the amount and the application_fee_amount, it is also required to define which Payment Method to use (i.e. the customer’s card data such as card number, expiration date and, CVC) and the merchant Account ID.
There are few other parameters to config in a Payment Intent but for now, it’s just ok. Just grasp the idea. We will dig deeper about that into the coding part.
Client Secret
The PaymentIntent’s client secret is a unique key that lets you confirm the payment and update card details on the client, without allowing manipulation of sensitive information, like payment amount. (From Stripe)
Once a Payment Intent object has been created, a Client Secret is returned.
Just fix this:
The publishable key identifies who receives the “application_fee_amount” (i.e. the App)
The Account ID identifies who receives the “amount” (i.e. the merchant)
Where to store relevant data?
Let’s see which data has to be stored in the App and which in the server.
Since the Secret Key enables to authenticate payment requests, you must store it in a secure place, far away from the App (e.g. in the server).
The Account ID, as already mentioned, identifies the merchant to which send the amount spent by a customer. Since the App needs to know the Account ID, you have to find a way to communicate to the App this info. A reasonable choice would be to store the Account ID in the server and then the App will retrieve this data from the server upon request (i.e. on payments).
Amount and application_fee_amount should be “hardcoded” in the Payment Intent created in the server.
Why?
Suppose to store amount and application_fee_amount on the app-side. Someone could manually change them and set each one to zero so… no income neither for you neither for the merchant!
The Business Logic
The following sequence diagram should help you to understand the code execution flow.
But wait, what is 3D secure (SCA)?
For extra fraud protection, 3D Secure requires customers to complete an additional verification step with the card issuer when paying. Typically, you direct the customer to an authentication page on their bank’s website, and they enter a password associated with the card or a code sent to their phone. This process is familiar to customers through the card networks’ brand names, such as Visa Secure and Mastercard Identity Check. (Stripe)
Is it necessary?
Absolutely yes. In case some costumer cards have 3D secure enabled, and you don’t provide the correct code to handle it, then the transaction will be rejected.
FINALLY: LET’S CODE
Server-Side
You need to install Stripe package on your server.
Because I decided to code in python:
pip3 install stripe
I have used AWS Lambda service, but you can implement the following POST method in your favorite way.
amount/application_fee_amount
You already know what these parameters are. Just to clarify one thing:
amount = 1000
It stands for 10.00€. (In case you set currency=’eur’)
The same goes for application_fee_amount, 140 stands for 1.40€
receipt_email
Specifying in “receipt_email” the customer email, once the transaction has been processed, Stripe sends an email to the customer about the transaction status.
It is not required this parameter, however, I find this extremely useful to feedback the customer. In case of any sort of problem, the customer can show you the receipt and you can find together a solution.
confirm
Set to
true
to attempt to confirm this PaymentIntent immediately. (Stripe)
Server-side part END. Easy, isn't it?
App-Side
You can find the Flutter SDK Stripe package here:
Add the following dependency in the pubspec.yaml of your Flutter project.
dependencies:
stripe_sdk: ^3.0.1+1
3D Secure setup
Now let’s set up the AndroidManifest.xml
(Android) and Info.plist
(iOS) files to handle 3D Secure (SCA).
The library “stripe_sdk” offers complete support for SCA on iOS and Android. It handles all types of SCA, including 3DS, 3DS2, BankID and others. It handles SCA by launching the authentication flow in a web browser, and returns the result to the app. The
returnUrlForSca
parameter must match the configuration of yourAndroidManifest.xml
andInfo.plist
as shown in the next steps.
Android
You need to declare the following intent filter in android/app/src/main/AndroidManifest.xml
. This example is for the URL stripesdk://3ds.stripesdk.io
:
iOS
For iOS you need to declare the scheme in ios/Runner/Info.plist
(or through Xcode's Target Info editor, under URL Types). This example is for the URL stripesdk://3ds.stripesdk.io
:
Flutter FrontEnd
Let’s code the Credit Card Widget and a Buy Button to submit the transaction.
Follows the expected result:
Once done the previous step, let’s code the buy()
function.
Don’t forget to add checks about the validity of the: CVC, Date, and, CardNumber.
The createPaymentIntent()
creates a paymentMethod and, through the postPaymentIntent()
, submits the payment method to the server (along with the costumer email). It returns the clientSecret.
Then retrievePaymentIntent()
is invoked and it returns the paymentIntentRes.
The following function handles 3D secure enabled cards. In case the card has not the 3D secure enabled, this function is not invoked and the payment is processed smoothly.
Follows the complete code.
TESTING
By now you can test cards by yourself. Use this card set provided by Stripe.
That’s all folks!
The presented code is a basic example, but it works!
The project repository is available on my GitHub!
Any suggestions are welcome!