Flutter Marketplace app with Stripe Connect - Part 1

Authors
Posted on
Posted on

In this series of blog posts, you'll learn how to configure Payments using Stripe Connect for a marketplace app built with Flutter. I'll also be working on a video tutorial series as well. Which will be more detailed and hands-on. This article series is for those of you who love reading and following articles instead. Find the links below for the code repositories:

Would rather see a video tutorial? I got you covered 👌
Watch it below 👇

Stripe account & marketplace configuration

First of all, log into your Stripe account. Once logged in, go to the Stripe Dashboard. From the left nav items, click View test data to make sure we're viewing the Dashboard in Test Mode as shown in Figure 1.1. This is important so we can build the app in Test Mode, and make some test transactions without having to actually pay anything.

View test data in stripe dashboard
Figure 1.1

Once done, go to the Developers > API Keys page to get your Publishable key and your Secret Key as shown in Figure 1.2. You'll need these later in the tutorial.

Get stripe keys image
Figure 1.2

Now, go to the Stripe Connect Settings page and fill the Branding information according to your business details. It's great that Stripe provides this configuration so when the user/seller signs up, the onboarding flow feels natural and consistent with our business's branding and colors. Figure 1.3 shows how I have configured it for my test application named "Panda Gums". Make sure to click the Save branding changes button once you're done.

Set branding info image
Figure 1.3

Now that we have set up the branding information, we need to enable Express Checkout for our country so we can work with test seller accounts created in our country. In order to do so, scroll above to the Availability section on the same Settings page and click the Manage link as shown in Figure 1.4.

Manage express accounts availability image
Figure 1.4

Now that you are on the Express Accounts Settings Page, check your desired country for which you'll create the seller accounts. Make sure to enable Transfers, Payments, and Cards capabilities for the same country as shown in Figure 1.5 and Figure 1.6. Once done, scroll to the bottom of the page and click the Save button.

Enable your country in Stripe image
Figure 1.5
Enable card payments for country image
Figure 1.6

Running the Stripe Backend server locally

Make sure you have NodeJS and Git installed in your system. Then clone the backend code from the repository by running the following command in your terminal:

git clone https://github.com/AhsanAyaz/stripe-connect-backend.git

Once cloned, navigate to the project folder and install the dependencies by running the command:

yarn
## OR
npm install

Then follow the instructions in the backend project's Readme file to set up the Environment variables. Essentially, you need to create the .env.local file in project root and set the following variables:

// .env.local
STRIPE_API_SECRET=YOUR_STRIPE_SECRET_KEY
NEXT_PUBLIC_STRIPE_API_PUBLIC=YOUR_STRIPE_PUBLIC_KEY
NEXT_PUBLIC_APP_SCHEME=pandagums
NEXT_PUBLIC_HOST=http://localhost:3000
STRIPE_APP_FEE=1.23

Once done, run the backend server by running the following command:

yarn dev
## OR
npm run dev

This should start the backend server at http://localhost:3000. The API routes in the backend project are avaible at http://localhost:3000/api/*. We don't essentially need to code anything for the backend in this tutorial. Everything is already set up. The specific API endpoint we're going to use in this article is GET http://localhost:3000/api/stripe/account. And the code for this API resides here in the stripe-connect-backend project. Let's have a look at it.

// pages/api/stripe/account/index.js
const stripe = require("stripe")(process.env.STRIPE_API_SECRET)
const host = process.env.NEXT_PUBLIC_HOST

const stripeAccount = async (req, res) => {
  const { method } = req
  if (method === "GET") {
    // CREATE CONNECTED ACCOUNT
    const { mobile } = req.query
    const account = await stripe.accounts.create({
      type: "express",
    })
    const accountLinks = await stripe.accountLinks.create({
      account: account.id,
      refresh_url: `${host}/api/stripe/account/reauth?account_id=${account.id}`,
      return_url: `${host}/register${mobile ? "-mobile" : ""}?account_id=${
        account.id
      }&result=success`,
      type: "account_onboarding",
    })
    if (mobile) {
      // In case of request generated from the flutter app, return a json response
      res.status(200).json({ success: true, url: accountLinks.url })
    }
    else {
      // In case of request generated from the web app, redirect
      res.redirect(accountLinks.url)
    }
  }
  else if (method === "DELETE") {...} else if (method === "POST") {...}
}

export default stripeAccount

Note that we use the Stripe NodeJS package/sdk to connect with stripe using the STRIPE_API_SECRET.

We first create the Account object using the stripe.accounts.create() method. Then we create the Account Link object using the stripe.accountLinks.create() method. Note that we provide the account_id from Account Object, the refresh_url, the type set to 'account_onboarding', and the return_url to this method. And we then return the url received with the Account Link object back to the Flutter app. More on this in a later section.

Getting started with the Flutter marketplace app

Run the following commands to clone the project from the repository and to checkout the start-here branch:

git clone https://github.com/AhsanAyaz/flutter-stripe-connect

git checkout start-here

Assuming you already have set up Flutter in your machine, open the project in Android Studio or your favorite editor. Install the dependencies of the Flutter project by running the following command in your project:

flutter pub get

Now run the project your Android Emulator or AVD. Once done successfully, you should be able to see the app running as shown in Figure 1.7.

App start state image
Figure 1.7

As you can see in Figure 1.7, we have a marketplace app named Panda Gums. There will be Merchants/Sellers registered within this marketplace. And there will be Customers paying for products from these sellers. In this part, we'll only focus on registering Sellers/Merchants in the app.

Understanding Seller Registration via Stripe Connect Onboarding

The Seller registration flow includes a couple of things as shown in Figure 1.8.

Sellers onboarding flow image
Figure 1.8

Essentially, the steps are as follows:

  1. Our Flutter app sends a request to our NodeJS Backend API
  2. The NodeJS backend receives the request and uses the Stripe NodeJS SDK to create a Stripe Account for the Seller. In return, the backend receives the Account object.
  3. The backend then uses the ID from the Seller's Account object to create an Account Link using the Stripe SDK. This Account Link grants the Seller's account to access the Stripe-hosted Connect Onboarding later. In response of the Account Link creation, we receive the Account Link object from Stripe.
  4. We use the url property from the Account Link object received, and send it back to the Flutter app.
  5. The Flutter app opens the Account Link URL into the mobile's browser using the url_launcher flutter package.
  6. In the Browser, we go through the entire Stripe Connect Onboarding flow. And Stripe provides a great way of using Test Data when we're working in the Test mode.
  7. Finally, when the seller has entered all the required information and has completed the onboarding flow, we land on the return_url. Which is configured in our NodeJS Backend for when we create the Account Link. And that's basically a web page served by our backend itself.
  8. The web page we land on in Step 7 contains a button that says Continue in App. We click it and that deep links into our Flutter app to open a new app page containing the success message.

Let's write some Code, Yeah?

Seller registration flow

Now that we understand how the Seller's Registration flow works, let's jump into the code. We'll begin with creating a Service in our Flutter application to communicate with the NodeJS backend.

In the Flutter app project, create a new folder inside the lib folder named services. If you're using Android Studio, you can right click on the lib folder, hover on the option New, and click on Package to create the folder. Inside the folder, create a file named stripe-backend-service.dart and add the following code:

// lib/services/stripe-backend-service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;

import '../constants.dart';

class CreateAccountResponse {
  late String url;
  late bool success;

  CreateAccountResponse(String url, bool success) {
    this.url = url;
    this.success = success;
  }
}

class CheckoutSessionResponse {
  late Map<String, dynamic> session;

  CheckoutSessionResponse(Map<String, dynamic> session) {
    this.session = session;
  }
}

class StripeBackendService {
  static String apiBase = '$BACKEND_HOST/api/stripe';
  static String createAccountUrl =
      '${StripeBackendService.apiBase}/account?mobile=true';
  static Map<String, String> headers = {'Content-Type': 'application/json'};

  static Future<CreateAccountResponse> createSellerAccount() async {
    var url = Uri.parse(StripeBackendService.createAccountUrl);
    var response = await http.get(url, headers: StripeBackendService.headers);
    Map<String, dynamic> body = jsonDecode(response.body);
    return new CreateAccountResponse(body['url'], true);
  }
}

Installing and configuring url_launcher

Install the url_launcher package in the flutter app project by running the following command in the project root:

flutter pub add url_launcher
## Based on your editor, you might also want to run `flutter pub get`

Now open the file android/app/src/main/AndroidManifest.xml and update to add the <queries> object as follows:

