Version: 5.0 | Date: December 23, 2025

This document covers the main app-server communication systems:

App-Server Sync Strategy

Core Priorities

Sync Flow Diagram

┌─────────────────┐                              ┌─────────────────┐
│   APP STARTS    │                              │     SERVER      │
└────────┬────────┘                              └────────┬────────┘
         │                                                 │
    ┌────▼──────────────┐                                 │
    │ Test Internet     │                                 │
    │ Speed (3 tests)   │                                 │
    │ Minimum: 1.5 Mbps │                                 │
    └────┬──────────────┘                                 │
         │                                                 │
         │ (1) Send Handshake with JWT                    │
         ├──────────────────────────────────────────────>│
         │   Authorization: Bearer {JWT}                  │
         │   {analytics, device_id, purchases}            │
         │                                                │
         │ (2) Return Manifest + User Data                │
         │<───────────────────────────────────────────────┤
         │   {"json_manifest": {...},                     │
         │    "ai_usage": {...},                          │
         │    "subscription": {...}}                      │
         │                                                │
    ┌────▼──────────────┐                                │
    │ Update user_profile│                                │
    │ with ai_usage &   │                                │
    │ subscription      │                                │
    └────┬──────────────┘                                │
         │                                                │
    ┌────▼──────────────┐                                │
    │ Analyze manifest: │                                │
    │ - Courses to ADD  │                                │
    │ - Courses to UPDATE│                                │
    │ - Courses to DELETE│                                │
    │ - System JSONs    │                                │
    └────┬──────────────┘                                │
         │                                                │
    ┌────▼──────────────┐                                │
    │ Check storage &   │                                │
    │ network type      │                                │
    └────┬──────────────┘                                │
         │                                                │
    ┌────▼──────────────┐          ┌──────────────┐      │
    │ Download new      │─────────>│  Cloudflare  │      │
    │ courses (with     │<─────────│      R2      │      │
    │ user confirmation)│          └──────────────┘      │
    └────┬──────────────┘                                │
         │                                                │
    ┌────▼──────────────┐                                │
    │ Batch UPDATE      │                                │
    │ courses & system  │                                │
    │ JSONs             │                                │
    └────┬──────────────┘                                │
         │                                                │
    ┌────▼──────────────┐                                │
    │ DELETE removed    │                                │
    │ courses (safe)    │                                │
    └────┬──────────────┘                                │
         │                                                │
    [Sync Complete]                                      │

Step-by-Step Process Walkthrough

1 Internet Speed Test

Before initiating sync, the app performs 3 speed tests using Cloudflare CDN (1MB download each). The average speed must be ≥1.5 Mbps to proceed. If the speed test server is unreachable, sync proceeds anyway.

Speed Test URL: https://speed.cloudflare.com/__down?bytes=1000000
Minimum Speed: 1.5 Mbps (averaged across 3 tests)
2 Build Request Payload

The app builds the handshake payload with analytics data, device ID, and pending purchases. Note: user_id is NOT included - it's extracted from JWT on the server.

{
  "analytics": {
    "current_course": "C14ENB",
    "voice": "Female",
    "background": "None",
    "induction": "Randomize",
    "deepening": "Randomize",
    "audio_usage": {
      "1": {"title": "How to Use These Sessions", "no_of_plays": 1},
      "2": {"title": "Release Social Anxiety", "no_of_plays": 3}
    }
  },
  "device_id": "unique-device-identifier-string",
  "purchases": [
    {
      "product_id": "h01enb",
      "purchase_id": "GPA.1234-5678",
      "platform": "android",
      "status": "pending_validation"
    }
  ]
}
3 Send Handshake Request

The app sends the payload to the handshake endpoint with JWT authentication:

URL: https://kagtryxgjwavupzlmlzv.supabase.co/functions/v1/handshake
Method: POST
Headers:
  Authorization: Bearer {user_jwt_from_supabase_session}
  Content-Type: application/json

Dev Mode URLs:
  Emulator: http://localhost:8080/handshake
  Device: http://{configured_ip}:8080/handshake
4 Server Response (Complete Response)

Server returns JSON manifest, AI usage data, and subscription status. All JSON content uses LinkedHashMap to preserve field order for consistent checksums.

