DocHub
Monthly subscription IAP — Android multi-plan with upgrade/downgrade, iOS single plan, free trial detection

Subscription Purchase

Key Points

  • Android: Single product (hypnoelp_subscriptions) with multiple base plans
  • iOS: Single monthly subscription (me.hypnoelp.app.subscription_1month)
  • Supports upgrade/downgrade with proration on Android
  • Free trial detection from pricing phases
  • Deferred acknowledgment (same as course purchases)

Product Configuration

Android (Google Play)

Setting Value
Product ID hypnoelp_subscriptions
Base Plans 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

  1. Query plansquerySubscriptionPlans() returns plans with pricing and free trial info
  2. User selects plan
  3. Initiate purchase — Android uses GooglePlayPurchaseParam (with ChangeSubscriptionParam for upgrades), iOS uses standard PurchaseParam
  4. BuybuyNonConsumable(), wait up to 20 minutes
  5. Extract data — convert date to ISO 8601, get receipt/token
  6. Validate — send to /functions/v1/validate_purchase
  7. AcknowledgecompletePurchase() only after server confirms
  8. Update profile — set subscription object, user_group = "premium"

Validation Payload

{
  "product_id": "hypnoelp_subscriptions",
  "purchase_id": "GPA.sub-1234-5678",
  "transaction_date": "2025-12-23T12:00:00.000Z",
  "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"
}

Note: transaction_date uses ISO 8601 format for subscriptions (not milliseconds).

Free Trial Detection (Android)

Free trials are detected from pricing phases:

// 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) {
  freeTrialText = _formatBillingPeriod(billingPeriod);
  // e.g., "First 7 days free"
}

Upgrade/Downgrade (Android Only)

if (_activeSubscription != null) {
  changeParam = ChangeSubscriptionParam(
    oldPurchaseDetails: _activeSubscription!,
    replacementMode: ReplacementMode.withTimeProration,
  );

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

iOS does not support upgrade/downgrade since there is only a single plan.