// android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.flutter_stripe_connect">
    <queries>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="https" />
        </intent>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="http" />
        </intent>
        <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="pandagums" />
        </intent>
    </queries>
   <application
        android:label="flutter_stripe_connect"
        android:icon="@mipmap/ic_launcher">
        ...
    </application>
</manifest>

This makes sure that our Flutter app can open http://, https:// and pandagums:// links in the browser using the url_launcher package. The Account Link object that we receive from Stripe has the https:// scheme. Therefore, this step is important.

Note: After the above change, the app needs a full restart. Restart the app before proceeding with further steps.

Open the file lib/pages/register.dart and modify it as follows:

// lib/pages/register.dart
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:sn_progress_dialog/progress_dialog.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_stripe_connect/services/stripe-backend-service.dart';

class RegisterSeller extends StatefulWidget {
  
  _RegisterSellerState createState() => _RegisterSellerState();
}

class _RegisterSellerState extends State<RegisterSeller> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Register as Seller"),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            ProgressDialog pd = ProgressDialog(context: context);
            pd.show(
              max: 100,
              msg: 'Please wait...',
              progressBgColor: Colors.transparent,
            );
            try {
              CreateAccountResponse response = await StripeBackendService.createSellerAccount();
              pd.close();
              await canLaunch(response.url) ? await launch(response.url) : throw 'Could not launch URL';
            } catch (e) {
              log(e.toString());
              pd.close();
            }
          },
          child: Text('Register with Stripe'),
        ),
      ),
    );
  }
}

The preceding change ultimately results in calling our Backend API and recieving the Account Link URL. Notice that we call the methods canLaunch() and launch() from the url_launcher package. And we provide response.url to these methods to identify if our app can open the link in the browser .

If you go to the Connected Accounts page in the Stripe dashboard, you'll see that there are currently no connected accounts as shown in Figure 1.9.

No Connected Accounts image
Figure 1.9

Now open the Flutter app and go to the Registration page by tapping the Register as Seller card from the home page. Then tap the Register with Stripe button. Ideally, it should open the Stripe Connect Onboarding flow in the mobile's browser as shown in Figure 1.10.

Sellers onboarding flow image
Figure 1.10

Note: You might encounter the following SocketException error when working with Android:

SocketException: OS Error: Connection refused, errno = 111, address = localhost, port = 43248

What you need to do is expose the port 3000 to the Android emulator by using the following command:

adb reverse tcp:3000 tcp:3000

Once you have the Onboarding flow opened in the Browser, tap the the test phone number link there to use the Stripe-provided test phone number. Then enter your Stripe email (the real one) for the Stripe account you're using right now.

Note: We need the real email for this step as Stripe will ask you to log into your account for this step.

Once you've successfully logged in, Stripe will redirect you back to the Onboarding flow. Use the Test code and test data provided by Stripe in all the following steps. Make sure the Country you select is the one we enabled Payments for in the initial Stripe configuration step.

Fill all the values (dummy values) i.e. the name, birth date, address in the form as shown in Figure 1.11. Also, select the Industry and write a dummy Business Website when prompted.

Dummy values in onboarding image
Figure 1.11

Use the test account when asked for Bank Details. And finally, review the information and hit the Submit button. You should be redirected to a web page served by our backend as shown in Figure 1.12. Notice that we have a button with text Continue in App. Ideally, pressing this button should open up the app and show us a new page with success message. However, that's not the case since we haven't implemented Deep linking yet.

Onboarding result redirect image
Figure 1.12

Even though we're not finished yet with the tutorial, we have made some great progress. If you go to the Connected Accounts page now, you should be able to see the newly registered connected account. Click on it and you should see the account details as shown in Figure 1.13

Seller account created image
Figure 1.13

And that is it for this article. See the next section to summarize what we've learnt today.

Conclusion

In this article, you learnt how to configure Stripe Connect in the Stripe Dashboard. And you learnt how to use http calls in our Flutter App, via our backend to create Connected Accounts for Sellers in Stripe. Ultimately, we are able to create Connected Accounts. And they can receive payments. However, in terms of user-experience for the Seller registration, our tutorial isn't finished yet. And we'll continue with it in the next part. Stay tuned, and as always, Happy Coding 🙌🏼!

Akso, make sure to share this article on your social media to help others 😎.