ServeAI documentation
Run it, point it at your own accounts, ship it under your own brand. Written against version 0.1.0 of the download you get on CodeCanyon.
- Version v0.1.0
- Updated —
- Platform Flutter
- Stack Flutter · Next.js
On this page
Overview
A complete 3-in-1 package: Next.js backend + admin panel, plus two Flutter apps — one for customers, one for providers. Book, track, pay, chat, review. Built for home services, handyman, beauty, tutoring, or any location-based on-demand marketplace.
Live demo: serveai.devsnack.dev · Quick start: quick-start.html
ServeAI is a complete on-demand services platform sold as a single purchase. Inside the ZIP you get three tightly-integrated applications:
- Customer app (Flutter 3 · Riverpod 3) — Browse categorized services, get an AI-powered price estimate, pick packages and add-ons, attach any documents the service requires, book with a card / wallet / cash, watch the provider arrive in real time, chat, pay, review.
- Provider app (Flutter 3 · Riverpod 3) — Onboarding + document upload, real-time job feed, accept / decline, status transitions, live location broadcast, chat, request extra charges, track earnings, request payouts. Agency owners also invite workers and dispatch jobs.
- Admin panel + backend (Next.js 16 · Drizzle ORM) — Everything you need to run the platform: services / categories CRUD, bookings oversight, customer document review, provider approvals, agencies, coupons, earnings, payouts, feature flags, maintenance mode, audit log, admin notifications. All on the same Next.js server that powers the mobile API.
Use cases. The code is domain-agnostic — we ship it seeded with home services, but the same engine works for handyman, beauty & wellness, car detailing, tutoring, pet care, moving help, any location-based on-demand marketplace.
Two verticals, one codebase. Alongside the physical on-demand model, ServeAI supports digital service / cyber cafe businesses — passport and ID applications, tax registration, banking paperwork, online form filling. Turn on the documents_enabled flag and services can require customers to attach documents at booking time, which an admin then reviews. Same backend, same apps, different default content.
Architecture
One Next.js server handles both the admin panel and the mobile API, with Socket.IO attached for real-time updates. Postgres lives on Neon (serverless). Stripe handles payments behind an abstraction so you can add other providers later. Cloudflare R2 stores photos, provider credentials, and customer booking documents — always via short-lived presigned upload URLs, with a per-namespace role policy so a provider can’t write into customer document storage.
┌──────────────────────────┐
│ Neon Postgres │
│ (Drizzle ORM schema) │
└────────────┬─────────────┘
│
┌───────────────────┐ ┌─────────┴──────────┐ ┌──────────────────┐
│ Customer App │◄──►│ Next.js server │◄──►│ Admin Panel │
│ (Flutter) │ │ /api/* + /admin │ │ (Next.js UI) │
└───────┬───────────┘ │ + Socket.IO │ └──────────────────┘
│ └──────┬────┬────────┘
│ │ │
┌───────┴───────────┐ │ │ ┌──────────────────┐
│ Provider App │◄──────────┘ ├────────►│ Stripe Connect │
│ (Flutter) │ │ │ (webhooks) │
└───────────────────┘ │ └──────────────────┘
│
┌────────────┼────────────┐
│ │ │
┌────────▼──────┐ ┌───▼────┐ ┌────▼──────────┐
│ Firebase │ │ R2 │ │ AI provider │
│ Auth + FCM │ │ bucket │ │ (OpenAI / │
└───────────────┘ └────────┘ │ Gemini) │
└───────────────┘Why not next dev?
Socket.IO is attached to a custom HTTP server (web/server.ts), which is why the dev command is npx tsx server.ts, not next dev. If you start the app with Next’s built-in dev server you’ll get HTTP routes but no real-time.
Customer app features
Catalog & discovery
- Browse service categories
- Service detail with packages + add-ons
- Featured services carousel
- AI-powered price estimation (when AI enabled)
Booking wizard
- Package → schedule → address → review → confirm
- A documents step appears only for services that require one
- Coupon apply with live preview
- Pay by card (Stripe sheet), wallet, or cash
- Before/after photo uploads
Document upload
- Attach images or PDFs — ID proof, address proof, photo, signature, forms
- Mandatory documents block the wizard; optional ones don’t
- Review status per document, with the admin’s note when something’s wrong
- Re-upload in place when an admin requests a replacement
- Approved documents are frozen — they can’t be silently swapped out
Real-time tracking
- Provider location on a map while en route
- Status transitions push-notified
- In-booking chat with the provider
- Reschedule or cancel with refund
Account
- Wallet with top-up (Stripe) and transactions
- Referral codes
- Saved addresses
- Booking history + reviews
Provider app features
Onboarding
- Multi-step signup wizard
- ID + certification document upload to R2
- Service selection with custom pricing
- Weekly availability editor
- Status gated —
PENDING→APPROVEDby admin
Job flow
- Live job feed with push notifications
- Accept / decline from lock screen
- State machine:
ACCEPTED→EN_ROUTE→ARRIVED→IN_PROGRESS→COMPLETED - Live location broadcast while en route
- Request extra charges (customer approves)
- Read-only view of the customer’s approved documents
Earnings
- Per-booking earnings with commission + agency cut breakdown
- Wallet balance + history
- Payout requests (Stripe Connect transfer)
Agency mode
- Owners create an agency and invite workers by phone
- Dispatch incoming jobs to any approved worker
- Configurable owner-cut percentage
- Workers still accept/decline via the same job flow
Admin panel features
Catalog & pricing
- Categories (hierarchical)
- Services with R2 photo uploads
- Packages & add-ons editor
- Per-service required-documents editor (type, label, required/optional)
- Coupons (flat or percentage, usage limits, expiry)
Operations
- Bookings list + detail with full audit trail
- Document review — approve, reject, or request a re-upload with a note
- Outstanding-documents banner so you can chase what’s missing
- Providers directory + approve / suspend
- Agencies directory + owner-cut override
- Customers directory with wallet + booking history
Money
- Transactions ledger (bookings, refunds, commissions, payouts)
- Payout requests queue with approve / reject
- Earnings + revenue charts
- Configurable commission rate
Platform
- Feature flags (AI, chat, cash, wallet, documents) — instant toggle
- Maintenance mode with banner shown in both apps
- AI provider switch (OpenAI ↔ Gemini)
- Broadcast push notifications
- Reviews moderation with admin replies
- Email composer via Resend
Tech stack
Database schema
All tables live in web/lib/db/schema.ts as Drizzle definitions. Migrations are generated with drizzle-kit generate and applied with drizzle-kit migrate (automatically on Railway boot via the start:migrate script).
Core entities
SUPER_ADMIN, MANAGER)Catalog
parent_id supports subcategories)Bookings & money
PENDING / APPROVED / REJECTED / REUPLOAD_REQUESTED), the admin’s note, and who reviewed itMessaging & platform
Feature flags
Runtime flags live in the settings table and are exposed to the mobile apps via GET /api/config. The admin panel’s Settings page edits them live — no redeploy, no rebuild of the mobile apps. Server routes also enforce disabled flags (they return 400) so the apps can’t slip past with stale cached values.
Backend setup
Full setup is documented in README.md at the repo root. The highlights below cover what’s unique to each surface; follow the README for the exact commands in order.
cd web
npm install
cp ../.env.example ../.env.local # edit — see §Setup — env vars below
npx drizzle-kit migrate
npm run db:seed # demo data: admin, providers, customer, bookings
npx tsx server.ts # dev server on :3000 (NOT "next dev")Admin panel: http://localhost:3000/admin. Seeded admin: admin@serveai.dev / password123.
Neon Postgres
- 01Create a Neon project at neon.tech.
- 02Copy the pooled connection string from the dashboard.
- 03Paste into
DATABASE_URLin.env.local— make sure it ends with?sslmode=require.
Firebase setup
- 01Create a Firebase project (or one per app — customer and provider can share).
- 02Enable Authentication (Email/Password) and Cloud Messaging.
- 03For each Flutter app, run
flutterfire configure— this writesfirebase_options.dart, dropsgoogle-services.jsonon Android, andGoogleService-Info.pliston iOS. - 04Download a service account JSON (Project Settings → Service accounts → Generate new private key).
- 05
In
.env.local, fill:.env.localFIREBASE_PROJECT_ID=your-project-id FIREBASE_CLIENT_EMAIL=firebase-adminsdk-...@your-project.iam.gserviceaccount.com FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n" - 06Flip
MOCK_AUTH=falseonce the keys are in.
Payments (Stripe)
- 01Create a Stripe account and enable Connect (Express accounts).
- 02In
.env.local, setSTRIPE_SECRET_KEY,STRIPE_PUBLISHABLE_KEY,STRIPE_WEBHOOK_SECRET. - 03Flip
MOCK_PURCHASE=false. - 04Add a webhook in the Stripe Dashboard pointing at
https://<your-domain>/api/payments/webhook. - 05For the Flutter customer app, pass the publishable key at build time:
--dart-define=STRIPE_PUBLISHABLE_KEY=pk_live_...
PaymentProvider abstraction
All Stripe code is confined to web/lib/payments/stripe.ts. Everything else (routes, services, UI) goes through the PaymentProvider interface in provider.ts. To add PayPal, Razorpay, or local wallets later, implement the interface in a new file and register it in the factory in index.ts — no call-site changes.
AI providers
- 01Grab an API key from OpenAI or Google AI Studio (or both).
- 02Set
OPENAI_API_KEYand/orGEMINI_API_KEYin.env.local. - 03In admin panel → Settings, pick which provider to route AI calls to. No redeploy.
Storage (R2)
- 01Create a Cloudflare R2 bucket from the Cloudflare dashboard.
- 02Create an API token with object read/write on that bucket.
- 03Expose the bucket on a custom domain or use the r2.dev preview URL.
- 04
Fill the R2 block in
.env.local:.env.localR2_ACCOUNT_ID=... R2_ACCESS_KEY_ID=... R2_SECRET_ACCESS_KEY=... R2_BUCKET=... R2_PUBLIC_URL=https://cdn.your-domain.com
Google Maps key (no-commit workflow)
The key never needs to touch git. Pick one path per platform.
Android — three options, in order of preference
- 01
User-global gradle properties
Edit
~/.gradle/gradle.properties(Windows:%USERPROFILE%\.gradle\gradle.properties) and add one line:gradle.propertiesMAPS_API_KEY=AIzaSy...Every Flutter project on this machine, every build, picks it up. This is the recommended path — it works outside the repo entirely.
- 02
Environment variable
Good for CI/CD:
terminalexport MAPS_API_KEY=AIzaSy... flutter build appbundle --release - 03
Per-project
local.propertiesIn
customer_app/android/local.properties(and the provider equivalent) add:local.propertiesmaps.api.key=AIzaSy...local.propertiesis gitignored by Flutter defaults.
iOS
Each app ships a tracked template. Copy it once and fill in your key:
cp customer_app/ios/Flutter/Secrets.xcconfig.example customer_app/ios/Flutter/Secrets.xcconfig
cp provider_app/ios/Flutter/Secrets.xcconfig.example provider_app/ios/Flutter/Secrets.xcconfig
# edit each file → MAPS_API_KEY = AIzaSy...The real Secrets.xcconfig is gitignored. Debug.xcconfig and Release.xcconfig optional-include it, Info.plist reads $(MAPS_API_KEY) into the GMSApiKey entry, and AppDelegate.swift passes it to GMSServices.provideAPIKey.
Android release signing
By default, release builds fall back to debug keys so flutter run --release works without extra setup. Play Store rejects debug-signed AABs, so for your own submission you’ll generate an upload keystore and wire it via a gitignored key.properties file.
Upload key vs. app signing key
Since August 2021 Google enrolls every new app in Play App Signing — Google holds the actual app signing key and re-signs the APKs delivered to devices. Your keystore is the upload key: Play verifies your uploads with it but isn’t the same key users install with. Practical effect: if you lose your upload key you can reset it via a Google support ticket, but you still need to keep it safe.
1. Generate the upload keystore
One keystore per app. Run from each app’s android/ directory:
cd customer_app/android
keytool -genkey -v -keystore upload.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias uploadKeytool will ask for a store password, key password, and your name/org. Use strong passwords and save them somewhere durable (a password manager, not a Post-it).
Back up the keystore before building anything with it
If you lose the upload.jks file you can recover via Google’s reset flow (a few days’ turnaround). If you lose the passwords you may still recover. But the safer habit: back up the .jks and the passwords to an offline location — losing both means you publish updates under a new upload identity, which is extra friction you don’t want.
2. Create key.properties
In customer_app/android/key.properties (gitignored — the .gitignore already covers it):
storeFile=upload.jks
storePassword=your-store-password
keyAlias=upload
keyPassword=your-key-passwordRepeat for provider_app/android/ — a separate keystore, a separate key.properties. They’re different apps, so Play treats them as different listings.
3. Build the AAB
flutter build appbundle --release -PMAPS_API_KEY=AIzaSy...Once key.properties exists, android/app/build.gradle.kts picks it up automatically via the release signingConfig. Output: build/app/outputs/bundle/release/app-release.aab.
4. First Play Console upload
- 01Create a Google Play developer account ($25 one-time) at play.google.com/console.
- 02Create a new app. Use the exact
applicationIdfrombuild.gradle.kts(com.devsnack.serveai.customer/.provider, or whatever you renamed them to). - 03Fill out the store listing (title, short/full description, icon, feature graphic, screenshots, privacy policy URL).
- 04Complete the content rating questionnaire and data-safety declaration.
- 05Release → Production (or Internal testing for a quick test track) → Create new release → upload the
.aab. - 06Play enrolls the app in Play App Signing on first upload. Confirm the dialog.
- 07Submit for review. First review is 1–7 days.
5. Subsequent releases
Bump version in pubspec.yaml (e.g. 0.1.1+2), rebuild the AAB, upload to the same release track. Play handles the upgrade migration automatically.
iOS code signing
iOS signing needs one up-front Apple Developer Program enrollment, then mostly lives inside Xcode. The Flutter build system takes care of the rest — you’ll only touch signing manually if you set up CI/CD later.
1. Apple Developer Program
- 01Enroll at developer.apple.com/programs ($99/year). Individual or organization account — organization needs a D-U-N-S number and takes a few days.
- 02Once active, you get access to Certificates, Identifiers & Profiles, and to App Store Connect.
2. Register bundle IDs
Register each app’s bundle ID under Certificates, Identifiers & Profiles → Identifiers → App IDs:
com.devsnack.serveai.customer(or your rename)com.devsnack.serveai.provider(or your rename)
Enable the Push Notifications capability on both — FCM delivers iOS pushes through APNs.
3. Signing inside Xcode (automatic — recommended)
For each app:
- 01Open the Xcode workspace (not the
.xcodeprojdirectly):open ios/Runner.xcworkspace. - 02Select the Runner target → Signing & Capabilities tab.
- 03Tick Automatically manage signing.
- 04Pick your Team from the dropdown. Xcode creates (or picks up) a development certificate and provisioning profile for that team + bundle ID.
- 05Confirm Bundle Identifier matches the ID you registered in Apple Developer.
- 06Add the Push Notifications capability from the “+ Capability” button.
Repeat for the provider app (separate Xcode workspace, separate bundle ID).
4. APNs auth key for FCM pushes
FCM needs to talk to APNs on your behalf. A single APNs auth key covers all your apps across both development and production, so you only do this once per Apple developer account.
- 01Apple Developer → Certificates, Identifiers & Profiles → Keys → +.
- 02Name it “ServeAI APNs”, tick Apple Push Notifications service (APNs), Continue, Register.
- 03Download the generated
.p8file. You can only download it once — save it somewhere durable. - 04Note the Key ID shown on screen and your Team ID (top-right of the developer dashboard).
- 05Firebase Console → Project settings → Cloud Messaging tab → under the iOS section, upload the
.p8file and paste the Key ID + Team ID.
5. Build and archive
From the Flutter project root:
cd customer_app
flutter build ipa --release \
--dart-define=API_BASE_URL=https://your-backend.up.railway.app \
--dart-define=STRIPE_PUBLISHABLE_KEY=pk_live_...Output: build/ios/ipa/customer.ipa. For submission, open the generated archive in Xcode:
open build/ios/archive/Runner.xcarchiveXcode’s Organizer opens. Click Distribute App → App Store Connect → Upload. Xcode validates, signs for distribution using your team’s distribution certificate, and uploads to App Store Connect.
6. TestFlight & App Store
- 01Open App Store Connect.
- 02My Apps → + → create a new app using the bundle ID you registered.
- 03Once the upload finishes processing (10–30 min), it appears under TestFlight. Add internal testers immediately; external testers need a quick Beta App Review.
- 04For a production release: App Store tab → fill metadata, screenshots (required sizes: 6.9", 6.5", 5.5" for iPhone; 13" for iPad), submit for App Review. First review is 1–3 days.
7. Subsequent releases
Bump the version in pubspec.yaml, rebuild the IPA, upload — App Store Connect keeps the history per version.
Manual signing / CI-CD
Automatic signing works for one developer on one Mac. If you set up CI (fastlane, Codemagic, Xcode Cloud), switch to manual signing and export your distribution certificate as a .p12 plus the matching provisioning profile. Load both into the CI environment as secrets, and point Xcode at them via CODE_SIGN_IDENTITY and PROVISIONING_PROFILE_SPECIFIER build settings.
Run the mobile apps
# Customer app
cd customer_app
flutter pub get
flutter run \
--dart-define=API_BASE_URL=http://10.0.2.2:3000 \
--dart-define=STRIPE_PUBLISHABLE_KEY=pk_test_xxx
# Provider app (same flags, separate emulator/device)
cd ../provider_app
flutter pub get
flutter run --dart-define=API_BASE_URL=http://10.0.2.2:3000On Android emulator, 10.0.2.2 is the host. On iOS simulator, use localhost or your LAN IP.
Deployment
Full step-by-step Railway recipe is in section 6. Deploy to Railway of the project README.md — TL;DR: push to GitHub, point Railway at the repo, paste env vars, done. Drizzle migrations run automatically on boot via the start:migrate script.
Customization
Branding
customer_app/lib/core/theme/*, provider_app/lib/core/theme/*, web/app/globals.cssassets/brand/ in each Flutter app, web/public/brand/assets/icon/ in each Flutter app (regenerate with dart run flutter_launcher_icons)flutter_native_splash section in pubspec.yaml (regenerate with dart run flutter_native_splash:create)android:label in each AndroidManifest.xml, CFBundleDisplayName in each Info.plistapplicationId in android/app/build.gradle.kts, PRODUCT_BUNDLE_IDENTIFIER in XcodePlatform behavior
- Commission rate — admin panel → Settings. Falls back to
PLATFORM_COMMISSION_RATEenv var (default 0.15). - Agency owner cut — per-agency in the admin panel; default 0.1 (10%).
- Service minimum order — per-coupon
min_order_valuecolumn. - Feature flags — admin panel → Settings (see Feature flags table above).
Adding a payment provider
- 01Create
web/lib/payments/<name>.tsexporting a class that implements thePaymentProviderinterface inprovider.ts. - 02Register it in
web/lib/payments/index.ts(the factory returns the active provider based on a setting). - 03Add the provider key to the admin panel’s Settings page.
- 04No call-site changes — every route and service imports from
lib/payments/index.ts, never directly from Stripe.
Adding a language
English ships. The stack is wired for i18n from day one:
- Web — add an ARB equivalent under
web/messages/, register innext-intlconfig. - Customer app — add an ARB file under
customer_app/lib/l10n/, regenerate withflutter gen-l10n. - Provider app — same pattern under
provider_app/lib/l10n/.
Production checklist
Before flipping your live site from demo to production:
MOCK_AUTH, MOCK_PURCHASE, MOCK_SUBSCRIBE to falseFIREBASE_* in Railway envSTRIPE_* in Railway env~/.gradle/gradle.properties + ios/Flutter/Secrets.xcconfig{customer,provider}_app/android/key.properties/api/payments/webhookRotate the demo Maps key
If you used the seller’s demo Google Maps key while evaluating, rotate your own at console.cloud.google.com before shipping — shared keys get throttled or banned.
Changelog
Every version published so far. Updates are free for the life of the item.
v0.2.0 — Customer documents & dependency refresh
- Customer document upload. Services can require documents (ID proof, address proof, photo, signature, form, other); customers attach them during booking; admins approve, reject, or request a re-upload with a note. Off by default behind
documents_enabled. - New
booking_documentsandservice_required_documentstables, plus a customer-onlybooking-documentsR2 namespace. - Providers see approved documents read-only; approved documents can’t be superseded.
- The booking wizard’s step list is now derived from the service instead of a fixed five steps.
- Dependencies brought up to date across all three apps — Riverpod 3, GoRouter 17, Firebase 4/6/16, flutter_stripe 14, flutter_local_notifications 22, flutter_secure_storage 11, ESLint 10, AI SDK 7, TanStack Table 9, firebase-admin 14.
- Removed an unused code-generation stack (Freezed, json_serializable, Retrofit, build_runner) — models were already hand-written.
- Android
compileSdkraised to 37; Dart SDK floor is now 3.12.
v0.1.0 — Initial release
- Next.js 16 backend + Drizzle ORM on Neon
- Customer + Provider Flutter apps (Riverpod 2)
- Admin panel with 20 pages (catalog, operations, money, platform)
- Stripe Connect payments with mock mode for dev
- Real-time tracking, chat, and status updates over Socket.IO
- FCM push notifications + notification inbox
- AI price estimation with OpenAI / Gemini switch
- Agency feature — owner invites workers, configurable commission split
- Wallet + coupon + referral systems
- Runtime feature flags + maintenance mode banner
- 270+ automated tests (backend + both mobile apps)
Support and licensing
Every purchase includes 6 months of support for setup questions and bug fixes. Custom feature work quoted separately.
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