Create a peer-to-peer payment system by building digital wallets stored as Firestore documents with a balance field. Transfer money between users via a Cloud Function that executes atomic Firestore transactions to debit the sender and credit the recipient simultaneously. Add payment request functionality where users can request money from others, a unified transaction history timeline, and a bill-splitting feature that divides totals and sends payment requests to multiple friends.
Building a Peer-to-Peer Payment System in FlutterFlow
Peer-to-peer payments let users send money directly to each other within your app. This tutorial builds a complete P2P system with wallet balances, instant transfers, payment requests, transaction history, and bill splitting. All transfers use Firestore transactions via Cloud Functions to ensure money never gets lost in transit.
Prerequisites
- A FlutterFlow project with Firestore and authentication configured
- Firebase Cloud Functions enabled for atomic transfer logic
- A wallets collection with balance field for each user
- Basic familiarity with Cloud Functions and Firestore transactions
Step-by-step guide
Set up the Firestore data model for wallets, transactions, and requests
Set up the Firestore data model for wallets, transactions, and requests
Create a wallets collection with fields: userId (String), balance (Double, default 0), lastUpdatedAt (Timestamp). Each user gets one wallet document, created on signup. Create a transactions collection with fields: fromUserId (String), toUserId (String), amount (Double), message (String, optional), type (String: send, receive, request_paid), timestamp (Timestamp). Create a payment_requests collection with fields: fromUserId (String), toUserId (String), amount (Double), message (String), status (String: pending, paid, declined), createdAt (Timestamp), resolvedAt (Timestamp, nullable). Add indexes on transactions for userId lookups and on payment_requests for status filtering.
Expected result: Firestore has wallets, transactions, and payment_requests collections ready for P2P operations.
Build the wallet home page with balance and quick actions
Build the wallet home page with balance and quick actions
Create a WalletPage with the user's balance displayed prominently at the top in a Container with large styled text. Below the balance, add a Row with three action buttons: Send (paper plane icon), Request (hand icon), and Split (group icon). Below the actions, add a ListView showing recent transactions, querying the transactions collection where either fromUserId or toUserId equals the current user, ordered by timestamp descending, limited to the 10 most recent. Each transaction row shows the other user's name, the amount (green with + for received, red with - for sent), the message if any, and the timestamp. Add a 'See All' link that navigates to a full transaction history page.
Expected result: A wallet home screen showing the current balance, quick action buttons, and a feed of recent transactions.
Implement the send money flow with atomic Firestore transactions
Implement the send money flow with atomic Firestore transactions
On the Send button tap, navigate to a SendMoneyPage. Add a recipient search TextField that queries users by email or display name and shows results in a dropdown ListView. After selecting a recipient, show their avatar and name. Add an amount TextField with numeric keyboard and a message TextField. The Send button calls a Cloud Function named transferFunds that takes senderId, recipientId, amount, and message. The Cloud Function uses a Firestore transaction: read sender's wallet balance, verify balance is sufficient, debit sender (decrement balance), credit recipient (increment balance), create two transaction documents (one for sender, one for recipient). If balance is insufficient, the function throws an error that the client displays.
1// Cloud Function: transferFunds (simplified)2const functions = require('firebase-functions');3const admin = require('firebase-admin');45exports.transferFunds = functions.https.onCall(async (data, context) => {6 const { recipientId, amount, message } = data;7 const senderId = context.auth.uid;8 const db = admin.firestore();9 10 await db.runTransaction(async (t) => {11 const senderWallet = await t.get(db.doc(`wallets/${senderId}`));12 const recipientWallet = await t.get(db.doc(`wallets/${recipientId}`));13 14 const senderBalance = senderWallet.data().balance;15 if (senderBalance < amount) throw new Error('Insufficient balance');16 17 t.update(senderWallet.ref, { balance: senderBalance - amount });18 t.update(recipientWallet.ref, {19 balance: recipientWallet.data().balance + amount20 });21 t.create(db.collection('transactions').doc(), {22 fromUserId: senderId, toUserId: recipientId,23 amount, message, type: 'send', timestamp: admin.firestore.Timestamp.now()24 });25 });26 return { success: true };27});Expected result: Sending money atomically debits the sender's wallet and credits the recipient's wallet, with a transaction record for both.
Build the payment request flow with accept and decline
Build the payment request flow with accept and decline
On the Request button tap, navigate to a RequestMoneyPage with the same recipient search and amount fields. On submit, create a payment_requests document with status set to pending. The recipient sees pending requests on their WalletPage: add a section above transactions showing a ListView of payment_requests where toUserId equals current user and status is pending. Each request card shows the requester's name, amount, message, and two buttons: Pay and Decline. Pay calls the transferFunds Cloud Function with the request amount (sender is the current user, recipient is the requester) and then updates the payment_requests document status to paid with resolvedAt timestamp. Decline updates the status to declined.
Expected result: Users can request money from others, and recipients see pending requests with one-tap Pay or Decline actions.
Add bill splitting functionality
Add bill splitting functionality
On the Split button tap, navigate to a SplitBillPage. Add a total amount TextField at the top. Below, add a friends selector: query the user's contacts or friends and display them as selectable avatars in a Wrap widget. Tapped friends get a checkmark overlay and are added to a Page State selectedFriends list. Show the calculated per-person amount below: total divided by (selected friends + 1 for the current user), rounded to two decimal places. Add a tip adjustment Slider optionally. On Split, create a payment_request for each selected friend with the per-person amount and a message like 'Split: Dinner at Restaurant - your share'. Display a confirmation showing all requests sent.
Expected result: Users can enter a bill total, select friends, and automatically generate payment requests for each person's equal share.
Build the full transaction history with filters
Build the full transaction history with filters
Create a TransactionHistoryPage with a ChoiceChips filter at the top: All, Sent, Received, Requests. The All tab queries transactions where fromUserId or toUserId equals current user. The Sent tab filters to fromUserId. The Received tab filters to toUserId. The Requests tab shows payment_requests for the current user. Each transaction row includes a direction indicator icon (arrow up for sent, arrow down for received), the other party's name and avatar, the amount with color coding, the message, and the date. Add a DatePicker range at the top for filtering by time period. At the bottom, show a summary: total sent and total received for the selected period.
Expected result: A complete transaction history with type filters, date range selection, and period summary totals.
Complete working example
1FIRESTORE DATA MODEL:2 wallets/{userId}3 userId: String4 balance: Double5 lastUpdatedAt: Timestamp67 transactions/{transactionId}8 fromUserId: String9 toUserId: String10 amount: Double11 message: String (optional)12 type: "send" | "receive" | "request_paid"13 timestamp: Timestamp1415 payment_requests/{requestId}16 fromUserId: String (requester)17 toUserId: String (payer)18 amount: Double19 message: String20 status: "pending" | "paid" | "declined"21 createdAt: Timestamp22 resolvedAt: Timestamp (nullable)2324PAGE: WalletPage25WIDGET TREE:26 Column27 ├── Container (balance display, large text)28 ├── Row (quick actions)29 │ ├── Button (Send → SendMoneyPage)30 │ ├── Button (Request → RequestMoneyPage)31 │ └── Button (Split → SplitBillPage)32 ├── Text ("Pending Requests")33 ├── ListView (payment_requests, status == pending, toUserId == me)34 │ └── Container (request card)35 │ ├── Text (requester name + amount)36 │ ├── Button (Pay → transferFunds + update request)37 │ └── Button (Decline → update request status)38 ├── Text ("Recent Activity")39 └── ListView (transactions, limit 10, order by timestamp)40 └── Container (transaction row)41 ├── Icon (arrow up/down)42 ├── Column (name + message)43 └── Text (amount, green/red)4445PAGE: SendMoneyPage46WIDGET TREE:47 Column48 ├── TextField (search recipient by email/name)49 ├── ListView (search results)50 ├── Container (selected recipient: avatar + name)51 ├── TextField (amount, numeric)52 ├── TextField (message, optional)53 └── Button (Send → Cloud Function transferFunds)5455PAGE: SplitBillPage56WIDGET TREE:57 Column58 ├── TextField (total amount)59 ├── Wrap (friend avatars, tappable to select)60 ├── Text ("Each person pays: ${total / (selected + 1)}")61 ├── Slider (tip adjustment, optional)62 └── Button (Split → create payment_requests for each friend)Common mistakes when creating a Peer-to-Peer Payment System in FlutterFlow
Why it's a problem: Not using a Firestore transaction for the wallet transfer
How to avoid: Always use a Firestore transaction in a Cloud Function that reads both wallets, verifies the balance, and writes both updates atomically. If any step fails, the entire transaction rolls back.
Why it's a problem: Allowing wallet balance updates directly from the client
How to avoid: Set Firestore Security Rules to deny client writes to the wallets collection. Only Cloud Functions with admin SDK access should modify wallet balances.
Why it's a problem: Not rounding currency amounts to two decimal places
How to avoid: Round all currency calculations to two decimal places. In the split bill function, assign any remainder cents to the first person so the total always matches exactly.
Best practices
- Use Firestore transactions via Cloud Functions for all balance-modifying operations
- Deny client-side writes to wallet documents in Firestore Security Rules
- Round all currency values to two decimal places consistently
- Create transaction records for both sender and recipient for complete audit trails
- Show pending payment requests prominently on the wallet home page
- Color-code transactions green for received and red for sent for quick scanning
- Add confirmation dialogs before sending money to prevent accidental transfers
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 with Firestore. Show me the data model for wallets, transactions, and payment requests, a Cloud Function for atomic transfers using Firestore transactions, a payment request flow with accept/decline, and a bill splitting feature.
Create a wallet home page with a large balance number at the top, three action buttons (Send, Request, Split) in a row below, a pending requests section, and a list of recent transactions with green and red amount labels.
Frequently asked questions
How do users add money to their wallet?
Integrate Stripe or another payment gateway. Create a Cloud Function that handles a Stripe Checkout session for wallet top-up. On successful payment, the webhook triggers a wallet balance increment. Display an Add Funds button on the wallet page.
Can I add transaction fees to P2P transfers?
Yes. In the transferFunds Cloud Function, calculate a fee (e.g., 1% of the amount) and deduct it from the transferred amount. Create a separate transaction record for the fee. Credit the fee to a platform wallet for your business revenue.
How do I prevent users from going into negative balance?
The Cloud Function checks the sender's balance before executing the transfer. If balance is less than the transfer amount, the function throws an error. The client catches this and displays an insufficient funds message.
Can I add recurring payments between users?
Yes. Create a recurring_payments collection with sender, recipient, amount, frequency (weekly/monthly), and nextPaymentDate. A scheduled Cloud Function runs daily, finds due payments, and calls transferFunds for each one.
How do I handle unequal bill splits?
On the SplitBillPage, add a toggle between Equal Split and Custom Split. In custom mode, show a TextField per person where the organizer can type each person's specific amount. Validate that all amounts sum to the total before creating requests.
Can RapidDev help build a fintech payment application?
Yes. RapidDev can implement full payment platforms with KYC verification, multi-currency wallets, bank account integration, transaction fraud detection, regulatory compliance, and detailed financial reporting.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation