# How to Create a Peer-to-Peer Payment System in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 30-40 min
- Compatibility: FlutterFlow Pro+ (Cloud Functions required for atomic transfers)
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

### 2. 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.

### 3. 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.

```
// Cloud Function: transferFunds (simplified)
const functions = require('firebase-functions');
const admin = require('firebase-admin');

exports.transferFunds = functions.https.onCall(async (data, context) => {
  const { recipientId, amount, message } = data;
  const senderId = context.auth.uid;
  const db = admin.firestore();
  
  await db.runTransaction(async (t) => {
    const senderWallet = await t.get(db.doc(`wallets/${senderId}`));
    const recipientWallet = await t.get(db.doc(`wallets/${recipientId}`));
    
    const senderBalance = senderWallet.data().balance;
    if (senderBalance < amount) throw new Error('Insufficient balance');
    
    t.update(senderWallet.ref, { balance: senderBalance - amount });
    t.update(recipientWallet.ref, {
      balance: recipientWallet.data().balance + amount
    });
    t.create(db.collection('transactions').doc(), {
      fromUserId: senderId, toUserId: recipientId,
      amount, message, type: 'send', timestamp: admin.firestore.Timestamp.now()
    });
  });
  return { success: true };
});
```

**Expected result:** Sending money atomically debits the sender's wallet and credits the recipient's wallet, with a transaction record for both.

### 4. 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.

### 5. 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.

### 6. 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 code example

File: `FlutterFlow P2P Payment Setup`

```text
FIRESTORE DATA MODEL:
  wallets/{userId}
    userId: String
    balance: Double
    lastUpdatedAt: Timestamp

  transactions/{transactionId}
    fromUserId: String
    toUserId: String
    amount: Double
    message: String (optional)
    type: "send" | "receive" | "request_paid"
    timestamp: Timestamp

  payment_requests/{requestId}
    fromUserId: String (requester)
    toUserId: String (payer)
    amount: Double
    message: String
    status: "pending" | "paid" | "declined"
    createdAt: Timestamp
    resolvedAt: Timestamp (nullable)

PAGE: WalletPage
WIDGET TREE:
  Column
    ├── Container (balance display, large text)
    ├── Row (quick actions)
    │     ├── Button (Send → SendMoneyPage)
    │     ├── Button (Request → RequestMoneyPage)
    │     └── Button (Split → SplitBillPage)
    ├── Text ("Pending Requests")
    ├── ListView (payment_requests, status == pending, toUserId == me)
    │     └── Container (request card)
    │           ├── Text (requester name + amount)
    │           ├── Button (Pay → transferFunds + update request)
    │           └── Button (Decline → update request status)
    ├── Text ("Recent Activity")
    └── ListView (transactions, limit 10, order by timestamp)
          └── Container (transaction row)
                ├── Icon (arrow up/down)
                ├── Column (name + message)
                └── Text (amount, green/red)

PAGE: SendMoneyPage
WIDGET TREE:
  Column
    ├── TextField (search recipient by email/name)
    ├── ListView (search results)
    ├── Container (selected recipient: avatar + name)
    ├── TextField (amount, numeric)
    ├── TextField (message, optional)
    └── Button (Send → Cloud Function transferFunds)

PAGE: SplitBillPage
WIDGET TREE:
  Column
    ├── TextField (total amount)
    ├── Wrap (friend avatars, tappable to select)
    ├── Text ("Each person pays: ${total / (selected + 1)}")
    ├── Slider (tip adjustment, optional)
    └── Button (Split → create payment_requests for each friend)
```

## Common mistakes

- **Not using a Firestore transaction for the wallet transfer** — If the debit succeeds but the credit fails due to a network issue, the sender loses money that never reaches the recipient. Separate operations are not atomic. Fix: 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.
- **Allowing wallet balance updates directly from the client** — A malicious user can modify their balance directly through Firestore API calls, giving themselves unlimited funds. Fix: Set Firestore Security Rules to deny client writes to the wallets collection. Only Cloud Functions with admin SDK access should modify wallet balances.
- **Not rounding currency amounts to two decimal places** — Floating-point arithmetic in bill splitting produces values like $16.666666667, which looks unprofessional and causes penny discrepancies when totals don't add up. Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-a-peer-to-peer-payment-system-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-a-peer-to-peer-payment-system-in-flutterflow