{
  "json_manifest": {
    "background.json": {
      "checksum": "a1b2c3d4...",
      "content": { /* full JSON content */ }
    },
    "core_segments.json": {
      "checksum": "e5f6g7h8...",
      "content": { /* full JSON content */ }
    },
    "FAQs.json": { ... },
    "shop.json": { ... },
    "ads.json": { ... },
    "C14ENB.json": { ... }
  },
  "ai_usage": {
    "weekly_limit_credits": 10,
    "weekly_used_credits": 2,
    "weekly_reset_date": "2025-12-30T00:00:00Z",
    "bank_limit_credits": 100,
    "bank_used_credits": 5
  },
  "subscription": {
    "subscription_id": "sub_12345",
    "external_subscription_id": "GPA.sub-12345",
    "subscription_type": "premium",
    "status": "active",
    "start_date": "2025-01-01T00:00:00Z",
    "expiry_date": "2026-01-01T00:00:00Z",
    "auto_renew": true,
    "plan": "monthly-plan"
  }
}
5 Update User Profile

After receiving the response, the app updates user_profile.json with:

6 Sync Analysis (ProgressiveSyncOrchestrator)

The orchestrator analyzes the manifest to categorize operations:

Category Condition Action
Courses to ADD In server manifest, not in local (and not disabled) Full download with user confirmation
Courses to UPDATE Checksum differs Batch update changed files
Courses to DELETE In local, not in server manifest Safe deletion (profile first)
System JSONs Checksum differs Batch update
7 Progressive Course Download

For each new course to ADD, the app:

8 Batch Updates

For courses and system JSONs needing updates:

9 Safe Course Deletion

For courses no longer in entitlements:

Sync Phases (SyncPhase enum)

Phase Description
analyzingAnalyzing sync requirements
checkingStorageVerifying device storage space
waitingForUserConfirmationWaiting for user to confirm download
downloadingCourseDownloading course files
addingCoursesAdding new courses to profile
updatingSystemUpdating system JSONs
updatingCoursesUpdating existing courses
deletingCoursesRemoving outdated courses
updatingProfileUpdating user profile
completedSync completed successfully
failedSync failed
cancelledByUserUser cancelled sync

System JSONs (Protected)

These system JSONs cannot be deleted and are always synced:

Critical Implementation Details

MD5 Checksum Computation

⚠️ CRITICAL: Checksum consistency requirements

JSON Processing Order

Processing order for system JSONs:
  1. background.json
  2. core_segments.json
  3. FAQs.json
  4. shop.json
  5. ads.json
  6. Course JSONs (alphabetical: C10ENB.json → C14ENB.json → H23ENB.json)

Key Concerns and Mitigations

Concern: Download Interruption

Risk: Network failure during course download

Mitigation:

Concern: Storage Space

Risk: Device may not have space for new files

Mitigation:

Concern: Speed Test Failure

Risk: Cloudflare speed test server unreachable

Mitigation:

💳 Course Purchase (One-Time)

Key Points:

Product ID Format

Platform Format Example
Android (Google Play) Lowercase course code h01enb
iOS (App Store) Bundle prefix + lowercase me.hypnoelp.app.h01enb

Purchase Flow Diagram

┌─────────────────┐           ┌─────────────────┐           ┌─────────────────┐
│      APP        │           │  STORE API      │           │     SERVER      │
│                 │           │ (Google/Apple)  │           │                 │
└────────┬────────┘           └────────┬────────┘           └────────┬────────┘
         │                              │                              │
         │ (1) User selects course      │                              │
         │     App normalizes product ID│                              │
         │                              │                              │
         │ (2) buyConsumable()          │                              │
         ├─────────────────────────────>│                              │
         │                              │                              │
         │ (3) PurchaseDetails          │                              │
         │     (NOT acknowledged yet)   │                              │
         │<─────────────────────────────┤                              │
         │                              │                              │
         │ (4) Check if already recorded│                              │
         │     in user_profile.json     │                              │
         │                              │                              │
         │ (5) Send to /validate_purchase                              │
         ├──────────────────────────────────────────────────────────>│
         │   {purchase_token, platform, │                              │
         │    product_id, course_code,  │                              │
         │    voice}                    │                              │
         │                              │                (6) Validate │
         │                              │<─────────────── with Store  │
         │                              │ "Valid" ──────────────────>│
         │                              │                              │
         │                              │                (7) Update DB │
         │                              │                              │
         │ (8) Success Response         │                              │
         │<──────────────────────────────────────────────────────────┤
         │                              │                              │
         │ (9) NOW acknowledge purchase │                              │
         ├─────────────────────────────>│                              │
         │     completePurchase()       │                              │
         │                              │                              │
         │ (10) Add to user profile     │                              │
         │      Trigger sync            │                              │

Step-by-Step Process

1 Initialize & Check Existing Purchases

Before initiating purchase, the app:

2 Query Product & Initiate Purchase
// Query product details from store
final response = await _iap.queryProductDetails({normalizedProductId});

