# How to Monitor Your FlutterFlow App's Payments

- Tool: FlutterFlow
- Difficulty: Beginner
- Time required: 30-45 min
- Compatibility: FlutterFlow Pro+ (code export required for Custom Widget charts)
- Last updated: March 2026

## TL;DR

Effective payment monitoring combines Stripe Dashboard for financial metrics with Firestore order sync for in-app status displays. Use Stripe webhooks to write order records to Firestore after payment succeeds. Build a simple revenue dashboard with fl_chart in a Custom Widget. Set up Stripe email alerts for failed payments and refunds. Monitoring both systems lets you catch issues fast and show payment status to your users.

## Why You Need Two Payment Monitoring Systems

Stripe Dashboard is the source of truth for your money — it shows every charge, payout, refund, and dispute. But your users can't see Stripe Dashboard. They need to see payment confirmation inside your app. This requires syncing Stripe events to Firestore via webhooks and displaying the order status from Firestore. The two systems serve different purposes: Stripe for your business metrics, Firestore for your users' experience. Setting up both takes under an hour and prevents the most common payment support complaints — 'I paid but nothing happened' and 'where is my order?'

## Before you start

- A Stripe account with at least one test payment made
- A FlutterFlow project with Firebase and Firestore connected
- A Cloud Function deployed for your payment flow (or Stripe Checkout sessions)
- FlutterFlow Pro plan for Custom Widget (Standard works for basic order status display)

## Step-by-step guide

### 1. Configure Stripe Dashboard Metrics and Alerts

Log in to dashboard.stripe.com. The Home tab shows your key metrics: gross volume, net volume, successful payment rate, and dispute rate. Navigate to Payments > Reports to access Revenue, Payouts, and Reconciliation reports. Enable email alerts by going to Settings > Email Notifications and enabling: failed payments, disputes, refunds, and successful payouts. Set a spend threshold alert at Settings > Billing Alerts so you know when you hit monthly revenue milestones. Bookmark the Stripe Radar dashboard (Radar > Overview) if you're in a fraud-prone category — it shows suspicious activity patterns.

**Expected result:** You receive email alerts for every payment failure, dispute, and refund, and can see key revenue metrics on the Stripe Dashboard home screen.

### 2. Create a Firestore Orders Collection for Payment Sync

In FlutterFlow, open your Firestore schema and create an 'orders' collection with these fields: userId (String), productId (String), amount (double), currency (String), status (String — values: pending, paid, failed, refunded), stripePaymentIntentId (String), stripeSessionId (String), createdAt (Timestamp), paidAt (Timestamp, nullable). This schema lets you display order history to users and track payment status without exposing Stripe details. The stripePaymentIntentId and stripeSessionId fields let you look up the corresponding Stripe event for any order, which is essential for debugging payment disputes.

**Expected result:** An 'orders' collection is defined in your FlutterFlow Firestore schema and ready to receive data from your payment webhook.

### 3. Deploy a Stripe Webhook Cloud Function to Sync Orders

In Firebase Console, go to Functions and create a new Cloud Function. This function receives Stripe webhook events and writes order records to Firestore. Register the webhook endpoint URL in Stripe at Dashboard > Developers > Webhooks. Listen for these events: checkout.session.completed (creates the order document), payment_intent.payment_failed (updates status to failed), charge.refunded (updates status to refunded). Always verify the Stripe webhook signature using the webhook secret — this prevents attackers from faking payment success events.

```
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

exports.stripeWebhook = functions.https.onRequest(async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.rawBody,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    console.error('Webhook signature verification failed:', err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  const db = admin.firestore();

  if (event.type === 'checkout.session.completed') {
    const session = event.data.object;
    await db.collection('orders').add({
      userId: session.metadata.userId,
      productId: session.metadata.productId,
      amount: session.amount_total / 100,
      currency: session.currency,
      status: 'paid',
      stripeSessionId: session.id,
      createdAt: admin.firestore.FieldValue.serverTimestamp(),
      paidAt: admin.firestore.FieldValue.serverTimestamp(),
    });
  }

  if (event.type === 'payment_intent.payment_failed') {
    const intent = event.data.object;
    await db.collection('orders')
      .where('stripePaymentIntentId', '==', intent.id)
      .get()
      .then(snapshot => {
        snapshot.forEach(doc => {
          doc.ref.update({
            status: 'failed',
            errorMessage: intent.last_payment_error?.message || 'Payment failed',
          });
        });
      });
  }

  res.json({ received: true });
});
```

**Expected result:** Stripe payment events automatically create or update order documents in Firestore within seconds of the payment completing.

### 4. Display Order History and Payment Status in Your App

In FlutterFlow, create an Order History page. Add a ListView with a Firestore query on the 'orders' collection filtered by userId == currentUserUID and ordered by createdAt descending. In each list item, display: amount formatted as currency, product name (look up from productId or denormalize the name into the order document), paidAt date, and a status badge. Style the status badge with conditional colors: green Container for 'paid', red for 'failed', orange for 'pending', grey for 'refunded'. This gives users a clear view of their payment history and resolves the most common support question — 'did my payment go through?'

**Expected result:** Users see a list of their orders with status badges, amounts, and dates — with paid orders showing green and failed orders showing red.

### 5. Set Up Revenue Dashboard with Key Metrics

