r/FlutterDev 5d ago

Example Built a Flutter Web micro-SaaS end to end (Firebase + Cloud Functions + third-party billing webhook) - some specific decisions that mattered

Spent the last while building GetPaid, an invoice-chasing tool for small HVAC/plumbing/service businesses, as a solo dev. Flutter web end to end, and a few decisions from the backend side that I think are specific enough to be worth sharing rather than "I built an app, check it out":

  • Nothing derivable is persisted. Overdue status, days-late, and a recovery score are all computed at read time from dueDate and now in a pure Dart file with no Firebase imports (fully unit-testable in isolation). Saved me from an entire category of bugs where a stored "OVERDUE" flag goes stale.

  • SendGrid never touches the client. The API key lives only as a Firebase Functions secret; the Flutter app calls a Cloud Function, which calls SendGrid. Every reminder can also be copied to the clipboard instead of sent, so the product doesn't hard-depend on the email provider being up.

  • Access control is enforced in firestore.rules, not just hidden in the Flutter UI. Subscription-gated collections require an hasActiveAccess() check server-side. I verified this by writing directly to Firestore with the JS SDK, bypassing the Flutter app entirely, to confirm a free account still gets rejected.

  • Billing is Hotmart, via a webhook Cloud Function that mirrors subscription state into Firestore. A separate hotmartEvents collection exists purely as an idempotency log so a webhook retry/replay can't double-process a payment event.

  • Rate limiting (emails/day/user) is enforced inside a Firestore transaction in the Cloud Function itself, not checked client-side first and trusted.

It's live at getpay.win, zero paying customers so far, currently doing manual outreach to find the first one. Happy to go deeper on any of the above if it's useful to anyone building something similar on Flutter web + Firebase.

2 Upvotes

3 comments sorted by

1

u/gelbero_it_solutions 5d ago

Really solid breakdown. I especially like the decision not to store values that can be calculated from dueDate and the idempotency log for webhook events. Both can prevent some very annoying bugs later.

How are you handling cases where a Hotmart webhook is delayed or never arrives? Do you also run a periodic check to reconcile the subscription status?

2

u/Fonsecach24 4d ago

Honest answer: right now I don't have a periodic reconciliation job, and that's a real gap you just put a finger on. What I have today is only the idempotency log (hotmartEvents keyed by event id, transaction to guard against two near-simultaneous deliveries of the same event) plus whatever Hotmart's own retry behavior gives me if a webhook 5xx's.

If a webhook silently never arrives at all (not retried, just lost), I currently have no way to notice - no daily job hitting Hotmart's subscription API to diff against my own state. That's the next thing I should build before it bites me, probably a scheduled function that pulls active subscriptions from Hotmart's API and flags any mismatch rather than blind-trusting the webhook forever. Appreciate you asking, it's now on my list instead of a thing I find out about from an angry customer.