Docs
Home
  • Overview
  • Web installer
  • Production deployment
  • aaPanel
  • aaPanel quick reference
  • Storefront & customers
  • Admin
  • eSIM providers
  • AI assistant
  • Flutter app
  • Mobile API
Home
  • Overview
  • Web installer
  • Production deployment
  • aaPanel
  • aaPanel quick reference
  • Storefront & customers
  • Admin
  • eSIM providers
  • AI assistant
  • Flutter app
  • Mobile API
  • Setup & deployment

    • Setup & deployment
    • Web installer
    • Production deployment overview
    • Deployment on aaPanel
    • aaPanel quick reference (esimScan)
  • Guides

    • Storefront & customers
    • Admin
    • eSIM provider configuration
    • AI assistant
  • Mobile app

    • Mobile app (Flutter)
    • Mobile API (/api/v1)

Mobile app (Flutter)

esimScan has a companion customer mobile app built with Flutter — one codebase for Android and iOS. Buyers browse destinations, get AI plan recommendations, pay through your existing Stripe/PayPal checkout, install the eSIM by QR, and manage their orders, support tickets and account from the phone.

Sold separately

The mobile app is a separate CodeCanyon item (eSimScan App — Flutter eSIM Store App). This guide covers configuring, building and publishing it. The app is a client for the store you already run — the mobile API it talks to ships with esimScan itself, so there is no backend work to do.

What the app does

AreaScreens
First runIntro carousel, sign in, register, forgot password
BrowseHome (popular packages, complete plans), destination search, destination detail with every plan
BuyOpens your existing web checkout in a secure in-app browser
My eSIMsPurchased eSIMs, QR install, manual SM-DP+ / activation code, top-ups, live status
AI AdvisorFree-text trip description → plans from your own catalog
SupportTicket list, threaded replies, new ticket
AccountProfile, edit details, change password, order history, delete account
SettingsLight / dark / system theme, terms & privacy links

Requirements

  • Flutter SDK 3.44+ and Dart 3.12+
  • Android: Android Studio + SDK — builds for Android 6.0+ (minSdk 23)
  • iOS: macOS with Xcode 15+ — builds for iOS 13.0+
  • A running esimScan store on a public HTTPS URL
  • Apple Developer and/or Google Play accounts to publish

1. Prepare the backend

The mobile API is part of esimScan and enabled by default under /api/v1. It authenticates with Laravel Sanctum bearer tokens issued to a CustomerAccount (the same storefront buyer account).

Run migrations once so the token table exists:

php artisan migrate

Check it responds:

curl https://yourstore.com/api/v1/health
# {"ok":true,"service":"esimscan-mobile-api","version":"v1"}

