A unified architectural specification and cross-platform contract spanning RexOne Core (Rails), RexOne Web (React), and RexOne Mobile (Flutter).
RexOne establishes a disciplined, production-grade foundation 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 client version is strictly newer than the live published version (e.g. 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 sovereign, 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 "Acme Product" "com.acme.product" "path/to/logo.png" [BrandName] [domain.com]- 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-acme-api,prod-acme-waka,prod-acme-media,prod-acme-db,acme-garage). - Synchronizes PostgreSQL database names (
acme_production,acme_development) and Garage S3 bucket names (acme). - 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 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.acme.productandcom.acme.product.uat). - Synchronizes release keystore filenames (
acme-upload-keystore.jks) inbuild.gradle.kts. - 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