Peer-to-Peer Payment Integration With Stream and Flutter

peer to peer payment stream chat app

Adding a peer-to-peer payment integration to your Flutter application creates a richer in-app experience for your end-users. However, you need to make sure your payment process is fast and secure.

In this tutorial, you’ll learn how to integrate a peer-to-peer payment solution in your Stream Chat Flutter application using an in-app digital wallet that provides both speed and security.

This tutorial will cover the following in detail:

  • What Are Peer-to-Peer Payment Services?
  • Setting Up Your Stream Chat Flutter App Environment
    • Set Up Stream
    • Set Up Rapyd
  • Create Your Flutter App
  • Add a Custom Action Widget
  • Add Payment Input Functionality
  • Add an Attachment Thumbnail Preview
  • Create a Digital Wallet With Rapyd Client
    • Generate a Signature
    • Transfer Money
    • Confirm a Transaction
  • Perform a Transaction
  • Build a Custom Attachment Preview
  • Wrapping Up

What Are Peer-to-Peer Payment Services?

Peer-to-peer (P2P) payment services provide a secure platform for end-users to make in-app financial transactions with other users or businesses (think Venmo, Zelle, or PayPal).

To make these mobile transactions possible, users must link their credit card or bank account details to their app account. When you send another user a payment, they can keep the P2P payment in a digital wallet for future use or transfer it to their bank account.

Setting Up Your Stream Chat Flutter App Environment

Before starting, you’ll need a:

  • Stream account for accessing the Stream Chat Messaging API.
  • Rapyd account for building P2P payment using the Rapyd Wallet API.

Stream Setup

If you don't already have a Stream account, you can start your free trial for Stream’s Chat Messaging.

If you’re working on a personal project or own a small business, you can register for a Stream Maker Account and access Stream Chat for free indefinitely.

Stream Get Started 30-day trial landing page.

After creating a Stream account, you can view your Stream dashboard and your first app.

Stream dashboard page.

Your app comes with the following:

  • API Key
  • Secret

Your API Key is only an app identifier and safe to share publicly. Your Secret helps generate authenticated user tokens and should be kept private.

From this dashboard, you can also edit the app name and create new apps.

Rapyd Setup

You’ll need a Rapyd account to build your P2P payment solution using Rapyd’s Wallet API.

Rapyd API sign-up page.

After signing up, you’ll be redirected to your Rapyd Client Portal.

In your client portal:

  1. Select the Sandbox toggle button (this gives you sample data to work with so you can test transactions with their test wallets).
  2. Go to the Developers page and save your Secret Key and Access Key (both are required to access Rapyd’s API from your app).
Rapyd API dashboard where you can select Sandbox mode.

Create Your Flutter App

In your terminal, create a new Flutter project with the following command:

$ flutter create stream_payment

⚠️Note: This tutorial was tested using version 2.2.3 of Flutter.

Open the project in your preferred IDE, and add the following packages to your pubspec.yaml file:

Loading code sample...

Replace the code inside main.dart with the following:

Loading code sample...

In the above code snippet, you:

  1. Instantiated a StreamChatClient using Stream’s Flutter SDK.
  2. Connected a user and set up a channel with the StreamChatClient.
  3. Called the StreamChat widget constructor inside MyApp and displayed the ChannelListPage, which contains a list of all channels for your Stream app.

The STREAM_KEY and USER_TOKEN are defined inside secrets.dart, like this:

Loading code sample...

To get your STREAM_KEY and USER_TOKEN:

  1. Copy your API Secret from your Stream dashboard.
  2. Go to Stream’s User JWT Generator.
  3. In the Your secret field, paste your API Secret.
  4. In the User ID field, enter a unique string to identify your user.
Stream's JWT Generator page.

⚠️Note: In a production scenario, you must generate a token using your server and one of Stream's server SDKs. You should never hardcode tokens in a production application.