If that returns 404, your web server is not passing /api/* through to Laravel — see Troubleshooting.

2. Point the app at your store

The API base URL is a compile-time define, so you never edit code to re-target the app:

flutter pub get

flutter run --dart-define=API_BASE_URL=https://yourstore.com/api/v1
TargetAPI_BASE_URL
Android emulator → your machinehttp://10.0.2.2:8000/api/v1 (with php artisan serve)
iOS simulator → your machinehttp://127.0.0.1:8000/api/v1
Physical device on the same Wi-Fihttp://<your-LAN-ip>:8000/api/v1
Local HTTPS with a self-signed certhttps://esimscan.test/api/v1 plus --dart-define=ALLOW_BAD_CERTS=true
Productionhttps://yourstore.com/api/v1

Never ship ALLOW_BAD_CERTS

It disables TLS certificate validation and exists only so you can test against a local self-signed certificate. Leave it out of every build you distribute.

The app also fetches /api/v1/config on launch for the store name, terms and privacy URLs and feature flags — so those follow your store without a rebuild.

3. Rebrand it

Everything visual lives in two places.

Colours and type — lib/core/theme/app_theme.dart:

static const Color brand      = Color(0xFF1A62E8); // primary
static const Color brandDark  = Color(0xFF0E3374); // navy
static const Color brandLight = Color(0xFF4487F3);
static const Color accent     = Color(0xFFFFC107); // highlight

Logo, icon and splash — replace the files in assets/images/ (icon.png, logo_black.png, logo_white.png), then regenerate:

dart run flutter_launcher_icons
dart run flutter_native_splash:create

Rename the app itself in pubspec.yaml (name, description), android/app/build.gradle.kts (applicationId), and the iOS bundle identifier in Xcode. The default identifier is com.esimscan.app.

4. Build for release

# Android — Play Store
flutter build appbundle --release --dart-define=API_BASE_URL=https://yourstore.com/api/v1

# Android — direct download / sideload
flutter build apk --release --split-per-abi \
  --dart-define=API_BASE_URL=https://yourstore.com/api/v1

# iOS — App Store
flutter build ipa --dart-define=API_BASE_URL=https://yourstore.com/api/v1

Add your own signing key

Out of the box android/app/build.gradle.kts signs release builds with Flutter's debug keystore, so the project builds immediately. Google Play rejects debug-signed uploads. Create a keystore and wire up a real signingConfig before publishing.

Payments: no in-app purchase

Tapping Buy calls POST /api/v1/checkout, which returns a web checkout URL. The app opens it in a secure in-app browser — SFSafariViewController on iOS, Chrome Custom Tabs on Android — reusing the storefront's existing Stripe or PayPal flow and the same provisioning pipeline.

eSIM connectivity is a real-world service, which both Apple and Google permit to be sold outside in-app purchase (the model Airalo and Holafly use). So there is no native IAP and no 30% store commission, and no payment credentials are ever handled inside the app.

The completed order links back to the buyer by email, so it appears under My eSIMs and Order history on their next refresh.

Store compliance

Already handled in the app:

  • In-app account deletion (Account → Delete account → DELETE /api/v1/me) — required by both stores
  • Terms of Service and Privacy Policy links, served from your backend
  • Only the INTERNET permission — no camera, photos, location or contacts, so no runtime prompts and nothing extra to declare
  • Secure token storage — iOS Keychain / Android EncryptedSharedPreferences
  • No third-party ad or tracking SDKs

Still yours to do before submitting:

  • Point API_BASE_URL at production HTTPS
  • Configure release signing (Android keystore, iOS provisioning profile)
  • Host a public Privacy Policy URL (the app links to /privacy on your store)
  • Answer Apple App Privacy / Google Data safety (data collected: name and email, for account and purchases; not used for tracking)

Project layout

Feature-first, layered by responsibility:

lib/
  main.dart                 # bootstrap + ProviderScope
  app.dart                  # MaterialApp.router + theme
  core/
    config/                 # AppConfig (compile-time defines)
    theme/                  # AppTheme + AppColors (light/dark)
    network/                # ApiClient (Dio) + ApiException
    storage/                # TokenStorage (Keychain / Keystore)
    router/                 # go_router + auth-aware redirect
    models/ · widgets/ · utils/
  features/
    onboarding/ auth/ catalog/ checkout/ esims/
    advisor/ support/ profile/ settings/ shell/

Every feature has data/ (repository), domain/ (models), application/ (Riverpod providers) and presentation/ (screens). State is Riverpod, navigation is go_router, networking is Dio.

Troubleshooting

SymptomCause and fix
Splash screen never finishesThe app cannot reach API_BASE_URL. Test the URL with curl from the same network; on an Android emulator use 10.0.2.2, not localhost.
/api/v1/health returns 404The web server isn't routing /api/* to Laravel. Confirm the try_files … /index.php?$query_string rule and clear caches with php artisan route:clear.
Login fails with a valid passwordMigrations not run — the personal_access_tokens table is missing. Run php artisan migrate.
Certificate errors on a local domainAdd --dart-define=ALLOW_BAD_CERTS=true for local testing only.
AI Advisor says it is unavailableNo AI provider configured, or demo_mode is on. See the AI assistant guide.
"My eSIMs" is empty after a purchaseThe order is still provisioning, or it was paid with a different email than the account. See fulfillment.
Too many requestsThe API allows 90 requests/minute per token; the AI Advisor is capped at 15/minute.
Last Updated: 8/13/26, 7:27 PM
Next
Mobile API (/api/v1)