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.
Prerequisites
- 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
Set up the Firestore data model for wallets and transactions
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.
Build the wallet main page with balance and action buttons
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.
Implement the Add Funds flow with Stripe and webhook crediting
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.
1// Cloud Function: onDepositComplete (Stripe webhook)2const admin = require('firebase-admin');3const db = admin.firestore();45exports.onDepositComplete = async (event) => {6 const session = event.data.object;7 const { userId } = session.metadata;8 const amountCents = session.amount_total;910 const walletSnap = await db.collection('wallets')11 .where('userId', '==', userId).limit(1).get();12 const walletRef = walletSnap.docs[0].ref;1314 const batch = db.batch();15 batch.update(walletRef, {16 balance: admin.firestore.FieldValue.increment(amountCents),17 lastUpdated: admin.firestore.FieldValue.serverTimestamp(),18 });19 batch.create(db.collection('wallet_transactions').doc(), {20 walletId: walletRef.id,21 type: 'deposit',22 amount: amountCents,23 description: 'Added funds via card',24 timestamp: admin.firestore.FieldValue.serverTimestamp(),25 status: 'completed',26 });27 await batch.commit();28};Expected result: Users can add funds via Stripe. The balance updates only after the webhook confirms successful payment.
Build the peer-to-peer transfer flow with atomic transactions
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.
Add PIN verification for all financial operations
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.
Add transaction detail and filtering to the history
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 working example
1FIRESTORE DATA MODEL:2 wallets/{walletId}3 userId: String4 balance: Integer (cents)5 currency: "USD"6 lastUpdated: Timestamp7 pinHash: String8 lockedUntil: Timestamp (nullable)910 wallet_transactions/{txId}11 walletId: String12 type: "deposit" | "withdrawal" | "transfer_sent" | "transfer_received" | "purchase"13 amount: Integer (cents)14 description: String15 counterpartyId: String (nullable)16 timestamp: Timestamp17 status: "completed" | "pending" | "failed"1819WALLET PAGE:20 Column21 ├── Container (gradient bg)22 │ ├── Text (balance, headlineLarge, formatted $X.XX)23 │ └── Text (currency + lastUpdated)24 ├── Row (action buttons)25 │ ├── Container (Add Funds, green + icon)26 │ ├── Container (Send, blue send icon)27 │ └── Container (Withdraw, orange download icon)28 ├── ChoiceChips (All, Deposits, Sent, Received)29 └── ListView (wallet_transactions, ordered timestamp desc)30 └── Row31 ├── Icon (type-based, colored)32 ├── Column33 │ ├── Text (description)34 │ └── Text (timestamp, small)35 └── Text (amount, green +/ red -)3637ADD FUNDS FLOW:38 1. Open AddFundsSheet39 2. Select/enter amount40 3. PIN verification41 4. API Call: createWalletDeposit → Stripe Checkout URL42 5. Launch URL43 6. Webhook: credit balance + create transaction4445TRANSFER FLOW:46 1. Open SendMoneySheet47 2. Search recipient by email48 3. Enter amount + note49 4. PIN verification50 5. Cloud Function: Firestore transaction51 - Read sender balance52 - Verify sufficient funds53 - Decrement sender, increment recipient54 - Create 2 transaction docsCommon mistakes when creating a Virtual Wallet for Storing Digital Assets in FlutterFlow
Why it's a problem: Crediting the wallet balance before Stripe payment confirmation
How to avoid: Only credit the wallet balance inside the Stripe webhook Cloud Function after checkout.session.completed confirms successful payment.
Why it's a problem: Updating sender and recipient balances in separate writes instead of a Firestore transaction
How to avoid: Use a Firestore transaction (runTransaction) that atomically reads both balances, verifies funds, and writes both updates. If any step fails, everything rolls back.
Why it's a problem: Storing wallet balance as a floating-point dollar amount
How to avoid: 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
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build a digital wallet in FlutterFlow. Show me the Firestore data model for wallets and transactions, a wallet page with balance display and action buttons, Stripe deposit flow with webhook crediting, peer-to-peer transfer using Firestore transactions, PIN verification, and transaction history with filtering.
Create a wallet page with a large balance display at the top with a gradient background, three action buttons in a row below it (add funds, send, withdraw), and a scrollable transaction list underneath.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation