Build a peer-to-peer payment system using Stripe Connect with Express accounts for each user. Cloud Functions handle transfers via stripe.transfers.create(), manage KYC verification through Stripe Connect onboarding, and track balances. Users send money by selecting a recipient and entering an amount, which triggers a Cloud Function that creates a Stripe transfer between connected accounts. Each user completes identity verification through Stripe's hosted onboarding flow before receiving payouts.
Building Payment Infrastructure for User-to-User Transfers
Peer-to-peer payments require more than just charging a card. This tutorial covers the Stripe Connect infrastructure: creating Express accounts for each user, handling KYC identity verification, executing transfers between accounts, and tracking balances. Stripe handles the complex compliance, tax reporting, and payout logistics while your FlutterFlow app provides the user interface.
Prerequisites
- A FlutterFlow project with Firestore and authentication configured
- A Stripe account with Connect enabled (Settings > Connect in Stripe Dashboard)
- Stripe API secret key stored in Cloud Function environment variables
- Cloud Functions environment configured for your project
Step-by-step guide
Set up Stripe Connect and the Firestore data model
Set up Stripe Connect and the Firestore data model
In the Stripe Dashboard, enable Connect and configure your platform settings. Choose Express account type for simplicity. In Firestore, extend user documents with: stripeConnectId (String, nullable), stripeOnboardingComplete (Boolean, default false), stripePayoutsEnabled (Boolean). Create a transfers collection: fromUserId (String), toUserId (String), amount (Integer, in cents), currency (String, 'usd'), stripeTransferId (String), status (String: 'pending', 'completed', 'failed'), createdAt (Timestamp), note (String, optional message from sender). This model tracks all transfers with their Stripe reference IDs for reconciliation.
Expected result: Stripe Connect is enabled and Firestore has user payment fields and a transfers collection for tracking all transactions.
Create Cloud Functions for Stripe Connect account onboarding
Create Cloud Functions for Stripe Connect account onboarding
Build two Cloud Functions. First, createConnectAccount: creates a Stripe Express account for the user via stripe.accounts.create() with type 'express', stores the account ID in the user's stripeConnectId field, then generates an Account Link URL via stripe.accountLinks.create() with return and refresh URLs pointing to your app. Second, checkOnboardingStatus: checks the Stripe account's charges_enabled and payouts_enabled fields and updates the user's Firestore document accordingly. The user opens the Account Link URL in a WebView or external browser to complete Stripe's hosted identity verification form.
1// Cloud Function: createConnectAccount2const functions = require('firebase-functions');3const admin = require('firebase-admin');4const stripe = require('stripe')(5 process.env.STRIPE_SECRET_KEY);67exports.createConnectAccount = functions.https8 .onCall(async (data, context) => {9 const userId = context.auth.uid;10 const user = (await admin.firestore()11 .doc(`users/${userId}`).get()).data();12 // Create Express account13 const account = await stripe.accounts.create({14 type: 'express',15 email: user.email,16 capabilities: {17 transfers: { requested: true },18 },19 });20 await admin.firestore().doc(`users/${userId}`)21 .update({ stripeConnectId: account.id });22 // Create onboarding link23 const accountLink = await stripe.accountLinks24 .create({25 account: account.id,26 refresh_url: `${data.baseUrl}/onboarding-refresh`,27 return_url: `${data.baseUrl}/onboarding-complete`,28 type: 'account_onboarding',29 });30 return { url: accountLink.url, accountId: account.id };31 });3233exports.checkOnboardingStatus = functions.https34 .onCall(async (data, context) => {35 const userId = context.auth.uid;36 const user = (await admin.firestore()37 .doc(`users/${userId}`).get()).data();38 const account = await stripe.accounts39 .retrieve(user.stripeConnectId);40 await admin.firestore().doc(`users/${userId}`)41 .update({42 stripeOnboardingComplete:43 account.details_submitted,44 stripePayoutsEnabled:45 account.payouts_enabled,46 });47 return {48 complete: account.details_submitted,49 payoutsEnabled: account.payouts_enabled50 };51 });Expected result: Users create a Stripe Connect account and complete identity verification through Stripe's hosted onboarding flow.
Build the identity verification and wallet setup UI
Build the identity verification and wallet setup UI
Create a WalletSetupPage. Check the user's stripeOnboardingComplete field. If false, show a setup card explaining that identity verification is required to send and receive money. Add a Verify Identity button that calls the createConnectAccount Cloud Function and opens the returned URL in a WebView or launches it externally. When the user returns, call checkOnboardingStatus to update their status. If onboarding is complete, show a green checkmark with 'Identity Verified' status. Display a KYC status indicator: 'Not Started' (grey), 'In Progress' (yellow), 'Verified' (green), 'Action Required' (red, when Stripe needs more info).
Expected result: Users see their verification status and can complete Stripe's identity verification flow. Status updates reflect in real-time.
Implement the send money flow with Cloud Function transfers
Implement the send money flow with Cloud Function transfers
Create a SendMoneyPage with a user search TextField to find the recipient by name or email. Display the selected recipient's name and avatar. Add an amount TextField (numeric, formatted as currency) and an optional note TextField. On send, call a transferMoney Cloud Function that: validates both sender and recipient have verified Stripe Connect accounts, creates a charge on the sender via stripe.charges.create() with the platform as destination, then creates a transfer to the recipient via stripe.transfers.create(). Write the transfer record to Firestore with status 'completed'. Show a success animation and transfer confirmation with details.
1// Cloud Function: transferMoney2exports.transferMoney = functions.https3 .onCall(async (data, context) => {4 const { toUserId, amount, note } = data;5 const fromUserId = context.auth.uid;6 const db = admin.firestore();7 const fromUser = (await db.doc(8 `users/${fromUserId}`).get()).data();9 const toUser = (await db.doc(10 `users/${toUserId}`).get()).data();11 if (!fromUser.stripePayoutsEnabled ||12 !toUser.stripePayoutsEnabled) {13 throw new functions.https.HttpsError(14 'failed-precondition',15 'Both users must complete verification');16 }17 // Create transfer18 const transfer = await stripe.transfers.create({19 amount: amount, // in cents20 currency: 'usd',21 destination: toUser.stripeConnectId,22 transfer_group: `p2p_${fromUserId}_${toUserId}`,23 });24 // Record in Firestore25 await db.collection('transfers').add({26 fromUserId, toUserId, amount,27 currency: 'usd',28 stripeTransferId: transfer.id,29 status: 'completed',30 note: note || '',31 createdAt: admin.firestore32 .FieldValue.serverTimestamp()33 });34 return { success: true,35 transferId: transfer.id };36 });Expected result: Users search for a recipient, enter an amount, and send money. The Cloud Function processes the Stripe transfer and records it in Firestore.
Display transfer history and available balance
Display transfer history and available balance
Create a WalletPage showing the user's current balance and transfer history. Call a Cloud Function that retrieves the Stripe balance for the user's Connect account via stripe.balance.retrieve({stripeAccount: connectId}). Display the available balance prominently at the top. Below, add a ListView with a Backend Query on transfers where fromUserId equals current user OR toUserId equals current user, ordered by createdAt descending. Each transfer row shows: direction icon (up arrow for sent, down arrow for received), recipient or sender name, amount (red for sent, green for received), note text if present, date, and status badge. Add filter ChoiceChips for All, Sent, and Received.
Expected result: Users see their available balance and a filterable list of all sent and received transfers with amounts and dates.
Complete working example
1FIRESTORE DATA MODEL:2 users/{userId} (extended)3 stripeConnectId: String (nullable)4 stripeOnboardingComplete: Boolean5 stripePayoutsEnabled: Boolean67 transfers/{transferId}8 fromUserId: String9 toUserId: String10 amount: Integer (cents)11 currency: 'usd'12 stripeTransferId: String13 status: 'pending' | 'completed' | 'failed'14 note: String (optional)15 createdAt: Timestamp1617CLOUD FUNCTIONS:18 1. createConnectAccount19 → stripe.accounts.create({ type: 'express' })20 → stripe.accountLinks.create() → return onboarding URL2122 2. checkOnboardingStatus23 → stripe.accounts.retrieve(connectId)24 → update user doc with verification status2526 3. transferMoney(toUserId, amount, note)27 → validate both users verified28 → stripe.transfers.create({ destination: connectId })29 → record in transfers collection3031 4. getBalance32 → stripe.balance.retrieve({ stripeAccount: connectId })33 → return available balance3435PAGE: WalletSetupPage36 Column37 ├── KYC Status Indicator (grey/yellow/green/red)38 ├── If not verified:39 │ Card: explanation + Verify Identity button40 │ → calls createConnectAccount → opens onboarding URL41 └── If verified:42 Text: 'Identity Verified' + green checkmark4344PAGE: SendMoneyPage45 Column46 ├── TextField (search recipient by name/email)47 ├── Container (selected recipient: avatar + name)48 ├── TextField (amount, numeric, currency format)49 ├── TextField (optional note)50 └── Button: Send Money51 → Cloud Function transferMoney52 → success animation + confirmation5354PAGE: WalletPage55 Column56 ├── Container (balance card: available amount)57 ├── ChoiceChips (All | Sent | Received)58 └── ListView (transfers, ordered by date desc)59 Row: direction icon + name + amount + note + dateCommon mistakes when implementing a Peer-to-Peer Payment System in FlutterFlow
Why it's a problem: Using regular Stripe charges and manual payouts instead of Stripe Connect
How to avoid: Use Stripe Connect with Express accounts. Stripe handles KYC verification, identity checks, tax reporting (1099s), and payout scheduling automatically.
Why it's a problem: Allowing transfers before both users complete identity verification
How to avoid: Check stripePayoutsEnabled for both sender and recipient before processing any transfer. Show clear status indicators and verification prompts.
Why it's a problem: Storing Stripe API keys in FlutterFlow frontend code or Firestore
How to avoid: Store the Stripe secret key only in Cloud Function environment variables. All Stripe API calls happen server-side in Cloud Functions, never from the client.
Best practices
- Use Stripe Connect Express accounts so Stripe handles KYC, compliance, and payouts
- Process all Stripe API calls through Cloud Functions, never from the client
- Verify both sender and recipient have completed onboarding before allowing transfers
- Store transfer records in Firestore for your own audit trail alongside Stripe's records
- Display KYC status prominently with clear calls to action for incomplete verification
- Use amounts in cents (integers) to avoid floating-point rounding errors
- Implement webhook listeners for transfer status updates from Stripe
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build a peer-to-peer payment system in FlutterFlow using Stripe Connect. Show me the Cloud Functions for creating Express accounts, processing transfers with stripe.transfers.create(), handling KYC onboarding, and retrieving balances. Include the Firestore data model and FlutterFlow page layouts for wallet setup, send money, and transfer history.
Create a wallet page with a large balance amount at the top inside a colored container, choice chips below for All, Sent, and Received filters, and a list view of transfer rows showing an icon, name, amount, and date.
Frequently asked questions
What is Stripe Connect and why do I need it for P2P payments?
Stripe Connect is Stripe's platform for enabling payments between users. It handles identity verification, compliance, tax reporting, and payouts for each user. Without it, you would need to build all of this infrastructure yourself, which is legally complex and time-consuming.
How long does Stripe identity verification take?
Most verifications complete instantly or within minutes. Some cases requiring manual document review can take 1-2 business days. Users can check their status in the app and Stripe sends email updates.
What are the fees for P2P transfers using Stripe Connect?
Stripe charges 2.9% + 30 cents per transaction for card payments, plus 0.25% + 25 cents per payout to connected accounts. You can choose whether the sender, recipient, or your platform absorbs these fees.
Can users withdraw their balance to a bank account?
Yes. Stripe Connect Express accounts include automatic payout scheduling. Users set their bank account during onboarding and Stripe sends payouts on a configurable schedule (daily, weekly, monthly).
How do I handle transfer disputes or refunds?
Create a dispute flow where users can flag a transfer. An admin reviews disputes and can initiate a refund via stripe.refunds.create() in a Cloud Function. Stripe also has built-in dispute handling for card chargebacks.
Can RapidDev help build a payment platform with advanced features?
Yes. RapidDev can implement split payments, escrow holds, multi-currency transfers, recurring payments, group expense splitting, and detailed financial reporting dashboards.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation