StayAI documentation
Run it, point it at your own accounts, ship it under your own brand. Written against version 1.0.0 of the download you get on CodeCanyon.
- Version v1.0.0
- Updated —
- Platform Flutter
- Stack Flutter · Firebase
On this page
Overview
AI-powered hotel booking & discovery for Flutter — a 3-in-1 bundle: customer app, admin panel, and Cloud Functions backend.
StayAI is a premium Flutter source-code product for buyers who want to launch a branded hotel booking and discovery platform on iOS, Android, and the web. Revenue is generated through affiliate commissions from Booking.com, Travelpayouts, and optionally Hotelbeds — meaning buyers can monetize on day one with zero inventory, zero payment processing, and zero airline/hotel contracts.
The 3-in-1 bundle
- Customer app — Flutter for iPhone (App Store) and Android (Play Store) from a single codebase.
- Admin panel — Flutter Web dashboard for content curation, user management, push composer, and AI configuration.
- Cloud backend — Firebase project with Auth, Firestore, Cloud Functions (Node 22), Storage, Remote Config, FCM, Analytics, Crashlytics, and App Check.
Why StayAI wins
- AI Travel Intelligence — conversational travel assistant, AI-powered hotel recommendations, AI review summaries (OpenAI + Gemini dual provider with Remote-Config-driven failover).
- Accessible affiliate monetization — buyers register for Booking.com Affiliate and Travelpayouts (both free), drop in their tokens, and start earning.
- Firebase-native architecture — zero hosting burden; free tier handles 10,000+ MAU.
TARGETED_DEVICE_FAMILY = "1". iPad listings on App Store Connect are not supported. App Store reviewers expect the listing to match.What’s included
- Complete Flutter source for the customer app and admin panel.
- Cloud Functions source (TypeScript, Node 22) — 18+ callables, scheduled jobs, and Firestore triggers.
- Firebase configuration: Firestore rules + composite indexes, Storage rules.
- Brand-asset generator: a small Dart tool that emits a launcher icon and splash logo from a single brand color + monogram letter.
- Legal templates — Privacy Policy and Terms of Service markdown ready for buyer review and hosting.
- Deep-links setup guide (AASA + assetlinks.json) under
docs/deep-links-setup.md. - This documentation (HTML + markdown listing copy).
- Six months of support + lifetime updates.
Features
Customer app
Admin panel
Backend (Cloud Functions)
blog on draft → published transition.aiPlan.aiReviewSummaries.notificationPrefs filtering; supports immediate or scheduled send.analyticsRollups/{yyyymmdd}.priceSnapshot vs hotel priceFromUsd; FCM on ≥8% drop.tripDates.notificationBroadcasts queued by the admin composer.Polish
- Dark mode with persisted user preference + dark Google Maps style.
- Hero transitions on hotel cards; fade-and-slide staggered list entrance.
- WCAG AA pass — 48dp tap targets, semantic labels on icon-only buttons, no color-only state communication.
- iOS APNS-token race fixed; Firebase Analytics bool-coercion guard.
Scaffolded but not wired in v1
The schema accommodates the items below but they don’t ship as feature code. Buyers wanting these should treat them as v1.1 work.
- Phone OTP auth (PRD §7.2).
- Referral program (schema fields exist; no UI).
- In-app notification history feed (push delivers; no inbox screen).
- Web push (Flutter Web is a no-op for FCM in v1).
- Compare view (4 hotels side-by-side) — N/A given admin-curated inventory.
- Localization beyond English (the
intlscaffolding is in place; only English copy is filled). - Lottie empty states — Material icons used instead.
Architecture
Tech stack
Monorepo layout
apps/
customer/ Flutter iPhone + Android
admin/ Flutter Web dashboard
packages/
core/ Pure-Dart models + validation
data/ Repositories + typed Function clients
ui/ Design tokens + shared widgets
firebase_services/ FlutterFire abstractions
functions/ Cloud Functions (TypeScript, Node 22)
firebase/
firestore.rules
firestore.indexes.json
storage.rules
hosting/
.well-known/ AASA + assetlinks templates
scripts/ Branding + license generators
docs/
codecanyon/ This documentation
legal/ Privacy + Terms templates
deep-links-setup.md Universal/App Links buyer guidePer-app folder convention (feature-first MVVM)
apps/customer/lib/
app.dart Root widget + system gates
router.dart GoRouter configuration
features/
onboarding/
auth/
home/
search/
hotel_detail/
favorites/
ai_assistant/
profile/
settings/
system/ Force-update + maintenance gates
router/
deep_links.dart Unified Universal-/App-Link/FCM tap pipe
l10n/ ARB files (EN populated)Firestore collections and indexes
tripDates.featuredHotels.priceFromUsd drives the price-drop watcher.Composite indexes
Six composite indexes ship in firebase/firestore.indexes.json: featured destinations / hotels / promotions ordered by active + display order; favorite items by folderId + addedAt; published blogs ordered by publishedAt; users by disabled + createdAt; notification broadcasts by status + sendAt for the dispatcher cron.
Firebase setup
- 01Create a Firebase project at
console.firebase.google.com. - 02Enable: Authentication, Firestore, Cloud Functions, Storage, Remote Config, Cloud Messaging, Analytics, Crashlytics, App Check.
- 03Enable Auth providers: Email/Password, Google, Apple, Anonymous.
- 04From
apps/customer/runflutterfire configureagainst your project; repeat inapps/admin/. - 05
Fill
functions/.envusing the.env.exampletemplate:functions/.envOPENAI_API_KEY=... GEMINI_API_KEY=... BOOKING_AFFILIATE_ID=... TRAVELPAYOUTS_TOKEN=... TRAVELPAYOUTS_MARKER=... AI_FREE_DAILY_LIMIT=10 AI_PRO_DAILY_LIMIT=200 - 06
Deploy backend:
terminalfirebase deploy --only firestore:rules,firestore:indexes,functions,storage:rulesFirst Cloud Functions deploy may fail with an Eventarc 400; wait 5–10 minutes and re-run.
- 07Promote yourself to admin: edit
users/{your-uid}.role = 'admin'in the Firebase console. Reload the admin app; you should see the full sidebar.
App Check
App Check posture differs by endpoint:
- AI endpoint —
functions/src/ai/generate_ai_assistant_reply.tsships withenforceAppCheck: true. The streaming chat burns OpenAI / Gemini budget on every call, so it must reject unattested clients out of the box. You cannot deploy until App Check providers are registered or legitimate calls will be rejected. - Open callables —
functions/src/common/callable.tsdefaults toenforceAppCheck: false. These callables (hotel search, lookup, blog reads,logAffiliateClick) are designed to work for guest/anonymous users before sign-in and don’t spend rate-limited budget. Flip the default totrueif you want defence-in-depth on every callable.
firebase deploy --only functions.Affiliate setup
booking.com/affiliate-programBOOKING_AFFILIATE_ID in functions/.env + Firebase Remote Config booking_com_affiliate_id for the client to embed in deep links.travelpayouts.comTRAVELPAYOUTS_TOKEN + TRAVELPAYOUTS_MARKER in functions/.env.HOTELBEDS_API_KEY + HOTELBEDS_SECRET. Gated behind an admin toggle.packages/core/lib/src/legal/disclosures.dart — edit it in one place and both surfaces update.AI provider setup
Two providers ship behind a common AIProvider abstraction. Active provider per feature is selected by Firebase Remote Config so admins can hot-swap without a deploy:
Per-feature rate limits
AI_FREE_DAILY_LIMIT env varAI_PRO_DAILY_LIMIT env varEnforcement runs in functions/src/ai/shared/rate_limit.ts. The function reads users/{uid}.aiPlan, picks the matching limit, and increments users/{uid}/aiUsage/{yyyymmdd}.count atomically. Over-limit calls throw resource-exhausted; the Dart client surfaces this as AiException.quotaExceeded.
Cost guidance
At gpt-4o-mini rates and average 600-token round-trips, 200 queries/day costs roughly $0.20 per Pro user / month. A Pro subscription priced at $4.99/month recovers that with margin.
In-app purchase (Pro)
The Pro upgrade ships as a working demo via MockPurchaseService: tapping Upgrade flips users/{uid}.aiPlan directly so you can show the flow end-to-end without a RevenueCat account, App Store Connect setup, or Play Console setup.
Activation steps
- 01Create a RevenueCat account at
app.revenuecat.com. - 02Add an iOS + Android app under one project.
- 03Create a
proentitlement and an offering with a single monthly product, idstayai_pro_monthly. - 04Configure App Store Connect + Google Play Console with the same product id.
- 05
Add to
apps/customer/pubspec.yaml:apps/customer/pubspec.yamldependencies: purchases_flutter: ^8.0.0 - 06Implement
packages/firebase_services/lib/src/purchases/revenuecat_purchase_service.dart— the file is stubbed with the exactPurchasesSDK calls in comments. - 07
Flip the toggle in
packages/firebase_services/lib/src/purchases/purchase_service.dart:purchase_service.dartconst bool kEnableRevenueCat = true; - 08Add a Cloud Function on the RevenueCat webhook (
revenuecat.com/docs/webhooks) that flipsusers/{uid}.aiPlanonINITIAL_PURCHASE,RENEWAL,CANCELLATION, andEXPIRATIONevents.
Notifications
iOS prerequisites
- 01Generate an APNs Auth Key in App Store Connect → Keys.
- 02Upload the
.p8+ Key ID + Team ID to Firebase Console → Project settings → Cloud Messaging. - 03Run the customer app on a real iPhone — APNs tokens never arrive on simulators.
Topics and targets
dealsblogonBlogPublish triggercheckPriceDrops cronsendTripReminders cronPer-user opt-in
The user doc carries notificationPrefs.<type> (deals, priceDrops, blog, tripReminders). The customer Settings → Notification preferences screen toggles each. sendPushNotification filters on notificationPrefs[type] !== false; missing or true means opted-in.
Deep links
Every supported deep link funnels through apps/customer/lib/router/deep_links.dart, which exposes a single pendingDeepLinkProvider. Three sources feed it:
- iOS Universal Links / Android App Links (via the
app_linkspackage). - Custom-scheme taps (
stayai://hotel/<id>). - FCM notification taps (the messaging service emits the
data.deepLinkpayload through the same pipe).
Supported URL shapes
docs/deep-links-setup.md for the AASA + assetlinks deployment steps.Branding and customization
Theme tokens (one-file rebrand)
Edit packages/ui/lib/src/theme/tokens.dart — every color, radius, spacing unit, and elevation flows from this file. Hot-restart the apps and every screen updates.
Launcher icon
- 01Drop a 1024×1024 RGBA PNG (transparent background, mark with ≥10% safe-area padding) at
apps/<app>/assets/branding/launcher_icon.png. - 02
From
apps/<app>/:terminalflutter pub get dart run flutter_launcher_icons - 03Commit the regenerated platform files.
Splash screen
- 01Drop a 512×512 RGBA PNG at
apps/customer/assets/branding/splash_logo.png. - 02Optionally edit the
flutter_native_splash:block inapps/customer/pubspec.yaml(background color etc.). - 03Run
dart run flutter_native_splash:create.
App name and bundle id
apps/customer/ios/Runner.xcodeproj/project.pbxproj (search PRODUCT_BUNDLE_IDENTIFIER)apps/customer/android/app/build.gradle.ktsapps/customer/ios/Runner/Info.plist → CFBundleDisplayNameapps/customer/android/app/src/main/AndroidManifest.xml → android:labelapps/customer/web/manifest.json + index.html <title>Adding a language
The customer app uses flutter_localizations + intl with ARB files in apps/customer/lib/l10n/. PRD §7.1 promises 10 languages; v1 ships English populated. To add a locale, copy intl_en.arb to intl_<code>.arb, translate the values, then add the locale to the onboarding language selector (features/onboarding/language_selector_screen.dart).
Production checklist
- 01Replace
assets/branding/launcher_icon.png+splash_logo.pngwith your artwork. - 02Edit
packages/ui/lib/src/theme/tokens.dartbrand colors. - 03From
apps/customer/:flutter pub get→dart run flutter_launcher_icons→dart run flutter_native_splash:create. Repeat inapps/admin/(skip splash for admin). - 04
flutterfire configureagainst your Firebase project for both apps. - 05Fill
apps/customer/.env+functions/.env. - 06terminal
firebase deploy --only firestore:rules,firestore:indexes,functions,storage:rules - 07
Build & deploy admin Web:
terminalcd apps/admin && flutter build web --release && cd ../.. firebase deploy --only hosting:admin - 08Promote yourself:
users/{your-uid}.role = 'admin'in Firestore. - 09Seed initial featured destinations + hotels via the admin panel. Set each hotel’s Reference price (USD/night) if you want price-drop alerts to fire.
- 10Register App Check providers (DeviceCheck / Play Integrity / reCAPTCHA Enterprise) — required, because the AI endpoint enforces App Check by default. Optionally flip
enforceAppCheck: trueinfunctions/src/common/callable.tsto tighten the open callables too. Register debug tokens for your simulators. - 11Render
docs/legal/privacy_policy.md+terms_of_service.mdto HTML and host them. Update URLs inapps/customer/lib/features/profile/about_screen.dart. - 12(Optional) Activate RevenueCat per In-app purchase (Pro) above.
- 13(Optional) Wire AASA + assetlinks per
docs/deep-links-setup.mdfor Universal/App Links. - 14Generate
LICENSES.md:bash scripts/generate_licenses.sh. - 15
Build release artifacts:
terminal# Android flutter build appbundle --release # AAB for Play Console flutter build apk --release # APK for ad-hoc distribution # iOS — open Xcode, archive, upload to App Store Connect flutter build ios --release
Code signing and store submission
Android — release signing
Flutter ships with a debug keystore that won’t be accepted by the Play Console. Generate a release keystore once, then wire it into the Gradle build.
- 01
From
apps/customer/android/:terminalkeytool -genkey -v -keystore ~/keys/stayai-release.jks \ -keyalg RSA -keysize 2048 -validity 10000 \ -alias stayaiPick a strong password and store the keystore in a backup-safe location outside the repo — losing it means losing the ability to publish updates to the same Play listing.
- 02
Create
apps/customer/android/key.properties(gitignored — confirm it appears in.gitignore):apps/customer/android/key.propertiesstoreFile=/Users/<you>/keys/stayai-release.jks storePassword=<keystore-password> keyAlias=stayai keyPassword=<key-password> - 03
Edit
apps/customer/android/app/build.gradle.kts— load the properties at the top of the file and reference them in thesigningConfigsblock. Then pointbuildTypes.releaseat the new config:apps/customer/android/app/build.gradle.ktsval keystoreProperties = Properties().apply { val f = rootProject.file("key.properties") if (f.exists()) f.inputStream().use { load(it) } } android { signingConfigs { create("release") { keyAlias = keystoreProperties["keyAlias"] as String keyPassword = keystoreProperties["keyPassword"] as String storeFile = file(keystoreProperties["storeFile"] as String) storePassword = keystoreProperties["storePassword"] as String } } buildTypes { release { signingConfig = signingConfigs.getByName("release") } } } - 04
Build a signed bundle for the Play Console:
terminalflutter build appbundle --releaseThe signed AAB lands at
build/app/outputs/bundle/release/app-release.aab. - 05
Get your release SHA-256 fingerprint (used for App Links
assetlinks.jsonand Google Sign-In):terminalkeytool -list -v -keystore ~/keys/stayai-release.jks -alias stayai \ | grep SHA256Add the fingerprint to Firebase Console → Project settings → Your Android app → Add fingerprint. Re-download
google-services.jsonafter. - 06Play Console → Create app → upload the AAB. Fill the Data Safety form, content rating, and pricing. Internal testing first; promote to production once you have ≥ 12 testers and a clean review.
firebase_app_check SDK once you enable Play Integrity on the Firebase Console; no extra Android code.iOS — Apple signing and Xcode archive
- 01Apple Developer Program enrollment ($99/yr). The customer bundle id
com.devsnack.stayaimust be registered as an App ID under your team. Also enable: Sign In with Apple, Push Notifications, Associated Domains. - 02Create an App Store Connect app record matching the same bundle id. Submit a privacy policy URL (the rendered
docs/legal/privacy_policy.md) under App Information. - 03
In Xcode, open
apps/customer/ios/Runner.xcworkspace(always the workspace, not the project). Runner target → Signing & Capabilities:- Team — your Apple Developer team.
- Bundle Identifier —
com.devsnack.stayai. - Toggle Automatically manage signing on for development; switch to manual + an App Store distribution provisioning profile for the archive build.
- Capabilities — confirm Push Notifications, Sign in with Apple, and Associated Domains (
applinks:<your-domain>) are present.
- 04Generate an APNs Auth Key in App Store Connect → Keys → “+”. Download the
.p8file (one-time download). Upload it + the Key ID + your Team ID to Firebase Console → Project settings → Cloud Messaging → Apple app → APNs Authentication Key. This unlocks production push delivery. - 05
Build for upload:
terminalflutter build ipa --releaseor in Xcode: Product → Archive. Pick the archive → Distribute App → App Store Connect → Upload.
- 06App Store Connect → TestFlight → wait for processing (≈ 30 min) → invite testers. Submit the same build for App Review once TestFlight passes your sanity checks.
Changelog
Every version published so far. Updates are free for the life of the item.
Version 1.1.0 · August 2026Feature update
Added — bulk hotel import (CSV)
- New Import CSV and Export CSV actions on the admin Hotels screen. Load a whole catalog from a spreadsheet instead of entering hotels one at a time.
- Downloadable template with every supported column pre-filled.
- Headers are matched by name, not position, and are case- and punctuation-insensitive —
Booking Hotel ID,booking_hotel_id, andbookingHotelIdall resolve to the same field. Unrecognised columns are reported back rather than silently dropped. - Row-by-row preview before anything is written, showing per-line New / Update / Skipped status and the exact validation error with its source line number. Invalid rows are skipped; the rest of the file still imports.
- Blank
idcreates a hotel; a populatedidupdates one — so export → edit in Excel or Sheets → re-import round-trips without duplicating your catalog. - Writes are batched at 450 documents per commit, with live progress, so multi-thousand-row files import in one pass.
- Exports carry a UTF-8 BOM so accented hotel names open correctly in Excel on Windows.
Added — one-command seeding
node scripts/seed_all.mjsnow populates roughly 250 demo documents across all 13 collections in dependency order.--with-authalso creates sign-in-able demo accounts;--wiperemoves only what the seeders wrote.
Changed — dependencies
- Upgraded across the board to current majors: Riverpod 3.4, GoRouter 17.5, Freezed 3.2 + json_serializable 6.14, firebase_core 4.13, app_links 7.2, google_maps_flutter 2.18, and firebase-admin 14.2 / firebase-functions 7.3 on Node 22.
Version 1.0.0+1 · April 2026Initial release
Customer app
- Onboarding (splash + walkthrough + language & currency).
- Auth: email/password, Google, Apple Sign-In (iPhone), anonymous.
- Home with curated rails, AI Recommended-for-you carousel.
- Hotel detail with Hero-paired gallery, AI review summary, Plan-Trip date picker, favorite, share, Booking.com deep link.
- Favorites with folders, swipe-to-delete + undo.
- AI Travel Assistant with streaming responses + 20-message memory.
- Map view with brand-tinted dark Google Maps style.
- Settings: theme override (Auto/Light/Dark) with persistence, notification preferences, account deletion (Apple 5.1.1 compliant).
- StayAI Pro paywall — mocked by default, RevenueCat-ready stub.
Admin panel
- Dashboard with KPI tiles + recent affiliate clicks.
- CRUD for hotels (with reference price), destinations, promotions.
- User management with paginated table, role + disable + delete.
- AI console with system prompt editor + model picker + playground.
- Push composer (immediate or scheduled) + live broadcast log.
- App settings editor (force update, maintenance, AI rate limits).
Backend
- 18+ Cloud Functions: callables (search, AI, push,
deleteOwnAccount,mergeAnonymousAccount), scheduled crons (price drops, trip reminders, push dispatcher, analytics rollup), Firestore triggers (onUserCreate,onBlogPublish). - Production-grade Firestore rules + 7 composite indexes.
- Storage rules with admin-only writes on featured content.
Submission and compliance
- Force-update gate, maintenance-mode gate, COPPA 13+ age gate, FTC affiliate disclosure modal, GDPR self-delete, anonymous-account merge.
- iPhone-only target (
TARGETED_DEVICE_FAMILY = "1").
Polish
- Dark mode with persisted user preference, dark map style.
- Hero transitions, staggered list entrance.
- WCAG AA pass — 48dp tap targets, semantic labels, no color-only state.
- Three production crash fixes folded in (APNS race, Analytics bool coercion, modal Navigator timing).
Lifetime free updates ship through CodeCanyon. Major releases (v1.x → v2) will keep upgrade notes in this section.
Support and licensing
Support covers setup help, bug reports, deployment troubleshooting, and clarifications about what the source does and how to extend it.
Response SLA: within 48 hours, business days (Mon–Fri, GMT+7).
Term: six months from the date of purchase, per CodeCanyon standard. Lifetime free updates regardless of support window.
Out of scope: custom feature work, third-party API account issues (Booking.com, Travelpayouts, OpenAI), Apple/Google review disputes, hosting administration on the buyer’s Firebase project.
What support covers
- Six months of support from purchase, extendable at checkout
- Support covers bugs in the template and questions about how it is put together
- It does not cover custom feature work, third-party API changes or store review outcomes
- The Regular licence covers one free end product. Charging users for the app itself needs the Extended licence
Sign in with Apple and Google
Sign in with Apple — special configuration
Apple Sign-In on iOS requires two places to match:
Sign In with Applecapability is added (the entitlement file already declares it).Sign in with Google — Android signing fingerprint
Google Sign-In on Android cross-checks the calling app’s signing certificate. After you set up the release keystore (above), grab both SHA-1 and SHA-256 fingerprints:
Drop both into Firebase Console → Project settings → Your Android app → Add fingerprint. Re-download
google-services.jsonand replaceapps/customer/android/app/google-services.json. Without this step, the production build silently fails Google Sign-In with an internal error.