A unified architectural specification and cross-platform contract spanning RexOne Core (Rails API), RexOne Web (React SPA), and RexOne Mobile (Flutter Native).
RexOne establishes the RexOne Architecture Foundation—a disciplined, production-grade tri-platform standard for modern digital products:
- Identity & IAM: Passwordless/passcode auth, Google SSO, granular role-based access control.
- Commerce & Subscriptions: Stripe Checkout, recurring billing, coupon redemptions, access entitlement ledger.
- Background Queues: Solid Queue hybrid Fiber + Thread workers, zero Redis dependency for jobs.
- Asset Management: Self-hosted S3-compatible Garage storage, async compression pipelines (libvips / FFmpeg).
- Real-Time WebSockets: Action Cable channels for notifications, AI streaming, live speech recognition.
- Observability: Server metrics (Rails Pulse), exception dashboard (RED), client error ingest (
Client::Log). - Client Experience: 100% localization parity (
en,es,my), dynamic theming, offline SQLite storage (Drift).
Building any modern commercial digital product across Backend, Web, and Native Mobile traditionally demands 8 to 12 months of grueling infrastructure assembly:
- Months 1–3: Wiring Devise/JWT auth, email verification, password resets, and 96+ IAM permissions.
- Months 4–6: Wrestling Stripe subscription webhook race conditions, coupon ledgers, and proration state machines.
- Months 7–9: Building CRUD admin portals, S3 signed upload pipelines, WebSockets, and resolving 50+ JSON contract drifts between Web and Flutter.
- The Result: $150,000–$300,000 burned before writing a single line of proprietary business logic.
RexOne is Architectural Time Travel. By delivering a synchronized, battle-hardened foundation across Rails 8, React 19, and Flutter 3 backed by 1,785+ automated tests (1,071 RSpec + 372 Vitest + 342 Flutter), RexOne deletes 9 months of generic plumbing from your roadmap. You launch your unique product in 1 to 3 weeks.
The author (Rex) could have closed-sourced this enterprise foundation or charged $800+ behind a commercial paywall. Instead, out of pure loving-kindness (mettā) for builders, indie hackers, and learners worldwide, RexOne is 100% free and open-source under Apache 2.0. If RexOne saves you months of work, please kindly return the loving-kindness: Sponsor the Author on GitHub and star the repositories. Thank u so much for your kindness.
| Resource | Scope & Canonical Specification |
|---|---|
| 📜 Constitutional Law | Strict engineering constraints and architectural rules: LAW.md (Zero exceptions) |
| 🤝 Governance & RFCs | Community guidelines, Code of Conduct, and BDFL/RFC governance: GOVERNANCE.md & CONTRIBUTING.md |
| 🤖 Anti-Vibe AI Policy | Machine-enforced coding standards for AI assistants & contributors: AI Contribution Policy |
| 🎓 GSoC & Grants Roadmap | Google Summer of Code project ideas catalog & institutional grant roadmap: GSoC Ideas Catalog |
| 🌐 Public Distribution | Curated directories, Awesome-lists, and community launch indexes: Public Distribution |
| 📖 API Docs & Swagger | Complete OpenAPI v1 schema and interactive Swagger UI at /admin/api-docs: swagger.yaml (Spec: spec/openapi/v1.rb) |
| 🌐 Live Web Demo | Production web application preview: rexone.rex9.me (API: api.rexone.rex9.me) |
| 🗺️ Visual Walkthrough | Screenshot tour across Core, Web, Mobile, and operations: Visual Walkthrough |
| 🛡️ Production Operations | Production hardening, Cloudflare edge defense, and DDoS protection: Core Deployment & DDoS Protection |
| 📱 Mobile Store CI/CD | Google Play & Apple TestFlight automated release pipelines: Mobile Deployment |
| 🌐 AI Discovery & GEO | Machine-readable context files (/llms.txt, /llms-full.txt), crawler policies: AI Discovery & GEO Guide |
| 🎞️ Media Playback | Short-lived signed provider URLs and streaming roadmaps: Media Playback & Roadmap |
Standardized across the ecosystem for any derivative product (custom TLD):
- Demo Tier:
rexone.rex9.me(Web) &api.rexone.rex9.me(API) - Product Prod:
<product>.<tld>(e.g.example.com) &api.<product>.<tld>(e.g.api.example.com)- Mobile App:
<App Name>(Android:<com.company.app>, iOS:<com.company.app>), deep link:<scheme>://
- Mobile App:
- Product UAT:
uat.<product>.<tld>(e.g.uat.example.com) &uat.api.<product>.<tld>(e.g.uat.api.example.com)- Mobile App:
<App Name> UAT(Android:<com.company.app>.uat, iOS:<com.company.app>.uat), deep link:<scheme>-uat://
- Mobile App:
- Product Dev:
dev.<product>.<tld>(e.g.dev.example.com) &dev.api.<product>.<tld>(e.g.dev.api.example.com)
flowchart TB
subgraph Clients["Clients Layer"]
Web["RexOne Web (React 19 + Vite 8 + TS 6)"]
Mobile["RexOne Mobile (Flutter 3 + GetX MVC)"]
end
subgraph Transport["Transport Layer"]
HTTPS["HTTPS (JSON:API, X-Platform, X-Locale, Bearer JWT)"]
WSS["WSS (Action Cable / Solid Cable Protocol)"]
end
subgraph Core["RexOne Core (Rails 8.1 API + Ruby 4.0.4)"]
API["Rails API Layer (Devise, Controllers, IAM, Pagy)"]
Waka["Waka Worker (Solid Queue: payments, ai, notifications, storage)"]
Media["Media Worker (Solid Queue: media compression via libvips / FFmpeg)"]
Services["Service Boundary (Payment, Storage, AI, Notification, Speech, Cache)"]
Obs["Observability (Rails Pulse, RED Error Dashboard, Solid UI, Client Logs)"]
end
subgraph Providers["Persistence & External Providers"]
Postgres[(PostgreSQL 18 - UUID, Discard, Audited)]
Garage[(Garage S3 Storage / Cloudinary / Local)]
Stripe["Stripe (Checkout, Subscriptions, Webhooks)"]
AI["AI Providers (DeepSeek / Google Gemini)"]
Speech["Azure & Nova Speech (TTS / STT)"]
OneSignal["OneSignal (Push)"]
Brevo["Brevo (Email)"]
Firebase["Firebase Analytics (Web & Mobile Telemetry)"]
end
Web -->|HTTPS| API
Web <-->|WSS /cable| API
Mobile -->|HTTPS| API
Mobile <-->|WSS /cable| API
API --> Postgres
API --> Services
API --> Obs
API --> Waka
Waka --> Postgres
Waka --> Services
Media --> Postgres
Media --> Services
Services --> Stripe
Services --> AI
Services --> Speech
Services --> OneSignal
Services --> Brevo
Services --> Garage
Web -.-> Firebase
Mobile -.-> Firebase
Mobile -.-> OneSignal
- Runtime: Ruby
4.0.4, Rails8.1.0(API mode), PostgreSQL18. - Docker Compose: Orchestrates a 5-container topology:
api: Rails API server on:3000.waka: Dedicated Solid Queue worker for async business queues (payments,ai,notifications,default).media: Quarantined Solid Queue worker for CPU-heavy compression (libvips,FFmpeg).db: PostgreSQL18on:5432.garage: Self-hosted S3-compatible distributed object storage on:3100API /:3101Admin.
- Key Gems:
devise,devise-jwt,solid_queue,solid_cable,solid_cache,discard(soft deletes),jsonapi-serializer,pagy(pagination),rails_pulse(performance monitoring),rails_error_dashboard(exception tracking),rswag(OpenAPI/Swagger),administrate(internal admin dashboard).
All tables use UUID primary keys (gen_random_uuid()), soft deletes via Discard (discarded_at, undiscarded_at), and user audit tracking via the Auditable concern (created_by_id, updated_by_id, discarded_by_id, undiscarded_by_id).
| Domain | Key Models | Responsibilities |
|---|---|---|
| Identity & Users | User |
Devise authentication, JWT JTI revocation, 6-digit confirmation codes, 6-digit password reset codes, Google OAuth linking, Stripe customer generation, profile avatar assets. |
| IAM (RBAC) | Iam::Role, Iam::Permission, Iam::UserRole, Iam::RolePermission |
Granular action-resource permissions (user.can?(action, resource)). System roles (super_admin, admin, default user). Dynamic auto-assignment on signup. |
| Commerce | Payment::Product, Payment::Subscription, Payment::Purchase, Payment::Coupon, Payment::UserCoupon, Payment::WebhookEvent |
Stripe product/price catalog, subscription items snapshot, percentage/fixed coupons with referral generation, targeting restrictions, immutable redemption ledger, purchase receipts, durable webhooks. |
| Entitlements | Access |
Granted, revoked, and expired product access records tied to users and products. |
| AI / Chat | Chat::Room, Chat::Message, Ai::Profile, Ai::Run |
Flexible chat rooms (ai, direct, group, hybrid), message processing jobs, 2,000-char message chunking (split_id, chunk_index, total_chunks), provider abstraction, execution telemetry. |
| Media | Asset |
Unified media records, Garage S3 storage keys (user/{user_id}/..., admin/...), compression telemetry, status (pending, processing, ready, optimal), child assets (parent_asset_id for thumbnails and subtitles). |
| Telemetry | Client::Log |
Client error ingest (stack traces, device, OS, browser, URL, severity, cookies, storage keys), linked to optional version_id. |
| Feedback | Feedback |
User rating (1–10), auto-inferred category (bug, feature_request, improvement, general), urgency (low, normal, high, urgent), device/route telemetry. |
| Notifications | Notification, UserNotification |
Multi-channel templates (In-App, Push, Email) with dynamic variable interpolation (user_name, user_email, link, domain entities), persistent inbox receipts with Pagy pagination. |
| App Versions | Client::Version, Client::UserVersion |
Marketing versions (draft, published, yanked), semantic version comparison, mandatory/optional update evaluation, client installation snapshots. |
Solid Queue operates under a hybrid concurrency architecture configured in config/queue.yml and config/queue.media.yml:
- Fiber Concurrency Worker (
waka):- Non-blocking Ruby Fibers (
async) running on Solid Queue withconfig.active_support.isolation_level = :fiber. - Runs up to 50 concurrent fibers on a single event loop across prioritized queues:
payments: Highest priority. Handles Stripe webhook fulfillment (Payment::ProcessWebhookJob) with strict idempotency.ai: Handles chat workflows (Chat::ProcessMessageJob), provider streaming (DeepSeek, Gemini via OpenAI-compatible endpoints), and text-to-speech jobs (Speech::ProcessTtsJob).notifications: Fans out Action Cable broadcasts, OneSignal push notifications, and Brevo transactional emails viaNotification::DispatchJobandNotification::DeliverJob.default: General asynchronous operations.
- Non-blocking Ruby Fibers (
- Thread Maintenance Worker:
- Runs 2 dedicated OS threads for sequential, transactional tasks:
solid_queue_recurringanddefault. - Recurring tasks (
config/recurring.yml) clean up stale caches, expired access records, processed webhook events, discarded records, and old notifications (Notification::CleanupJob: read >30d, unread >90d, discarded >7d). - Weekly data reconciliation via
DataSyncJobcallingDataSyncService.sync_all!. Lifetime counters (sent_count,read_count) are maintained atomically.
- Runs 2 dedicated OS threads for sequential, transactional tasks:
- Quarantined Media Worker (
media):- Runs in a dedicated container with OS threads to prevent CPU-intensive
libvipsandFFmpegworkloads from starving I/O-bound fiber queues. - Handles image/audio/video compression (
Media::CompressMediaJob), SVG-to-PNG conversion (Media::ConvertImageJob), and video thumbnail generation. - Employs an optimal-first pipeline: if size reduction is
< 3%or size does not decrease, the asset is markedoptimal; otherwise, a configurable pass cap is enforced.
- Runs in a dedicated container with OS threads to prevent CPU-intensive
- Storage Worker (
storage):Storage::DeleteJobasynchronously purges remote objects from Garage S3 after DB transactions commit.
ApplicationControllerinspects the incomingX-Platformheader (web,android, orios).- Checks against Redis/SolidCache key:
active_session:user:#{user_id}:#{platform}. - Allows up to 3 concurrent sessions per user across different platforms (1 Web, 1 Android, 1 iOS).
- Automatically invalidates an older session when a new sign-in occurs on the same platform type.
- Single-Field Entry (
/peek): Clients send email or username toGET /v1/auth/peek(protected by a 12 req/min IP rate limiter). Core returns the account state so the client knows whether to prompt for login, initiate registration, or apply security cooldowns. - Passcode Authentication: 6-digit numeric passcodes verified via Devise with escalating cooldown protection.
- Google OAuth: Links Google accounts directly, handles registration challenge flows, and sets up authentication without loose intermediate states.
- User-Facing Terminology: Surfaces as "Delete Account" in user interfaces for absolute clarity.
- Email Reservation: The email address remains reserved and cannot be reused to register a new account.
- Confirmation Prompt: Frontends MUST display a confirmation dialog warning users that the account will be deleted, that the email address cannot be reused for future registration, and directing users to contact support (
support@rexone.com) if needed. - Comprehensive Session Revocation: Deletion rotates
user.jti = SecureRandom.uuid, clears active sessions (AuthConstants::Session.clear_all), and broadcastsNotificationConstants::Type::SESSION_INVALIDATEDover Action Cable, immediately terminating active sessions across Web and Mobile. - Super-Admin Protection: Super administrators (
super_admin) are strictly forbidden from self-deletion (returns422 Unprocessable Content).
Permissions follow a clean, four-level administrative model:
super_admin: Full system-wide authority over all resources, configurations, and IAM tables.admin: Operational control across domain resources (feedbacks,payments,ai,assets,logs,notifications). Restricted from managingusers,iam,versions, anduser_versions.- Partial Admin (
*_admin): Scoped administrative roles (e.g.feedback_admin,payment_admin,ai_admin).- Must use the
*_adminsuffix convention. - Grants access only to specific
/v1/admin/*resources matching the role's assigned permissions. - Non-admin permissions (e.g. from the base
userrole) can never access/v1/admin/*endpoints.
- Must use the
- Single-Request IAM Introspection:
GET /v1/users/current/iamreturns complete role/permission sets (is_admin,is_super_admin,roles,admin_roles,permissions,admin_permissions) so clients evaluate UI permissions immediately without secondary calls. - Unified Product Entitlements Flow (
UserSerializer.accesses): Just like IAM permissions, active product accesses (id,product_id,product_code,product_name,granted_at,expires_at,remaining_days,active) are serialized directly intoUserSerializer.accessesupon authentication (/signin,/signup,/confirmation) and session introspection (GET /v1/users/me). Frontends (Web & Mobile) persist this payload locally on boot, evaluate entitlements in-memory with real-time expiration awareness (useAccessin Web,AuthController.hasAccessin Mobile), and seamlessly refresh via WebSockets (payment_success,subscription_created).
- User Management:
GET/POST /v1/admin/users,PATCH /v1/admin/users/:id,DELETE /v1/admin/users/:id(CRUD, soft-delete discard/undiscard, role assignment, confirmation status auditing). - IAM Management:
GET/PATCH/DELETE /v1/admin/iam/roles,GET/POST/PATCH/DELETE /v1/admin/iam/permissions, bidirectional user role assignments viaGET/POST /v1/admin/iam/users/:user_id/roles,DELETE /v1/admin/iam/users/:user_id/roles/:role_id, andGET/POST /v1/admin/iam/roles/:role_id/users,DELETE /v1/admin/iam/roles/:role_id/users/:user_id. - Chat Endpoints: User API:
GET/POST /v1/chat/rooms,GET/PUT/DELETE /v1/chat/rooms/:id,GET/POST /v1/chat/messages,DELETE /v1/chat/messages/destroy_all. Admin moderation:GET/PATCH/DELETE /v1/admin/chat/roomsandGET/PATCH/DELETE /v1/admin/chat/messages. - AI Control Plane & Universal TOON Pipeline:
GET/PATCH /v1/admin/ai/profiles(prompt templates, models, token limits, multi-attribute sorting and filters),GET /v1/admin/ai/runs(execution telemetry, latency, token consumption). RexOne enforces a universal Zero-JSON LLM Pipeline: Large Language Models never receive or output raw JSON. All inbound JSON (in user messages, assistant history, system prompts, or template values) is automatically converted to compact Token-Oriented Object Notation (TOON viaAi::ToonService), compressing context by 30–60% over JSON. Models are instructed to output structured data strictly in TOON format (```toon). On receiving completions, the server transparently converts TOON structures back into standard, formatted JSON before database persistence and client WebSocket broadcasting, maintaining 100% standard JSON compatibility across web, mobile, and REST clients without requiring frontend TOON parsers. - Standardized Permissions Protocol: Strictly 4 canonical CRUD actions (
read,create,update,delete). Soft deletes map to:delete, restores map to:delete. Resources are explicitly prefixed (e.g.ai_profiles,chat_rooms,payment_products). - Product Management:
GET/POST/PATCH/DELETE /v1/admin/payment/products(supporting ILIKE multi-attribute search across product name, code, and description, sorting, and lifecycle recycle bin pagination), Stripe catalog sync, active user access inspection (GET /v1/admin/accesses?product_id=:id). - App Versions: Super-admin only.
GET/POST /v1/admin/client/versions, discard/undiscard, andGET /v1/admin/client/versions/:id/user_versions. - User Versions: Super-admin only.
GET /v1/admin/client/versions/user_versionslists device snapshots with optional platform filtering. - Asset Management:
GET /v1/assets(list assets by type),POST /v1/assets/upload(direct upload),GET /v1/assets/:id/playback(S3 SigV4 signed playback URL),GET /v1/assets/:id/subtitles/:subtitle_id(CORS-enabled raw VTT/SRT text). Admin API:GET/PUT/DELETE /v1/admin/assets,GET /v1/admin/assets/storage_stats(bucket, object, and host disk capacity), manual secondary compression triggers. - Video Thumbnails & Subtitles: Core generates WebP thumbnails asynchronously and broadcasts
asset_thumbnail_generated. Admins can upload thumbnail replacements or attach.srtsubtitle files (POST /v1/admin/assets/:id/subtitle/upload). Subtitles are served inline with pre-fetched text. - Notifications:
GET/POST /v1/admin/notifications,POST /v1/admin/notifications/dispatch(audience fanout by roles/users/all), andGET/DELETE /v1/admin/user_notifications(receipt lifecycle, recycle bin management).
- Core: React
19, TypeScript6, Vite8, Tailwind CSS4, DaisyUI, Headless UI, Heroicons, Lucide. - State Management: React Contexts (
AuthContext,LoadingContext,ToastContext), Jotai atomic stores. - Networking: Axios instance with centralized auth interceptors; Action Cable JS client for WebSockets.
- Localization:
i18nextwith strictly typed keys (en,es,my).
-
Atoms & Tokens: Neon Scarlet Red (
#FF2238), Secondary Vermilion (#FF4D2E), Accent Laser Red (#FF0D2D), Deep Night Canvas (#160B11), 8-based spacing grid, soft radius scale (xstofull). -
Inputs:
TextInput,TextArea,PasswordInput,Dropdown,Toggle,DateTimePicker(with automatic UTC$\leftrightarrow$ Local conversion),NumberInput. -
Overlays:
Dialog,ConfirmDialog(for destructive actions),LoadingOverlay(high-opacity backdrop blur),Toast. Universal loading managed viaLoadingContext. -
Buttons: Polymorphic
Button(renders semantic<a>or<button>with identical styling and running neon laser borders),GoogleButton,SignOutButton. Zero raw<a>or<button>tags in domain pages. -
Media:
Asset,Image,Video. Zero raw<img>or<video>tags.
- Authentication: URL-driven dialog flow (
?dialog=auth&step=...). Passwords held strictly in memory; zero leakage into URL params or storage. - Commerce & Billing: Product catalog, coupon validation (
/v1/payment/coupons/validate), Stripe Checkout handoff, instant provisioning for 100% free coupons, active subscription management, and detailed discount breakdown cards. - AI Workspace: Queued conversational interface. Displays thinking states, streams responses over WebSocket (
useAiSocket), and auto-refreshes conversation history. - Speech & Audio: Streams binary MP3 audio directly from
/v1/speech/ttswithout base64 overhead, plays message TTS, and connects live WebSocket audio recognition. - Asset Control Center (
/admin/assets): Live VPS and Garage storage dashboard, multi-file batch uploads with optimistic progress, searchable parent asset picker (AdminParentAssetSelect), real-time compression badges, and secondary compression triggers. - Admin AI Control Panel (
/admin/ai): Profiles management (AdminAiProfileCreatePage,AdminAiProfileEditPage) with provider/model selectors, temperature/token boundaries, and prompt editors; AI execution audit log (AdminAiRunsPage) with latency and token breakdowns. - AI Discovery & SEO/GEO: Standardized
/llms.txt,/llms-full.txt(llmstxt.org standard), comprehensive crawler rules inrobots.txt, and Schema.orgSoftwareApplicationrich results inindex.html. - Admin Portal Governance: Route guards (
AdminRootRoute), client-side permission checks (usePermissions), confirmation status badges (confirmed/unconfirmed), and operational KPI navigation.
-
Framework: Flutter
3.x, Dart3.x(pure native 60fps). -
Architecture: GetX Clean MVC (Pages
$\rightarrow$ Controllers$\rightarrow$ Services$\rightarrow$ Models), dependency injection viaInitialBinding. -
Core Packages:
get(reactive state management & DI),drift&drift_flutter(type-safe SQLite local database),flutter_screenutil(375x812 baseline),flutter_dotenv(.env.dev,.env.uat,.env.prod),google_sign_in,onesignal_flutter,firebase_analytics,better_player(hardware-accelerated video),just_audio&just_audio_background(background audio engine),background_downloader&live_activities,cryptography(AES-256-GCM sandbox encryption),in_app_purchase(Google Play Billing & StoreKit),app_links(universal deep linking).
- Design System Tokens:
AppColors,AppTypography,AppSpacing,AppStyles,AppIcons,AppMedia,AppTheme(Material 3 Light/Dark). - Theme Extensions: Reactive styling via
context.colors.*andcontext.typo.*. - UI Components:
AppAccessGate,AppButton,AppDropdown(universal select dropdown with custom options, prefix icons, and small/medium/large sizes matching Web'sDropdown),AppInputField,AppPasswordField,AppLoading(dual-mode: modal blocking overlay & non-blocking top linear progress),AppPagyListView(infinite scroll lazy loading with automatic next-page trigger, pull-to-refresh, empty/error fallbacks),AppSearchBar(debounced search with clear trigger, filter badge, and integratedAppDropdownfilter selection),AppSnackbar,AppDialog(withAppDialog.confirm()for destructive flows),AppPage,AppListTile,AppToggle,AppNetworkBanner("Offline mode" banner).
- Auth Flow: Parity with Web & Core (email peek, 6-digit passcode, OTP verification, Google OAuth, session invalidation).
- Push Notifications: OneSignal integration (
PushNotificationService). Syncs user ID and tags on login, clears on logout. Deletions confirmed viaAppDialog.confirm(). - Email Delivery: Multi-channel templates rendered through a responsive email layout (
TemplateRenderer) with dynamic variables and normalized absolute client URLs (AppConfig.client_url). - Product Analytics: Firebase GA4 integration using constantized
action_nounevents (sign_up,sign_in,view_product,purchase_product,open_notification) tagged with platform (android,ios). - Stripe & Billing: In-app Stripe Checkout WebView (
CheckoutPage), subscription management cards, cancellation confirmation, canonical currency minimum charge limits (StripeMinimumAmounts). - AI Assistant: Persistent multi-room chat, JSON:API response parsing, background thinking indicators, real-time completion toasts over WebSocket.
- Media Playback & Lyrics: Mixed audio/video playlist from
GET /v1/assets, signed streaming URLs fromGET /v1/assets/:id/playback, background audio player (just_audio+just_audio_background), hardware-accelerated inline video (better_playerwith dynamic 16:9 layout helper, audio focus tuning, and 100MB chunk caching), and synced.srtsubtitles/lyrics with per-track selection and gap bridging. - Offline Media & SQLite (Drift): AES-256-GCM encrypted local application sandbox downloads backed by local Drift SQLite database (
rexone_offline). Schema parity mirroring backend polymorphic tables (local_assets,local_child_assets). Local-first playback preferences and offline playlist mode with pre-fetched SRT subtitles and zero network-choke. - Client Telemetry: Uncaught Flutter and platform dispatcher errors dispatched to Core via
POST /v1/client/logs. - Localization: 100% translated in English (
en_US), Spanish (es_ES), and Burmese (my_MM). SynchronizesX-LocaleandAccept-Languageheaders on every HTTP request. - Dual-App Architecture & Store CI/CD: Side-by-side Prod & UAT application IDs (
<com.company.app>vs<com.company.app>.uat) preventing physical device overwrites, automated GitHub Actions release builds on Linux runners with direct upload to Google Play Internal Testing, and native local Apple Silicon CLI (./scripts/release_ios.sh) pre-validating and deploying release IPAs directly to Apple TestFlight in ~90 seconds with zero cloud cost (see Mobile Deployment).
| Capability Area | rexone-core |
rexone-web |
rexone_mobile |
|---|---|---|---|
| Auth: Email & 6-digit Passcode | ✅ | ✅ | ✅ |
| Auth: Google Sign-In & Challenge Flow | ✅ | ✅ | ✅ |
| Auth: Active Single-Platform Session Enforcement | ✅ | ✅ | ✅ |
| Auth: Escalating Password Retry Cooldown (Redis) | ✅ | ✅ | ✅ |
| Light & Dark Theming | N/A | ✅ | ✅ |
Multi-Language Localization (en, es, my) |
✅ | ✅ | ✅ |
HTTP X-Locale / Accept-Language Synchronization |
✅ | ✅ | ✅ |
| Destructive Action Confirmation Prompts | N/A | ✅ (ConfirmDialog) |
✅ (AppDialog.confirm) |
Error Telemetry Ingest & Storage (/v1/client/logs) |
✅ | ✅ | ✅ |
| Stripe: Product & Pricing Catalog | ✅ | ✅ | ✅ |
| Stripe: Checkout Session Handoff | ✅ | ✅ (Redirect) | ✅ (WebView) |
| Stripe: Subscriptions & Cancellation/Resumption | ✅ | ✅ | ✅ |
| Stripe: Purchase History & Discount Audits | ✅ | ✅ | ✅ |
| User Feedback System (1–10 Rating & Auto-Triage) | ✅ | ✅ | ✅ |
| Chat: Conversational Rooms (AI, Direct, Group) | ✅ | ✅ | ✅ |
| AI: Queued Background Execution (DeepSeek / Gemini) | ✅ | ✅ | ✅ |
| AI: Real-Time WebSocket Completion Alerts | ✅ | ✅ | ✅ |
| Speech: Text-to-Speech (Binary Audio Streaming) | ✅ | ✅ | ✅ |
| Speech: Speech-to-Text (Upload & URL) | ✅ | ✅ | ✅ |
| Speech: Live Audio STT Streaming (WebSocket) | ✅ | ✅ | ✅ |
| Media: Multi-Provider Storage (Garage S3, Cloudinary, Local) | ✅ | ✅ | ✅ |
| Media: Background Compression (libvips / FFmpeg) | ✅ | ✅ | N/A |
| Media: Real-Time WebSocket Compression Updates | ✅ | ✅ | N/A |
| Media: Batch Upload & Optimal-First Pipeline | ✅ | ✅ | N/A |
| Media: Audio/Video Playlist & Synced SRT Subtitles | ✅ | N/A | ✅ |
Media: Signed Playback URL Resolution (/v1/assets/:id/playback) |
✅ | N/A | ✅ |
| Offline Media Downloads & Drift SQLite Database | N/A | N/A | ✅ |
| Push Notifications (OneSignal) | ✅ | N/A | ✅ |
| Product Analytics (Firebase) | Constants | ✅ | ✅ |
| Client Admin Panel (RBAC, Users, IAM, Products, Assets) | ✅ | ✅ | N/A |
| Admin AI Control Panel (Profiles & Runs Telemetry) | ✅ | ✅ | N/A |
| In-App Version Upgrader & Splash Check | ✅ | ✅ | ✅ |
| AI Discovery & Generative Engine Optimization (GEO) | N/A | ✅ | N/A |
| Dual-App Store Architecture (Prod vs UAT) | N/A | N/A | ✅ |
| Automated Android CI/CD (Google Play & GitHub Releases) | N/A | N/A | ✅ |
Native iOS TestFlight CLI Pipeline (release_ios.sh) |
N/A | N/A | ✅ |
| Single-Command Multi-Platform Rebranding Engine | ✅ | ✅ | ✅ |
| Modular Coolify PaaS VPS Deployment | ✅ | ✅ | N/A |
| Zero-JSON LLM Pipeline (TOON Compression 30–60%) | ✅ | ✅ | ✅ |
| Omnichannel In-App Purchases (Stripe, Play Store, App Store) | ✅ | N/A | ✅ |
| Hardware-Accelerated Media Streaming (Vidstack / BetterPlayer) | ✅ | ✅ | ✅ |
| AES-256-GCM Encrypted Media Downloads (Drift SQLite) | N/A | N/A | ✅ |
Universal Deep Linking (rexone://) & Continue URLs |
N/A | ✅ | ✅ |
-
Base URL:
/v1/ -
Standard Request Headers:
Authorization: Bearer <JWT_TOKEN> X-Platform: web | android | ios X-Locale: en | my | es Accept-Language: en | my Content-Type: application/json
-
Standard JSON:API Response Envelope:
{ "status": { "code": 200, "success": true, "message": "Localized status description", "error": null }, "data": { ... }, "meta": { "token": "...", "storage_details": { ... }, "pagination": { "current_page": 1, "total_pages": 5, "total_count": 50, "limit": 10, "next_page": 2, "prev_page": null } } } -
Strict Separation of Primary Entity (
data) and Auxiliary Metadata (meta) (Law C2, Law U14):datastrictly encapsulates the primary domain entity (Serializer.record(record)) or resource collection (Serializer.collection(collection)).- Redundant nested wrapper keys (
{ user: ... },{ asset: ... },{ role: ... },{ product: ... },{ room: ... }) insidedataare strictly forbidden and completely eradicated. metastrictly encapsulates all auxiliary metadata and operation context outside ofdata. This includes authentication tokens (token), verification states (otp_sent,password_required,challenge_token), rate-limiting feedback (remaining_attempts,cooldown_remaining), cloud storage upload metrics (storage_details), and collection pagination (pagination).- Zero Legacy Fallbacks: Frontends (Web and Mobile) do not maintain backward-compatibility shims or fallback chains (
meta?.token ?? data?.token,data?.user ?? data). Contracts are deterministic, clean, and unambiguous.
-
Canonical 5 Serializer Standards (
ApplicationSerializer): All serializers inherit fromApplicationSerializer(jsonapi-serializer). Controllers and services must never call raw.serializable_hashdirectly; serialization is strictly centralized through 5 canonical class methods:record(record, options = {}): Serializes a single JSON:API resource ({ id, type, attributes }). Used for single record GET/POST/PATCH responses (data: Serializer.record(record)). Redundant top-level entity wrapper hashes (e.g.{ user: ... },{ asset: ... },{ user_version: ... }) are strictly forbidden for single-record responses.collection(collection, options = {}): Serializes a JSON:API resource array ([{ id, type, attributes }]).collection_pagy(collection, pagy, options = {}): Serializes a paginated JSON:API collection with standardmeta.paginationenvelope ({ data: [...], meta: { pagination: { current_page, total_pages, total_count, limit, next_page, prev_page } } }).record_attributes(record, options = {}): Extracts a clean, flat attribute hash ({ id, ... }). Used for nested object attributes inside serializers or composite multi-key operation hashes.collection_attributes(collection, options = {}): Extracts a clean, flat array of attribute hashes ([{ id, ... }]). Used for nested collections inside serializers (e.g.user.accesses,asset.subtitles,subscription.items).
-
Universal Collection Pagination & Zero "All" Flags (Law U8):
- All Top-Level Collections Are Paginated: Every list/collection endpoint MUST paginate via
Pagy(pagy, records = pagy(scope)) and return the standardized envelope withdataarray andmeta.pagination. - Default Full Collection (Omitted Params): If a client omits
pageandlimit,PagyHelper#pagycalculates total count and automatically returns all records onpage: 1(limit: [total_count, 1].max) wrapped inside a compliant pagination envelope. Clients never pass arbitrary string flags (limit: "all"). - Zero Pagination for Nested Collections: Embedded associations inside serializers (e.g.
UserSerializer.accesses,AssetSerializer.subtitles) MUST NEVER be paginated; they always return all associated items cleanly as flat attribute lists viacollection_attributes. If a client requires paginated sub-resources, it queries the dedicated top-level collection endpoint with pagination filters (e.g.,GET /v1/admin/accesses?user_id=:id&page=1&limit=20).
- All Top-Level Collections Are Paginated: Every list/collection endpoint MUST paginate via
-
Client-Side Centralized Parsing Standard (Law W8):
- RexOne Web (React): All API consumer controllers MUST use centralized parsers from
@/services/api.service:parseRecord<T>(record)for single entities andparsePagyList<T>(response)for paginated collections (returning{ records, pagination }). The parsers cleanly unwrap{ id, type, attributes }JSON:API envelopes into flat domain entities while handling flat payloads. Controllers must never perform manual.attributesdrilling. - RexOne Mobile (Flutter): Standardized strictly on two centralized parsers in
ApiService:ApiService#parseRecord<T>(response, [T Function(Map<String, dynamic>)? fromJson])for single entities andApiService#parsePagyList<T>(response, T Function(Map<String, dynamic>) fromJson)for paginated collections (returningPaginatedResponse<T>withrecordsandpagination). Endpoints parse directly into cohesive domain entities (UserModel,CouponModel,CouponValidationModel,AiMessageModel,AssetModel,UserPeekModel,MediaPlaybackModel), with operation metadata delivered inApiResponse.meta. All domain models consume flat attribute records directly fromApiService#flattenRecord. - Chat Message Creation Contract (
POST /v1/chat/messages): Responses provide primary user message indata(Chat::MessageSerializer.record), full messages array inmessages(Chat::MessageSerializer.collection), and metadata inmeta(room_id,messages). Both Web and Mobile clients gracefully parse eitherdata(single message),messages(list), ormeta.messages. - Asset Creation & Upload Contract (
POST /v1/admin/assets,POST /v1/assets/upload): Single asset responses deliver the record serialized viaAssetSerializer.recorddirectly indata, accompanied by storage metadata inmeta.storage_details. Frontends parsedatadirectly intoAssetModeland retrieve storage details frommeta.
- RexOne Web (React): All API consumer controllers MUST use centralized parsers from
-
Mobile Pagy Controller & Infinite Scroll Architecture:
PagyControllerMixin<T>: Standardized reactive controller mixin managingitems,pagination(PaginationMeta),isLoading,isLoadingMore,isRefreshing,errorMessage,searchQuery,activeFilters, and automatic debounced search. Implements automatic state transitions forrefreshList(),loadMore(),setFilter(), andonSearchChanged().AppPagyListView<T>: Reusable infinite scroll list view that monitors scroll offsets (scrollThreshold: 200px), displays a non-intrusive bottom loading spinner when fetching subsequent pages, binds to pull-to-refresh (RefreshIndicator), and cleanly displays customized initial loading, empty, and retry error states.AppSearchBar: Universal search input with built-in keystroke debouncing, active search in-flight indicator, clear button, filter modal trigger badge, and integratedAppDropdownfilter selection.- Unified Centralized Loading Paradigm (
AppLoading): Eliminates redundant, scattered local spinners in favor of unified global loading matching Web'sLoadingContext:- Modal Blocking Overlay (
AppLoading.showOverlay([message])/AppLoading.hideOverlay()): Used for high-stakes asynchronous mutations (checkout, authentication, cancellations) with backdrop blur and spinner. - Non-Blocking Inline Progress (
AppLoading.showInline()/AppLoading.hideInline()): Renders a sleek top linear progress indicator mounted underSafeAreafor seamless background or list updates without freezing UI interaction.
- Modal Blocking Overlay (
- Endpoint:
GET /v1/client/versions/current?version=1.2.0&build_number=42- Headers:
X-Platform: ios | android | web(No JWT required). - Evaluates semantic marketing version first; if equal, evaluates platform build numbers (
ios_build_numberorandroid_build_number). - Response Format: Returns
dataserialized viaClient::VersionSerializer.recordcontaining standard JSON:API{ id, type: "client_version", attributes: { number, update_required, must_update, skip_premium, store_url, ... } }.
- Headers:
- Response Flags:
update_required:truewhen client version is strictly behind the live published version (triggers non-blocking update prompt).must_update:truewhen the live version is flagged as force update and is newer than the client (triggers mandatory blocking modal).skip_premium:truewhen no live version is published yet in the catalog (initial app store review dark launch) or when client version is strictly newer than the live published version (app store review builds).store_url: Platform store URL configured viaIOS_STORE_URLorANDROID_STORE_URL.
- Client Handling:
- Mobile: When
must_updateis true,SplashPagepresents an un-bypassablePopScope(canPop: false)blocking screen. Optional updates (update_required: true && !must_update) prompt onHomePageviaAppDialog.update(...).VersionModel.fromJsonandApiService#parseRecordseamlessly resolve both JSON:API attributes envelopes and flat key structures. - Device Snapshot: Authenticated clients submit
POST /v1/client/versions/user-versionwith{ user_version: { version, build_number } }to upsert installation telemetry.
- Mobile: When
- Endpoint:
/cable - Authentication: JWT token passed via connection query param (
?token=<JWT>) or subscription payload. - Channels:
Broadcasts async processing events, payment confirmations, and in-app inbox items:
| Event Type | Payload Attributes | Description |
|---|---|---|
ai_response_ready |
room_id, message_id |
AI message generation finished. |
ai_response_failed |
room_id, error |
AI generation failed. |
tts_ready |
message_id, asset_id |
TTS audio synthesis completed. |
tts_failed |
message_id, error |
TTS audio synthesis failed. |
asset_updated |
id, status, size_bytes, compressed_size_bytes, compression_ratio, compression_passes |
Real-time compression status updates. |
asset_thumbnail_generated |
asset_id, thumbnail |
Video thumbnail generated in background. |
payment_success |
product_name, amount |
Successful payment confirmation. |
payment_failed |
product_name, amount |
Failed payment alert. |
subscription_created |
product_name, active_until |
Subscription activated. |
subscription_canceled |
product_name, active_until |
Subscription canceled. |
subscription_resumed |
product_name, active_until |
Subscription resumed. |
in_app_notification |
id, title, message, link, read_at, created_at, metadata |
New persistent notification received. |
iam_updated |
roles |
Roles/permissions updated; JWT invalidated, client signs out. |
access_updated |
product_id |
Manual admin entitlement grant/extension; JWT invalidated, client signs out. |
access_revoked |
product_id |
Manual admin entitlement revocation; JWT invalidated, client signs out. |
session_expired |
{} |
Server session expired; client signs out. |
session_invalidated |
{} |
Server session invalidated; client signs out. |
welcome |
{} |
Emitted upon successful Action Cable subscription. |
Navigation targets: link represents the destination. Common internal destinations: /home, /profile, /payment, /ai. External URLs (https://) open in a new tab on Web and request user confirmation before opening the system browser on Mobile.
Provides bidirectional streaming for real-time speech recognition:
- Subscription Params:
{ "channel": "SpeechLiveChannel", "language": "en-US" } - Client RPC Actions:
audio: Streams 16-bit 16kHz mono base64 PCM audio chunk:{ "action": "audio", "chunk": "<base64_pcm>" }stop: Concludes stream and requests final transcription:{ "action": "stop" }
- Server Events:
partial: Interim transcription:{ "type": "partial", "text": "...", "is_final": false }final: Final transcription chunk:{ "type": "final", "text": "...", "is_final": true }error: Error payload:{ "type": "error", "error": "Reason" }
Used by Web and Mobile clients to report unhandled JavaScript or Dart exceptions:
{
"log": {
"message": "Exception description",
"severity": "error",
"platform": "web" | "android" | "ios",
"environment": "development" | "staging" | "production",
"app_version": "1.0.0",
"os": "Android",
"os_version": "14",
"device": "Pixel 8",
"browser": "Chrome",
"browser_version": "124.0.0",
"url": "/payment",
"method": "APP_EVENT",
"stack_trace": ["..."],
"local_storage_keys": ["auth_token", "user_email"],
"session_storage_keys": [],
"cookies": []
}
}Version resolution: Core maps app_version to a matching Client::Version record and stores version_id. If the version string is unrecognized, version_id remains null without rejecting the error report (no 422).
-
Password Authentication Retry Escalation:
- Tracked via Redis/SolidCache keys:
password:attempts:{user_id}andpassword:cooldown:{user_id}. - 3 failures
$\rightarrow$ 30s cooldown - 6 failures
$\rightarrow$ 60s cooldown - 9 failures
$\rightarrow$ 120s cooldown - 12+ failures
$\rightarrow$ 300s (5-minute) cooldown - Clients consume
meta.remaining_attemptsandmeta.cooldown_remainingto drive UI timers.
- Tracked via Redis/SolidCache keys:
-
Password Reset Email Cooldown (
POST /password/forgot):- Key:
password_reset:cooldown:{user_id}. - Enforces a 60s cooldown between consecutive reset requests.
- Returns
429 Too Many Requestswithmeta.cooldown_remaining.
- Key:
- Backend Infrastructure Dashboards:
- Rails Pulse: CPU load, memory usage, request latency, slow database queries.
- RED (Rails Error Dashboard): Server-side Ruby exceptions and backtraces.
- Solid UI: Background job queue throughput, retry backoffs, recurring cron jobs.
- Rails Administrate: Low-level database table inspection. Super-admin access only for sensitive tables.
- Client Admin Panel (React SPA):
- Focused strictly on business operations and end-user governance:
- Revenue analytics, subscription metrics, user growth KPIs.
- RBAC management (users, roles, permissions).
- Commerce catalog, coupons, and redemptions audit.
- Feedback triage (ratings, categories, urgency levels).
- Client telemetry (
Client::Logbrowser and mobile crashes).
- Focused strictly on business operations and end-user governance:
-
Validation Contract (
POST /v1/payment/coupons/validate):- Request:
{ "code": "SAVE20", "product_id": "UUID" }(Bearer auth required). - Success (200 OK):
{ "code": 200, "message": "Coupon is valid.", "data": { "valid": true, "coupon": { "id": "UUID", "code": "SAVE20", "title": "20% Off", "coupon_type": "percentage", "amount": 20, "currency": "usd" }, "original_amount": 1000, "discount_amount": 200, "final_amount": 800, "currency": "usd" } } - Invalid Code (422 Unprocessable Content):
{ "code": 422, "message": "Coupon is invalid.", "error": "Coupon is invalid.", "meta": { "remaining_attempts": 2, "cooldown_remaining": 0 } } - Cooldown Active (429 Too Many Requests):
{ "code": 429, "message": "Coupon is invalid.", "error": "Too many invalid coupon attempts. Please wait 30 seconds before trying again.", "meta": { "remaining_attempts": 0, "cooldown_remaining": 30 } }
- Request:
-
Security & Anti-Brute-Force Protection:
-
Uniform Error Masking: Non-existent, expired, maxed-out, or user-restricted coupons all return the identical message
"Coupon is invalid."to prevent code probing. -
Progressive Cooldown Ladder: 3 attempts
$\rightarrow$ 30s, 6 attempts$\rightarrow$ 60s, 9 attempts$\rightarrow$ 120s, 12+ attempts$\rightarrow$ 300s cooldown. - Reset: Successful redemption or checkout immediately clears attempt and cooldown counters.
-
Uniform Error Masking: Non-existent, expired, maxed-out, or user-restricted coupons all return the identical message
-
Checkout Integration (
POST /v1/payment/session,GET /v1/payment/session/:session_id):-
POST /v1/payment/session: Iffinal_amount > 0: Creates Stripe Checkout Session withdiscounts: [{ coupon: provider_coupon_id }], appendingsession_id={CHECKOUT_SESSION_ID}tosuccess_url, returning{ "checkout_url": "...", "session_id": "..." }. - If
final_amount == 0: Bypasses Stripe, recordsPayment::Purchase(withunit_amount: product.unit_amount, amount_received: 0), createsPayment::UserCoupon, and grants access viaAccessService.grant(...). -
GET /v1/payment/session/:session_id: Immediate client fulfillment upon reaching the success return URL. Inspects session status and immediately provisions subscription/purchase records and entitlement grants (AccessService.grant(...)) without waiting for background webhook delivery.
-
-
Referral Coupons:
- Every user signup automatically generates a unique referral code (
REF+ 6 random uppercase alphanumeric characters, e.g.REF7K9M2P) offering a 20% discount withmax_usage_per_user: 1.
- Every user signup automatically generates a unique referral code (
-
Canonical Stripe Minimum Limits:
Enforced across Core (
PaymentConstants::StripeMinimumAmount), Web (STRIPE_MINIMUM_AMOUNTS), and Mobile (StripeMinimumAmounts):- USD: 50 cents ($0.50)
- SGD: 50 cents (S$0.50)
- EUR: 50 cents (€0.50)
- GBP: 30 pence (£0.30)
- CAD: 50 cents (CA$0.50)
- AUD: 50 cents (AU$0.50)
- JPY: 50 yen (¥50)
- Default fallback: 50 units
-
Product Pricing Type Immutability & Bidirectional Stripe Sync:
-
Permanent Pricing Mode Lock: Once created, a Product's pricing type (Free vs Premium) cannot be transitioned in either direction (
Payment::Productenforces bidirectional immutability on update; Web Admin product form permanently disables the radio selector in edit mode). - 100% Discount Coupons: Premium products support 100% discount coupons for zero-cost checkout without altering product classification or detaching payment provider handling.
-
Discarded Scope Integrity: Discarded products remain treated as "not found" via
default_scope -> { kept }. No access is granted for discarded products, and they are omitted from client catalogs except in administrative recycle bins. -
Hardened Webhook Ingestion: Webhooks update product name, description, and active status directly even when
default_priceis omitted. Sync guards prevent non-default prices from overwriting primary product definitions, ignore unsupported currencies, reject remote conversions between free and premium, and restore discarded records (undiscard) when reactivated in Stripe.
-
Permanent Pricing Mode Lock: Once created, a Product's pricing type (Free vs Premium) cannot be transitioned in either direction (
-
Discount Audit Breakdown:
-
Payment::PurchaseandPayment::Subscriptionassociatehas_one :user_couponandhas_one :coupon. - Detail views display dedicated discount cards: coupon code, title, discount amount deducted, original amount, and net charged amount.
-
-
Recurring Entitlement Durations:
- Recurring billing periods use canonical intervals (
PaymentConstants::BillingInterval:day,week,month,year). - Access durations calculate via
product.interval_in_duration(1.day,7.days,30.days,365.days) to ensure continuous coverage across all billing cycles.
- Recurring billing periods use canonical intervals (
-
Batch Coupon Generation & Stripe Synchronization:
-
POST /v1/admin/payment/coupons/batchcaps requests at 100 coupons per batch. - Coupons are created with
active: falseandmetadata.status = "processing". - Synchronized to Stripe asynchronously in slices of 50 via
Payment::SyncBatchCouponsJobon queue:payments. - Successfully synced coupons become
active: true(metadata.status = "succeeded"). Failed coupons remainactive: falsewith logged errors for administrative inspection.
-
-
Targeting Restrictions:
- Coupons accept specific recipient emails via
target_user_emails: []. - Core resolves emails to user UUIDs and stores them in PostgreSQL
target_user_ids: uuid[], rejecting unresolvable emails with a descriptive 422 error.
- Coupons accept specific recipient emails via
-
Recycle Bin & Lifecycle Operations:
- Soft delete (
discard) moves coupons to the recycle bin while preserving Stripe sync IDs for potential restoration. - Hard delete (
destroy) permanently purges the coupon and its dependent redemption records from the database and Stripe. - Discarded coupons remain inspectable via
/v1/admin/payment/coupons/:idand/v1/admin/payment/coupons/:id/redemptions.
- Soft delete (
-
Unified Omnichannel Catalog Product Tier Model:
-
Payment::Productrepresents a single catalog entitlement tier (e.g. "Pro Monthly", $9/mo) and holds multi-store identifiers simultaneously:-
stripe_product_id&stripe_price_id: Web and card payment identifiers. -
google_play_product_id: Google Play Console product/SKU identifier. -
app_store_product_id: Apple App Store / StoreKit product/SKU identifier. -
supported_providers: Array of active platforms for this tier (["stripe", "google_play", "app_store"]).
-
- Single Entitlement Linkage:
Accessconnectsuser_idtoproduct_id(the single tier UUID). A subscription purchased on Web, iOS, or Android unlocks the exact same entitlement tier across all platforms. -
Bi-Directional Omnichannel Catalog Sync:
-
Stripe
$\rightarrow$ RexOne: Settinggoogle_play_product_idandapp_store_product_idin Stripe Product metadata automatically populates the RexOne database via webhooks (product.created,product.updated,price.created). -
RexOne
$\rightarrow$ Stripe: Creating or updating products in RexOne Admin transmitsgoogle_play_product_idandapp_store_product_iddirectly to Stripe's product metadata via API.
-
Stripe
-
-
Universal Transaction & Provider Tracking:
-
Payment::Purchase,Payment::Subscription,Payment::WebhookEvent, andPayment::Couponrecord the specific origin provider (provider: "stripe" | "google_play" | "app_store") with universal transaction keys (provider_payment_id,provider_subscription_id,provider_event_id,provider_coupon_id). - Swappable provider adapters exist in
app/services/payment/providers/(Payment::Providers::Stripe,Payment::Providers::GooglePlay,Payment::Providers::AppStore). - Unified In-App Purchase orchestrator:
Payment::IapService(app/services/payment/iap_service.rb).
-
-
Free Products (
unit_amount: 0):- Free products operate natively without requiring external store identifiers, though optional Stripe free tiers are supported.
- Immediate access is granted via
AccessService.grant(...)onPOST /v1/payment/sessionreturning{ "free_access_granted": true, "access_id": "..." }.
-
In-App Purchase Verification (
POST /v1/payment/verify):- Mobile initiates native purchase via StoreKit (iOS) or Google Play Billing (Android).
- Mobile posts store proof to
POST /v1/payment/verify:{ "provider": "google_play", // or "app_store" "product_id": "UUID", "transaction_id": "STORE_TX_ID", "purchase_token": "TOKEN", // Google Play "package_name": "BUNDLE_ID", // Google Play "receipt_data": "BASE64_JWS", // Apple App Store "coupon_code": "SUMMER50" // Optional server-side promo coupon } -
With or Without Coupon:
-
Without Coupon (
coupon_code: nil): Directly verifies token/receipt with the app store, createsPayment::PurchaseorPayment::Subscription, and provisions entitlements viaAccessService.grant(...). Zero coupon redemptions recorded. -
With Coupon: Validates coupon upfront against product restrictions, expiration, and user limits. Following successful store verification, applies coupon via
CouponService.apply_to_checkout!under pessimistic lock (user.lock!), creatingPayment::UserCoupon, incrementing usage, and returning coupon metadata in the response. - 100% Free Coupon: Bypasses native app store sheets entirely and provisions access instantly.
-
Without Coupon (
- Core broadcasts real-time WebSocket events (
payment_success/subscription_created) across connected Web and Mobile clients.
-
Silent Mobile Integration & Dual Checkout Toggle:
- Mobile features
PaymentConfig.enableInAppPurchases = falseby default, ensuring developers without app store merchant setups can run the app without errors or native store channel initialization. - When
enableInAppPurchases = false, mobile exclusively displays Stripe web checkout on all platforms. - When
enableInAppPurchases = true, mobile dynamically resolves native store SKUs (googlePlayProductIdon Android,appStoreProductIdon iOS), displaying native store checkout alongside optional card checkout whensupportsStripeis true.
- Mobile features
A unified, tamper-proof protocol across Web and Mobile that preserves user navigation intent when unauthenticated visitors attempt to access protected resources, coupled with native universal deep link dispatch:
- Cross-Platform Storage Standard:
- Universal storage key:
continueUrl(StorageKeys.CONTINUE_URLon Web,StorageKeys.continueUrlon Mobile).
- Universal storage key:
- Strict Zero URL Query Parameter Law:
continueUrlis NEVER exposed in URL query parameters (?continueUrl=...,?returnTo=...).- Stored strictly in local client storage (
localStorageviaAtomServiceon Web,GetStorageviaStorageServiceon Mobile) to eliminate URL tampering, open-redirect vulnerabilities, credential phishing vectors, and broken UX caused by users accidentally deleting or modifying query parameters.
- Capture on Protected Access:
- Web (
ProtectedRoute.tsx): When an unauthenticated visitor accesses an auth-required route (e.g.,/ai), the router captureslocation.pathname + location.search, persists it viaAtomService.setContinueUrl(targetUrl), and opens the onboarding auth dialog (AppRoutes.buildDialogUrl(DialogAuthSteps.INITIAL)). - Mobile (
GuardRoutes.redirect/AppRoutes.handleDeepLink): When an unauthenticated visitor hits an auth-required route (AppRoutes.ai,AppRoutes.settings, deep link), the router executesstorage.setContinueRoute(target)and redirects toAppRoutes.auth.
- Web (
- Open-Redirect & Circular Loop Protection (
getSafeContinueUrl):- Must begin with a single
/and must not begin with//(protocol-relative external redirect attack prevention). - Explicitly rejects circular auth routes (
/signin,/signup,/confirm-email,/forgot-password,/reset-password,/signout) using centralizedAppRoutesconstants with zero hardcoded string literals. - Returns
nullif the stored route violates validation rules.
- Must begin with a single
- Consumption on Authentication (
navigateContinueURL):- Upon completing authentication (password login, Google SSO, signup verification OTP, or email confirmation token link), client consumes and immediately purges the stored continue route (
consumeContinueUrlon Web,consumeContinueRouteon Mobile). - Web (
navigateContinueURL(navigate)): If a valid continue route was stored and passesgetSafeContinueUrl(target), navigates with history replacement (navigate(target, { replace: true })), falling back cleanly toAppRoutes.client.protected.HOME. - Mobile (
AppRoutes.navigateContinueURL([storageService])): Because unauthenticated mobile users are gated behind/authwithout access to in-app links,continueRoutetriggers when unauthenticated users open external deep links (rexone://...) or push notification links. To prevent users from being trapped on the destination without a way to return toHome, mobile mountsAppRoutes.homeas the navigation stack base (Get.offAllNamed(AppRoutes.home)followed byGet.toNamed(continueRoute)), persists_storage.saveRouteStack([AppRoutes.home, continueRoute]), enabling standard back-button and swipe-back navigation directly toHome. If no continue route is present, falls back cleanly toAppRoutes.toHome().
- Upon completing authentication (password login, Google SSO, signup verification OTP, or email confirmation token link), client consumes and immediately purges the stored continue route (
- Universal Deep Linking Specification (
rexone://):- Custom Scheme Authority: Canonical URI scheme is strictly
rexone://(e.g.rexone://ai,rexone://payment,rexone://profile,rexone://settings,rexone://notifications). Legacy or compound schemes likerexonemobile://are rejected. - Native Platform Registrations:
- iOS (
ios/Runner/Info.plist): Registered underCFBundleURLTypes->CFBundleURLSchemeswith<string>rexone</string>. - Android (
android/app/src/main/AndroidManifest.xml): Configured with<intent-filter>forandroid.intent.action.VIEWwithandroid:scheme="rexone".
- iOS (
- Flutter Routing Gateway (
DeepLinkService&AppRoutes.handleDeepLink):- Native stream and initial cold-start links are listened to via
app_linksinDeepLinkService. AppRoutes.handleDeepLinkparses URIs viaAppRoutes.resolveNotificationRoute:- If user is unauthenticated: stores route as
continueRouteand prompts/redirects to/auth. Once authenticated,navigateContinueURLrestores the requested screen atopHome. - If user is already authenticated: guarantees
Homeis the base of the backstack (Get.offAllNamed(home)thenGet.toNamed(target)) so native back navigation always leads back toHome.
- If user is unauthenticated: stores route as
- Native stream and initial cold-start links are listened to via
- Custom Scheme Authority: Canonical URI scheme is strictly
- Session Purge on Sign Out:
- Explicit sign-out (
signOut) and auth dialog dismissal (handleClose) automatically clearcontinueUrlfrom local storage to prevent stale redirects.
- Explicit sign-out (
RexOne includes a built-in, zero-friction rebranding engine executed via a single master command from rexone-core:
# Full ecosystem synchronization (Core API, Web SPA, and Mobile Flutter simultaneously):
./scripts/rebrand.sh brand.config.jsonFor the comprehensive guide and operational matrix, see docs/REBRANDING.md.
- RexOne Core (Rails API):
- Synchronizes
config/app_config.rbdefault parameters (RAILS_JWT_SECRET_KEY,SMTP_DOMAIN,FROM_EMAIL,S3_BUCKET). - Updates
docker-compose.yaml(Production / Coolify) anddocker-compose.dev.yamlwith kebab-case container slugs (prod-<kebab>-api,prod-<kebab>-waka,prod-<kebab>-media,prod-<kebab>-db,<kebab>-garage). - Synchronizes PostgreSQL database names (
<snake>_production,<snake>_development) and Garage S3 bucket names (<kebab>). - Updates database backup scripts (
backup_db.sh,backup_garage.sh,dev_garage.sh,prod_garage_init.sh). - Updates checked-in
.env.exampletemplates with matching parameter examples.
- Synchronizes
- RexOne Web (React SPA):
- Synchronizes
package.jsonpackage name (<kebab>-web) and HTML application titles. - Synchronizes branding locales (
src/locales/en.json,src/locales/my.json). - Updates public brand assets (
public/brand/logo.png,public/favicon.png) and image alt tokens (src/assets/index.ts). - Synchronizes
scripts/uat.shandscripts/prod.shtarget API hostnames (https://api.domain.com,https://uat.api.domain.com). - Synchronizes checked-in
.env.exampletemplates.
- Synchronizes
- RexOne Mobile (Flutter Client):
- Updates application display names dynamically in Android (
AndroidManifest.xmlviaappName) and iOS (Info.plistviaCFBundleDisplayName). - Synchronizes native application ID and iOS bundle ID (
com.brand.appandcom.brand.app.uat). - Synchronizes release keystore filenames (
<flat>-upload-keystore.jks) inbuild.gradle.kts. - Synchronizes Dart
pubspec.yamlpackage name and updates all package imports acrosslib/,test/, andintegration_test/. - Synchronizes parameter contracts inside
./scripts/release_android.sh,./scripts/release_ios.sh, and.github/workflows/build_android.yaml. - Generates native launcher icons across all screen densities from source logo via
flutter_launcher_icons.
- Updates application display names dynamically in Android (
- Secret Isolation Protocol (Rule 5 & Law U16):
- Automation NEVER touches live gitignored
.envfiles (.env,.env.dev,.env.prod,.env.uat). - Product creators apply live secret values manually using synchronized
.env.examplereference files.
- Automation NEVER touches live gitignored