Build an in-app virtual currency system where users earn coins through actions like completing their profile, daily logins, and watching ads, and spend them on in-app items like avatars, themes, and power-ups. The coin balance lives on the user's Firestore document and all earn and spend operations go through Cloud Functions for server-side validation. Users can also purchase coin packs with real money via Stripe Checkout. A persistent coin icon and balance display in the AppBar keeps users aware of their balance across all pages.
Building a Virtual Currency System in FlutterFlow
This tutorial creates a complete in-app currency system with earning, spending, and purchasing. Virtual currencies drive engagement in games, social apps, and learning platforms by rewarding user activity and enabling in-app purchases. The balance is stored securely in Firestore and all transactions go through Cloud Functions to prevent client-side manipulation. Users earn coins by completing actions, spend them in a store, and can buy additional coins with real money. This pattern is used in gaming apps, fitness rewards, educational platforms, and social media apps.
Prerequisites
- A FlutterFlow project with Firestore and Authentication configured
- Firebase Cloud Functions enabled (Blaze plan)
- A Stripe account for real-money coin purchases
- Basic understanding of App State and Cloud Functions in FlutterFlow
Step-by-step guide
Set up the Firestore data model for currency and transactions
Set up the Firestore data model for currency and transactions
Add a coinBalance (int, default 0) field to your users collection documents. Create a coin_transactions collection with fields: userId (String), type (String: earn, spend, purchase), amount (int, positive for earn/purchase, negative for spend), description (String, e.g., 'Completed profile', 'Bought Galaxy Avatar'), timestamp (Timestamp). Create a store_items collection with fields: name (String), imageUrl (String), coinCost (int), type (String: avatar, theme, powerup), description (String), isActive (bool). Create a coin_packs collection with fields: name (String, e.g., 'Starter Pack'), coinAmount (int, e.g., 100), priceUsd (double, e.g., 0.99), stripePriceId (String). Add sample store items and coin packs.
Expected result: Firestore has user balance tracking, transaction history, store items, and purchasable coin packs.
Build the persistent coin balance display in the AppBar
Build the persistent coin balance display in the AppBar
In your app's main layout or navigation scaffold, add a Row to the AppBar containing a coin Icon (a gold circle or custom coin image) and a Text widget bound to the current user's coinBalance field via a real-time Backend Query (Single Time Query OFF so it updates live). This Row appears on every page so users always see their balance. Style the Text in bold with the coin icon to the left. When the balance changes from any action, the real-time listener updates the display automatically without page refresh.
Expected result: A coin icon and balance number appear in the AppBar on every page and update in real time.
Create Cloud Functions to award coins for user actions
Create Cloud Functions to award coins for user actions
Create Cloud Functions that award coins for specific actions. For daily login: a callable function checks if the user has already earned today's bonus (query coin_transactions for type earn, description 'Daily Login', timestamp today). If not, atomically increment coinBalance by 10 and create a transaction document. For profile completion: trigger on user document update, check if all required fields are filled, and award 50 coins if so. For ad views: a callable function validates the ad completion token, then awards 5 coins. Each function uses a Firestore transaction to atomically read the balance, add the reward, and write both the updated balance and the transaction document.
1// Cloud Function: awardDailyLogin2const functions = require('firebase-functions');3const admin = require('firebase-admin');4admin.initializeApp();56exports.awardDailyLogin = functions.https.onCall(async (data, context) => {7 const userId = context.auth.uid;8 if (!userId) throw new functions.https.HttpsError('unauthenticated', 'Login required');910 const today = new Date().toISOString().split('T')[0];11 const existing = await admin.firestore().collection('coin_transactions')12 .where('userId', '==', userId)13 .where('description', '==', 'Daily Login')14 .where('timestamp', '>=', admin.firestore.Timestamp.fromDate(new Date(today)))15 .limit(1).get();1617 if (!existing.empty) return { success: false, message: 'Already claimed today' };1819 const userRef = admin.firestore().collection('users').doc(userId);20 await admin.firestore().runTransaction(async (t) => {21 const userDoc = await t.get(userRef);22 const currentBalance = userDoc.data().coinBalance || 0;23 t.update(userRef, { coinBalance: currentBalance + 10 });24 t.create(admin.firestore().collection('coin_transactions').doc(), {25 userId, type: 'earn', amount: 10, description: 'Daily Login',26 timestamp: admin.firestore.FieldValue.serverTimestamp(),27 });28 });29 return { success: true, awarded: 10 };30});Expected result: Cloud Functions securely award coins for daily login, profile completion, and ad views.
Build the in-app store for spending coins
Build the in-app store for spending coins
Create a StorePage. Query the store_items collection where isActive equals true. Display items in a GridView: each card shows the imageUrl, name, coin cost with a coin icon, and a Buy button. On Buy tap, call a Cloud Function that validates the purchase: check if the user's coinBalance >= coinCost, deduct the cost atomically, create a coin_transactions document with type spend, and add the item to a users/{uid}/purchased_items subcollection. If balance is insufficient, return an error. In FlutterFlow, show a SnackBar with success or insufficient balance messages. Purchased items should be visually marked with a checkmark and the Buy button replaced with Owned.
Expected result: Users can browse store items and spend coins to purchase them with server-side balance validation.
Add real-money coin pack purchases via Stripe
Add real-money coin pack purchases via Stripe
Create a CoinShopPage or section on the StorePage showing coin packs. Display each pack as a Container card: pack name, coin amount with a coin icon, and real price (e.g., $0.99). On tap, call a Cloud Function that creates a Stripe Checkout Session using the pack's stripePriceId, sets the mode to payment, and returns the checkout URL. Navigate to the URL. On successful payment, a Stripe webhook calls another Cloud Function that reads the checkout session metadata (userId and coinAmount), atomically increments coinBalance, and creates a coin_transactions document with type purchase. Display a Transaction History page querying coin_transactions ordered by timestamp descending.
Expected result: Users can buy coin packs with real money via Stripe, and their balance updates after successful payment.
Complete working example
1FIRESTORE DATA MODEL:2 users/{uid}3 coinBalance: int (default 0)4 ... other user fields56 coin_transactions/{txId}7 userId: String8 type: String (earn / spend / purchase)9 amount: int (+10, -50, +100)10 description: String11 timestamp: Timestamp1213 store_items/{itemId}14 name: String15 imageUrl: String16 coinCost: int17 type: String (avatar / theme / powerup)18 description: String19 isActive: bool2021 coin_packs/{packId}22 name: String ("Starter Pack")23 coinAmount: int (100)24 priceUsd: double (0.99)25 stripePriceId: String2627 users/{uid}/purchased_items/{docId}28 itemId: String29 purchasedAt: Timestamp3031APPBAR (persistent across all pages):32 Row33 ├── Image (coin icon, 20x20)34 └── Text (coinBalance, bold, real-time query)3536PAGE: StorePage37 WIDGET TREE:38 Column39 ├── Text ("Store")40 ├── GridView (store_items where isActive)41 │ └── ItemCard42 │ Image (imageUrl)43 │ Text (name)44 │ Row (coin icon + coinCost)45 │ Button ("Buy" or "Owned")46 │ On Tap: call purchaseItem Cloud Function47 ├── Divider48 ├── Text ("Buy Coins")49 └── Row / GridView (coin_packs)50 └── PackCard51 Text (name + coinAmount)52 Text (priceUsd formatted)53 Button ("Buy $0.99")54 On Tap: call Stripe Checkout Cloud Function5556CLOUD FUNCTIONS:57 awardDailyLogin → +10 coins (once per day)58 awardProfileComplete → +50 coins (once per user)59 awardAdView → +5 coins (validated)60 purchaseItem → deduct coinCost, add to purchased_items61 createCoinCheckout → Stripe Checkout Session62 handleCoinPurchaseWebhook → add coins from Stripe payment6364ALL BALANCE CHANGES:65 1. Run in Firestore transaction66 2. Read current coinBalance67 3. Validate (sufficient for spend, not duplicate for earn)68 4. Update coinBalance atomically69 5. Create coin_transactions docCommon mistakes when creating a Virtual Currency System in FlutterFlow
Why it's a problem: Storing coin balance in App State instead of Firestore
How to avoid: Store coinBalance on the Firestore user document. All earn and spend operations must go through Cloud Functions with server-side validation.
Why it's a problem: Incrementing balance directly from the client without a Cloud Function
How to avoid: Use Cloud Functions for all balance changes. Set Firestore Security Rules to deny direct writes to coinBalance from clients.
Why it's a problem: Not checking for duplicate daily login rewards
How to avoid: In the Cloud Function, query coin_transactions for today's daily login entry. Only award if no entry exists for the current date.
Best practices
- Store all currency balances in Firestore and protect with Security Rules, never in App State
- Process all balance changes through Cloud Functions with Firestore transactions
- Check for duplicate rewards before awarding (daily login, profile complete, etc.)
- Keep a complete transaction history for auditing and dispute resolution
- Display the coin balance persistently in the AppBar with real-time updates
- Use Stripe for real-money purchases with proper webhook handling
- Show purchased items as Owned in the store to prevent double purchases
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I want to build a virtual currency system in FlutterFlow with coins that users earn from actions (daily login, profile complete, ad views), spend in an in-app store (avatars, themes), and buy with real money via Stripe. All balance changes go through Cloud Functions with Firestore transactions. Give me the data model, Cloud Function code, store UI widget tree, and Stripe integration flow.
Create a store page with a coin balance display at the top, a grid of purchasable items showing images, names, and coin prices with Buy buttons, and a section below for buying coin packs with real dollar prices.
Frequently asked questions
Can I have multiple currency types like coins and gems?
Yes. Add separate balance fields on the user document for each currency type, such as coinBalance and gemBalance. Create separate earn and spend Cloud Functions for each currency. Display both balances in the AppBar.
How do I prevent users from spending coins they do not have?
In the Cloud Function, always read the current coinBalance inside a Firestore transaction. If balance is less than the item cost, reject the purchase and return an error. The transaction ensures the check and deduction happen atomically.
Can users transfer coins to other users?
Yes. Create a transfer Cloud Function that deducts from the sender and adds to the recipient in a single Firestore transaction. Create transaction documents for both users. Validate that the sender has sufficient balance.
How do I handle refunds for Stripe coin purchases?
When a Stripe refund webhook fires, create a Cloud Function that reads the original purchase amount, deducts the coins from the user balance via a transaction, and creates a coin_transactions document with a negative purchase type.
Should I use in-app purchases instead of Stripe?
For mobile apps published on the App Store or Google Play, Apple and Google require using their in-app purchase systems for virtual currencies. Stripe is suitable for web-only apps or server-to-server purchases. Check platform policies before choosing.
Can RapidDev help build a complete in-app economy?
Yes. RapidDev can build sophisticated in-app economies with multiple currency types, marketplace trading, subscription-based coin grants, anti-fraud detection, and analytics dashboards for tracking currency flow.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation