# How to Create a Virtual Wallet for Storing Digital Assets in FlutterFlow

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 30-40 min
- Compatibility: FlutterFlow Free+
- Last updated: March 2026

## TL;DR

Build a digital wallet with a wallets collection storing balance and currency, plus a wallet_transactions collection logging every deposit, withdrawal, transfer, and purchase. The wallet page shows the current balance, recent transactions, and action buttons. Add Funds uses Stripe Checkout with a Cloud Function that credits the balance only on webhook confirmation. Peer-to-peer transfers use a Firestore transaction to atomically debit the sender and credit the recipient. A PIN verification Custom Action protects all financial operations.

## Building a Digital Wallet App in FlutterFlow

A digital wallet lets users store funds, send money to other users, and view their transaction history. This tutorial builds a complete wallet system with Stripe deposits, peer-to-peer transfers using atomic Firestore transactions, a detailed transaction history, and PIN-based security for all financial operations.

## Before you start

- A FlutterFlow project with Firestore and Firebase Authentication configured
- A Stripe account with API keys stored in Cloud Function environment variables
- Cloud Functions enabled for server-side transaction processing
- Basic familiarity with FlutterFlow Action Flows and Backend Queries

## Step-by-step guide

### 1. Set up the Firestore data model for wallets and transactions

Create a wallets collection with fields: userId (String), balance (Number, default 0), currency (String, default 'USD'), lastUpdated (Timestamp), pinHash (String). Create a wallet_transactions collection with: walletId (String), type (String: 'deposit', 'withdrawal', 'transfer_sent', 'transfer_received', 'purchase'), amount (Number), description (String), counterpartyId (String, optional for transfers), timestamp (Timestamp), status (String: 'completed', 'pending', 'failed'). Add a Firestore index on wallet_transactions for walletId + timestamp descending. Create a wallet document automatically when a user signs up using a Cloud Function triggered on user creation.

**Expected result:** Firestore has wallets and wallet_transactions collections with a wallet auto-created for each new user.

### 2. Build the wallet main page with balance and action buttons

Create a WalletPage with a Column. At the top, add a Container with a gradient background showing the balance in large headlineLarge Text (formatted as currency), currency label Text, and lastUpdated timestamp. Below, add a Row of three action button Containers: Add Funds (green, plus icon), Send (blue, send icon), Withdraw (orange, download icon). Below the actions, add a 'Recent Transactions' Text heading and a ListView with Backend Query on wallet_transactions filtered by walletId, ordered by timestamp descending, limited to 20. Each transaction row shows: a type-based Icon (colored: green for deposits, red for sent/withdrawn, blue for received), description Text, amount Text (green with + for incoming, red with - for outgoing), and timestamp.

**Expected result:** A wallet page showing the current balance, action buttons, and a scrollable transaction history.

### 3. Implement the Add Funds flow with Stripe and webhook crediting

On Add Funds tap, open an AddFundsSheet Bottom Sheet. Display preset amount Containers ($10, $25, $50, $100) and a custom amount TextField. On Confirm, call a Cloud Function createWalletDeposit that creates a Stripe Checkout Session (mode: 'payment') with the amount and user metadata, returning the session URL. Launch URL to open Stripe Checkout. Create a webhook Cloud Function for checkout.session.completed that reads the userId from metadata, finds their wallet, atomically increments the balance using FieldValue.increment(), and creates a wallet_transactions document with type 'deposit' and status 'completed'. This ensures the balance is only credited after confirmed payment.

```
// Cloud Function: onDepositComplete (Stripe webhook)
const admin = require('firebase-admin');
const db = admin.firestore();

exports.onDepositComplete = async (event) => {
  const session = event.data.object;
  const { userId } = session.metadata;
  const amountCents = session.amount_total;

  const walletSnap = await db.collection('wallets')
    .where('userId', '==', userId).limit(1).get();
  const walletRef = walletSnap.docs[0].ref;

  const batch = db.batch();
  batch.update(walletRef, {
    balance: admin.firestore.FieldValue.increment(amountCents),
    lastUpdated: admin.firestore.FieldValue.serverTimestamp(),
  });
  batch.create(db.collection('wallet_transactions').doc(), {
    walletId: walletRef.id,
    type: 'deposit',
    amount: amountCents,
    description: 'Added funds via card',
    timestamp: admin.firestore.FieldValue.serverTimestamp(),
    status: 'completed',
  });
  await batch.commit();
};
```

**Expected result:** Users can add funds via Stripe. The balance updates only after the webhook confirms successful payment.

### 4. Build the peer-to-peer transfer flow with atomic transactions

On Send tap, open a SendMoneySheet Bottom Sheet. Add a TextField for recipient lookup (email or username) with a search action that queries the users collection and displays the matching user with avatar and name. Add an amount TextField and an optional note TextField. On Confirm, first verify the PIN (next step), then call a Cloud Function processTransfer that uses a Firestore transaction to: read sender wallet balance, verify sufficient funds, decrement sender balance, increment recipient balance, and create two wallet_transactions documents (transfer_sent and transfer_received). The Firestore transaction ensures atomicity: if any step fails, everything rolls back.

**Expected result:** Users can send money to other users with atomic balance updates that prevent inconsistencies.

### 5. Add PIN verification for all financial operations

Create a PINVerification Component that appears as a Bottom Sheet before any financial action. It shows a Text ('Enter your PIN'), four dot indicators, and a number pad Grid. On first use, prompt the user to set a PIN via a SetPIN flow: enter PIN, confirm PIN, hash with SHA-256, store pinHash on the wallet document. On subsequent uses, hash the entered PIN and compare with the stored pinHash. On match, trigger a callback action (the financial operation). After 5 failed attempts, lock the wallet for 30 minutes by setting a lockedUntil Timestamp on the wallet document. Require PIN verification before Add Funds, Send, and Withdraw actions.

**Expected result:** All financial operations require PIN verification, and the wallet locks after multiple failed attempts.

### 6. Add transaction detail and filtering to the history

Enhance the transaction ListView with filtering. Add a Row above the list with ChoiceChips for transaction types: All, Deposits, Sent, Received, Purchases. Filter the Backend Query based on the selected type. Add a DateTimePicker for date range filtering. Tap a transaction row to navigate to a TransactionDetailPage showing: type with colored icon, amount (large), description, counterparty name (for transfers), timestamp, and status badge. For pending transactions, show a status indicator. Add a search TextField above the list for searching transactions by description.

**Expected result:** Users can filter and search their transaction history and view full details for each transaction.

## Complete code example

File: `FlutterFlow Digital Wallet Setup`

```text
FIRESTORE DATA MODEL:
  wallets/{walletId}
    userId: String
    balance: Integer (cents)
    currency: "USD"
    lastUpdated: Timestamp
    pinHash: String
    lockedUntil: Timestamp (nullable)

  wallet_transactions/{txId}
    walletId: String
    type: "deposit" | "withdrawal" | "transfer_sent" | "transfer_received" | "purchase"
    amount: Integer (cents)
    description: String
    counterpartyId: String (nullable)
    timestamp: Timestamp
    status: "completed" | "pending" | "failed"

WALLET PAGE:
  Column
    ├── Container (gradient bg)
    │     ├── Text (balance, headlineLarge, formatted $X.XX)
    │     └── Text (currency + lastUpdated)
    ├── Row (action buttons)
    │     ├── Container (Add Funds, green + icon)
    │     ├── Container (Send, blue send icon)
    │     └── Container (Withdraw, orange download icon)
    ├── ChoiceChips (All, Deposits, Sent, Received)
    └── ListView (wallet_transactions, ordered timestamp desc)
          └── Row
                ├── Icon (type-based, colored)
                ├── Column
                │     ├── Text (description)
                │     └── Text (timestamp, small)
                └── Text (amount, green +/ red -)

ADD FUNDS FLOW:
  1. Open AddFundsSheet
  2. Select/enter amount
  3. PIN verification
  4. API Call: createWalletDeposit → Stripe Checkout URL
  5. Launch URL
  6. Webhook: credit balance + create transaction

TRANSFER FLOW:
  1. Open SendMoneySheet
  2. Search recipient by email
  3. Enter amount + note
  4. PIN verification
  5. Cloud Function: Firestore transaction
     - Read sender balance
     - Verify sufficient funds
     - Decrement sender, increment recipient
     - Create 2 transaction docs
```

## Common mistakes

- **Crediting the wallet balance before Stripe payment confirmation** — The payment might fail, be reversed, or the user might close the browser mid-checkout. Optimistic crediting creates phantom money in the system. Fix: Only credit the wallet balance inside the Stripe webhook Cloud Function after checkout.session.completed confirms successful payment.
- **Updating sender and recipient balances in separate writes instead of a Firestore transaction** — If the sender debit succeeds but the recipient credit fails (network error, quota), money disappears from the system. The reverse scenario creates money from nothing. Fix: Use a Firestore transaction (runTransaction) that atomically reads both balances, verifies funds, and writes both updates. If any step fails, everything rolls back.
- **Storing wallet balance as a floating-point dollar amount** — Floating-point arithmetic causes precision errors. For example, 0.1 + 0.2 = 0.30000000000000004 in most languages, leading to balance discrepancies over time. Fix: Store balance in cents as an integer. Display as dollars by dividing by 100 and formatting. All arithmetic operations use integers.

## Best practices

- Store balances in cents (integer) to avoid floating-point precision issues
- Only credit wallet balance in Stripe webhook handlers, never optimistically on the client
- Use Firestore transactions for peer-to-peer transfers to ensure atomic balance updates
- Require PIN verification before all financial operations
- Lock the wallet after 5 failed PIN attempts to prevent brute-force attacks
- Log every balance change as a wallet_transactions document for audit trail
- Auto-create wallet documents on user signup via Cloud Function trigger

## Frequently asked questions

### Can I support multiple currencies in the wallet?

Yes. Add a currency field to each wallet and transaction. For multi-currency wallets, create separate wallet documents per currency. For currency conversion, use an exchange rate API in a Cloud Function to convert between currencies at transfer time.

### How do I handle withdrawal to a bank account?

Use Stripe Connect payouts. The user links their bank account via Stripe, and a Cloud Function initiates a payout. Deduct the amount from the wallet balance and create a withdrawal transaction with status 'pending' until Stripe confirms the payout.

### What happens if a transfer fails mid-transaction?

Firestore transactions are atomic. If any read or write within the transaction fails, all changes roll back automatically. Neither the sender nor recipient balance is modified, and no transaction documents are created.

### Can I add transaction notifications?

Yes. After creating a wallet_transactions document, trigger a Cloud Function that sends a push notification to the affected user. For transfers, notify both sender (debit confirmation) and recipient (incoming funds).

### How do I prevent negative wallet balances?

The Cloud Function processTransfer reads the sender balance inside the Firestore transaction and returns an error if balance < amount. This check is atomic with the write, so even concurrent transfers cannot overdraw the wallet.

### Can RapidDev help build a complete payment platform?

Yes. RapidDev can implement a full wallet system including multi-currency support, bank withdrawals via Stripe Connect, recurring payments, spending analytics, and fraud detection.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-a-virtual-wallet-for-storing-digital-assets-in-flutterflow
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-to-create-a-virtual-wallet-for-storing-digital-assets-in-flutterflow