// Initiate consumable purchase (repeatable)
final result = await _iap.buyConsumable(purchaseParam: purchaseParam);
3 Wait for Purchase Completion

The app waits up to 20 minutes for purchase completion via purchase stream. Duplicate purchases are detected by checking user_profile.json.

4 Extract Platform-Specific Data
Field Android iOS
Verification Data purchase_token from serverVerificationData receipt_data from method channel
Receipt Source Google Play Billing Bundle.main.appStoreReceiptURL
Method Channel N/A app_receiptgetAppReceipt
5 Send to Server for Validation
{
  "product_id": "h01enb",
  "purchase_id": "GPA.1234-5678-9012",
  "transaction_date": "1703347200000",
  "status": "PurchaseStatus.purchased",
  "platform": "android",
  "purchase_token": "token-from-google-play",
  "course_code": "H01ENB",
  "voice": "Female"
}

For iOS:

{
  "product_id": "me.hypnoelp.app.h01enb",
  "purchase_id": "1000000123456789",
  "transaction_date": "1703347200000",
  "status": "PurchaseStatus.purchased",
  "platform": "ios",
  "receipt_data": "base64-encoded-app-receipt...",
  "course_code": "H01ENB",
  "voice": "Female"
}
6 Server Validates & Returns Response

Server validates with Google Play API or Apple's verifyReceipt API, then returns:

{
  "status": "success",
  "message": "Course purchase validated",
  "acknowledged": true,
  "course": {
    "course_code": "H01ENB",
    "title": "Confidence Building",
    "voice": "Female",
    "purchased_at": "2025-12-23T12:00:00Z"
  }
}
7 Acknowledge Purchase (ONLY after server validation)
// Called ONLY after server confirms purchase is valid
await _iap.completePurchase(purchaseDetails);
⚠️ CRITICAL: If server validation fails, DO NOT acknowledge. Google/Apple will auto-refund after 3 days.
8 Update Profile & Trigger Sync

📅 Subscription Purchase

Key Points:

Subscription Product Configuration

Android (Google Play)

Setting Value
Product ID hypnoelp_subscriptions
Base Plans Multiple (monthly-plan, quarterly-plan, yearly-plan, etc.)
Offer Tags Configured per base plan

iOS (App Store)

Setting Value
Product ID me.hypnoelp.app.subscription_1month
Duration 1 month
Billing Period P1M

Subscription Flow Diagram

┌─────────────────┐                              ┌─────────────────┐
│      APP        │                              │     SERVER      │
└────────┬────────┘                              └────────┬────────┘
         │                                                 │
         │ (1) querySubscriptionPlans()                   │
         │     Returns: plans with pricing, free trials   │
         │                                                 │
         │ (2) User selects plan                          │
         │                                                 │
         │ (3) purchaseSubscription()                     │
         │     - Android: GooglePlayPurchaseParam         │
         │       with ChangeSubscriptionParam (upgrade)   │
         │     - iOS: PurchaseParam                       │
         │                                                 │
         │ (4) buyNonConsumable()                         │
         │     Wait for completion (20 min timeout)       │
         │                                                 │
         │ (5) Extract purchase data                      │
         │     - Convert date to ISO 8601                 │
         │     - Get receipt/token                        │
         │                                                 │
         │ (6) Send to /validate_purchase                 │
         ├──────────────────────────────────────────────>│
         │   {platform, purchase_token/receipt_data,      │
         │    base_plan_id, transaction_date (ISO 8601)}  │
         │                                                 │
         │ (7) Validation Response                        │
         │<──────────────────────────────────────────────┤
         │                                                 │
         │ (8) Acknowledge purchase                       │
         │     completePurchase()                         │
         │                                                 │
         │ (9) Update user_profile.json                   │
         │     - subscription object                      │
         │     - user_group = "premium"                   │

Free Trial Detection

Free trials are detected from pricing phases on Android:

// First pricing phase with priceAmountMicros == 0 indicates free trial
// Billing period format (ISO 8601 duration):
// P1D = 1 day, P7D = 7 days
// P1M = 1 month, P3M = 3 months
// P1Y = 1 year

if (firstPhase.priceAmountMicros == 0) {
  // Free trial phase
  freeTrialText = _formatBillingPeriod(billingPeriod);
  // e.g., "First 7 days free"
}

Upgrade/Downgrade (Android Only)

// If user has active subscription, enable upgrade/downgrade
if (_activeSubscription != null) {
  changeParam = ChangeSubscriptionParam(
    oldPurchaseDetails: _activeSubscription!,
    replacementMode: ReplacementMode.withTimeProration,
  );

  purchaseParam = GooglePlayPurchaseParam(
    productDetails: googlePlayDetails,
    changeSubscriptionParam: changeParam,
  );
}

Subscription Data Sent to Server

{
  "product_id": "hypnoelp_subscriptions",
  "purchase_id": "GPA.sub-1234-5678",
  "transaction_date": "2025-12-23T12:00:00.000Z",  // ISO 8601 format
  "status": "PurchaseStatus.purchased",
  "plan_title": "Monthly Plan",
  "plan_description": "Premium access for 1 month",
  "base_plan_id": "monthly-plan",
  "offer_id": "",
  "platform": "android",
  "purchase_token": "token-from-google-play"
}

🤖 AI Credits Purchase

Key Points:

AI Credits Purchase Data

{
  "product_id": "ai_credits_100",
  "purchase_id": "GPA.1234-5678-9012",
  "transaction_date": "1703347200000",
  "status": "PurchaseStatus.purchased",
  "platform": "android",
  "purchase_token": "token-from-google-play"
}

Server Response Updates

After successful AI credits purchase, the server updates the user's AI usage:

{
  "ai_usage": {
    "weekly_limit_credits": 10,
    "weekly_used_credits": 0,
    "weekly_reset_date": "2025-12-30T00:00:00Z",
    "bank_limit_credits": 200,  // Increased by purchase
    "bank_used_credits": 0
  }
}

📝 Logging

Overview: The app sends various log events to the server for monitoring, debugging, and analytics purposes. These logs help track user activities, sync operations, and billing events.

📤 App → Server (Log Event)

The app sends logging payloads with the following fields:

Example Log Payloads

✅ Sync Successful

{
    "user_id": "user_9372328",
    "log_type": "sync_module",
    "message": "Sync completed successfully",
    "origin": "app",
    "created_at": "2025-12-23T14:30:00Z"
}

🌐 Speed Test Result

{
    "user_id": "user_7339483",
    "log_type": "sync_module",
    "message": "Speed test: 5.23 Mbps (3 attempts average)",
    "origin": "app",
    "created_at": "2025-12-23T14:31:00Z"
}

❌ Sync Skipped - Slow Connection

{
    "user_id": "user_7339483",
    "log_type": "sync_module",
    "message": "Sync skipped: slow connection (0.87 Mbps)",
    "origin": "app",
    "created_at": "2025-12-23T14:35:00Z"
}

❌ Failed Download During Sync

{
    "user_id": "user_7339483",
    "log_type": "sync_module",
    "message": "No connection. Interrupted downloading file: H01ENB_audio_01.mp3",
    "origin": "app",
    "created_at": "2025-12-23T14:35:00Z"
}

💳 Purchase Initiated

{
    "user_id": "user_u38439493",
    "log_type": "billing",
    "message": "Course purchase initiated: H01ENB (Android)",
    "origin": "app",
    "created_at": "2025-12-23T14:40:00Z"
}

✅ Purchase Validated

{
    "user_id": "user_u38439493",
    "log_type": "billing",
    "message": "Course purchase validated: H01ENB - acknowledged to Google Play",
    "origin": "app",
    "created_at": "2025-12-23T14:41:00Z"
}

❌ Purchase Validation Failed

{
    "user_id": "user_u38439493",
    "log_type": "billing",
    "message": "Purchase validation failed: H01ENB - NOT acknowledged (will auto-refund)",
    "origin": "app",
    "created_at": "2025-12-23T14:41:00Z"
}

📋 User Report

{
    "user_id": "user_u38439493",
    "log_type": "user_report",
    "message": "Course H23ENB fails to load after purchase validation",
    "origin": "app",
    "created_at": "2025-12-23T14:40:00Z"
}

Common Log Types

📊 Summary

Endpoint Summary

Endpoint Purpose Auth
/functions/v1/handshake Sync, user data, manifest JWT Bearer
/functions/v1/validate_purchase Course, subscription, AI credits validation JWT Bearer
/functions/v1/app_logs Event logging JWT Bearer

Platform Support Matrix

Feature Android iOS
Course Purchase ✅ Google Play Billing ✅ StoreKit
Subscription ✅ Multi-base plan ✅ Monthly only
AI Credits ✅ Consumable ✅ Consumable
Upgrade/Downgrade ✅ With proration ❌ N/A (single plan)
Free Trial ✅ From pricing phases ✅ App Store managed
Receipt Retrieval serverVerificationData Method channel (app_receipt)

✅ Key Implementation Highlights