Next, you’ll display a list of channels where the current user is a member. It’s better to separate the code into multiple files so that it’s easier to maintain as the code grows.

Create a new file called channel_list_page.dart and add the following code:

Loading code sample...

To display the message list view, create a new file called channel_page.dart and add the following code:

Loading code sample...

Notice that you’ve passed down the channel variable as a constructor argument; you’ll need it for other operations later.

If you run the app now, you’ll find–with only a small amount of code–that you have a pretty robust messaging app complete with all the necessary functionality 😎 .

Add a Custom Action Widget

Next, add a custom action widget so that it’s convenient for users to trigger a payment. For example, in your MessageInput you can add an IconButton like the one below:

A custom action Flutter widget so users can initiate a payment.

To add the IconButton, add the custom actions argument below to channel_page.dart:

Loading code sample...

Next, define what will happen when a user selects the payment icon. To handle this logic, do the following:

  1. Create an _onPaymentRequestPressed() method.
  2. In the method, create a new TransactionPage screen that allows users to enter the amount they’d like to send.
  3. Create a PageRouteBuilder method that surrounds the widget with a semi-transparent background.
Loading code sample...

⚠️NOTE: At the moment, you’re using a placeholder for the destination wallet. You’ll update the placeholder after defining the Rapyd client.

After receiving the amount:

  1. Create a Stream Chat Attachment.
  2. Specify the type to be payment.
  3. Set the extraData with a key of amount and a value set to the amount the user entered.
Loading code sample...

To do this, create a GlobalKey:

Loading code sample...

Then, set it as the key on MessageInput:

Loading code sample...

Add Payment Input Functionality

Next, you’ll create several UI elements for all the payment functionality.

First, create the TransactionPage where a user can enter the transaction amount.

This screen will contain a TextField to take the user input and preview the destination wallet address that was passed in.

Stream Chat Flutter app transaction page.

Create a new file called transaction_page.dart, and add the following code:

Loading code sample...

Add an Attachment Thumbnail Preview

To show the custom attachment thumbnail preview, you have to use the attachmentThumbnailBuilders property inside the MessageInput widget. This lets you create a custom widget to display the payment as a preview while the user types in a message they want to send along with it.

Loading code sample...

Insert the code for the TransactionAttachment widget:

Loading code sample...

Below is an example of the attachment with an amount of $5:

Adding an attachment thumbnail in the Stream Flutter app.

With the basic UI set up to handle payments, you can integrate P2P payments with Rapyd Wallet.

Create a Digital Wallet With Your Rapyd Client

You must use wallets to process payments and enable transactions between users. With Sandbox mode turned on in your Rapyd Client Portal, you can access a number of sample wallets with default Account Balance amounts.

Rapyd Wallets Sandbox dashboard with test wallets.

You can choose any two wallets. You’ll use one wallet as the source wallet (the wallet sending the payment) and the other one as the destination wallet (the wallet receiving the payment).

Since the wallet address will be unique for each user, you can store the wallet address as extra data within the Stream user account.

In the main.dart file, pass the source wallet address in extraData for the User:

Loading code sample...

This will store the specified wallet address, for this user, within Stream's database. In a similar manner, you can create another user with a separate wallet address on Stream. Below is a demo showing multiple users and their information.

Example Stream demo using multiple users.

To view your users:

  1. Go to your Stream dashboard.
  2. Select Options.
  3. Select Open in Chat Explorer.

To create your Rapyd Client, create a new file called apyd_client.dart and define the RapydClient class inside it:

Loading code sample...

Get your Rapyd Access Key and Secret Key from the Rapyd Client Portal and store them inside the secrets.dart file:

Loading code sample...

Generate a Signature

To use the Rapyd API, you must calculate and pass a signature within the request headers. The formula provided by Rapyd for signature calculation is as follows:

Loading code sample...

Note: Please see the Rapyd documentation for additional examples and security considerations.

This process is a bit tricky to execute correctly using Dart, but here’s the entire process for generating the signature:

Loading code sample...

The method _generateHeader is used to create the correct headers, specifically the signature. This requires the HTTP method, the endpoint, and the JSON data (if present) to generate the correct signature.

Transfer Money

With the difficult part out of the way, you can create a method to transfer money from one wallet to another.

This will require the source wallet address, the destination wallet address, and the amount to transfer.

Loading code sample...

The Transfer class is a user-defined model that lets you store the details of a Rapyd transaction. To get the Transfer model, copy+paste the code from the Peer-to-Peer GitHub repo.

Define the JSON data you’ll send as a part of the transfer request:

Loading code sample...

Get the headers by using the _generateHeader() method defined earlier:

Loading code sample...

Now, use the method to send the request to the API:

Loading code sample...

If the request is successful (status code 200), the response body will contain the details of the transaction.

Loading code sample...

You can parse this JSON response and store it inside a Transfer object. For example, final transfer = Transfer.fromJson(json).

The JSON response will contain a transaction ID with the status PEN (for pending). In order to confirm the transaction, you must perform one more POST request.

Confirm Transaction

To confirm the transaction, you must send a Transfer request to the /v1/account/transfer/response endpoint.

To do so, you must define a new method that takes the following parameters:

  • Transaction ID (”id”: id)
  • Status (”status”: response)
Loading code sample...

The JSON data you’ll send should contain the ID and the status. Use the _generateHeader() method to generate the headers, and use method to send the request:

Loading code sample...

After confirming the transaction, its status will update to one of the following:

  • CLO (or closed), meaning you successfully completed the transaction.
  • DEC (or declined), meaning you declined the transaction.

Now that you’ve defined the Rapyd Client, you can use the methods in the next section to make a successful transfer.

Perform a Transaction

You should perform the transaction before sending or showing the message inside MessageListView. The best place to do this is in the preMessageSending callback.

Before you perform the transaction, you must retrieve the source and destination wallet addresses.
Define a method called getWallets() inside the _ChannelPageState class:

Loading code sample...

This code uses the channel to retrieve the members, and stores their respective wallet addresses.

Perform the transaction inside the preMessageSending callback:

Loading code sample...

Inside the _performTransaction() method, check if the message contains an attachment with the amount as extra data.

This way, you can verify that the attachment is related to the payment.

Loading code sample...

The Boolean _isSending indicates whether the transaction is in progress. If the transaction is in progress, don’t allow the user to send any messages; instead, show a progress indicator.
Retrieve the amount from the attachment and perform the transaction using RapydClient. If the transaction is successful, update the message attachment.

Loading code sample...

Once the transaction is complete, send the message with a custom attachment that will show up on the message list.

Build a Custom Attachment Preview

You can build your custom attachment and pass it to the MessageListView using the customAttachmentBuilders property.

Loading code sample...

The custom widget is defined within the _buildPaymentMessage() method.

Loading code sample...

Now, you retrieved the required properties of the transaction stored inside the Transfer class.

The TransactionWidget will display the transaction attachment. Get the code for this widget in the Peer-to-Peer Github repo.

Completed transaction widget screen in  Flutter app.

Wrap the contents of the TransactionWidget with an InkWell to navigate to the DetailPage.

Loading code sample...

The DetailPage contains the transaction information. You can find the UI code for the page in the Peer-to-Peer GitHub repo.

Transaction details widget screen.

The final step requires the Boolean flag (_isSending) you used to indicate the progress of the transaction.

Use this Boolean flag to update the UI by showing a progress indicator with the help of the loading_overlay package.

Wrap the contents of the Scaffold with the LoadingOverlay widget and use the Boolean to indicate whether the transaction is in progress.

Loading code sample...

See the transaction process in action:

Wrapping Up

Congratulations 🎉, you successfully implemented a peer-to-peer payment solution using Stream’s Flutter SDK and Rapyd’s Wallet API.

Users can now send and receive payments within your messaging app.