For your admin or personal tracking, create a Payment Analytics page visible only to users with an 'admin' role. Use Firestore aggregate queries (via a Custom Action) to calculate: total revenue (sum of amount where status == paid), order count, average order value, and failure rate. Display these in metric cards using a Row of stat containers. For a simple revenue chart showing daily totals, use fl_chart in a Custom Widget — add fl_chart as a package dependency in your project settings. Alternatively, embed a Stripe Dashboard link in an iFrame for web deployments (Stripe allows this for internal tools).

**Expected result:** An admin page shows total revenue, order count, and failure rate — giving you a quick pulse on payment health without opening Stripe Dashboard.

## Complete code example

File: `order_status_widget.dart`

```dart
// Custom Function: Format order amount for display
String formatOrderAmount(double amount, String currency) {
  final formatter = NumberFormat.currency(
    symbol: currency.toUpperCase() == 'USD' ? '\$' : currency.toUpperCase() + ' ',
    decimalDigits: 2,
  );
  return formatter.format(amount);
}

// Custom Function: Get status badge color
Color getStatusColor(String status) {
  switch (status.toLowerCase()) {
    case 'paid':
      return const Color(0xFF22C55E); // green-500
    case 'pending':
      return const Color(0xFFF59E0B); // amber-500
    case 'failed':
      return const Color(0xFFEF4444); // red-500
    case 'refunded':
      return const Color(0xFF6B7280); // gray-500
    default:
      return const Color(0xFF6B7280);
  }
}

// Custom Function: Get status display label
String getStatusLabel(String status) {
  switch (status.toLowerCase()) {
    case 'paid':
      return 'Payment Successful';
    case 'pending':
      return 'Processing';
    case 'failed':
      return 'Payment Failed';
    case 'refunded':
      return 'Refunded';
    default:
      return 'Unknown';
  }
}

// Custom Function: Format order date
String formatOrderDate(DateTime? timestamp) {
  if (timestamp == null) return 'Date unknown';
  return DateFormat('MMM d, yyyy \'at\' h:mm a').format(timestamp);
}
```

## Common mistakes

- **Relying only on Stripe Dashboard without syncing to Firestore** — Stripe Dashboard shows you your money, but your users can't see it. Without Firestore order sync, users have no way to view their order history or payment status inside your app, leading to support requests asking 'did my payment go through?' Fix: Deploy the webhook Cloud Function to sync every checkout.session.completed event to a Firestore 'orders' document. This creates the user-facing payment record automatically.
- **Not verifying the Stripe webhook signature** — Without signature verification, anyone can POST to your webhook URL with a fake 'payment succeeded' payload, granting users access to paid content without actually paying. Fix: Always use stripe.webhooks.constructEvent() with your webhook secret. This cryptographically verifies the event came from Stripe.
- **Calculating revenue aggregates from Firestore on every page load** — Summing all order amounts on every admin page load reads every order document each time. At 10,000 orders, this costs 10,000 Firestore reads per page view. Fix: Use a scheduled Cloud Function to pre-calculate aggregates (daily revenue, order count) and store them in a single 'metrics' document. Display that document on your admin page.

## Best practices

- Always verify Stripe webhook signatures — never trust unsigned payment events
- Denormalize product names and user emails into order documents to reduce additional Firestore reads
- Store Stripe payment intent IDs and session IDs in orders so you can cross-reference with Stripe Dashboard
- Set up Stripe email alerts for failed payments, disputes, and refunds on day one
- Use Firestore security rules to ensure users can only read their own orders
- Test your webhook with the Stripe CLI before deploying to production
- Set up budget alerts in Firebase Console to catch unexpected Firestore read spikes from payment queries

## Frequently asked questions

### How do I know if a Stripe payment succeeded in my FlutterFlow app?

The most reliable method is a Stripe webhook: Stripe POSTs a checkout.session.completed event to your Cloud Function, which creates an order document in Firestore with status 'paid'. Your app queries this document to confirm payment. Never rely on URL parameters from Stripe's success redirect URL, as users can manipulate them.

### Can I display Stripe payment status directly in FlutterFlow without a backend?

No. Stripe's API requires your secret key, which must never be in client-side code. You must use a Cloud Function or server to call Stripe and return the status. The Cloud Function creates an order record in Firestore, which FlutterFlow then reads directly — that is safe.

### How do I handle failed payments in my FlutterFlow app?

Listen for the payment_intent.payment_failed Stripe webhook event in your Cloud Function. Update the corresponding order document's status to 'failed' and store the error message from intent.last_payment_error.message. Display a clear message to the user with the failure reason and a link to retry payment.

### What Stripe Dashboard metrics should I check daily?

Check gross volume (total revenue), successful payment rate (anything below 95% needs investigation), dispute rate (above 0.5% risks losing Stripe access), and payout status (to confirm funds are arriving in your bank account on schedule).

### How do I handle refunds in my FlutterFlow app?

Issue refunds from Stripe Dashboard (Payments > select payment > Refund). Listen for the charge.refunded webhook event in your Cloud Function and update the order document's status to 'refunded'. Show the refunded status in your order history so users can see it without contacting support.

### Is it safe to store Stripe payment intent IDs in Firestore?

Yes. Payment intent IDs (pi_xxx) and session IDs (cs_xxx) are not sensitive — they're identifiers, not credentials. Storing them in Firestore lets you cross-reference Firestore orders with Stripe Dashboard events for debugging and dispute resolution. Never store secret keys or full card details.

---

Source: https://www.rapidevelopers.com/flutterflow-tutorials/how-can-i-monitor-my-flutterflow-app-s-payments
© RapidDev — https://www.rapidevelopers.com/flutterflow-tutorials/how-can-i-monitor-my-flutterflow-app-s-payments
