フロー & ペイウォールイベントの処理 - Kotlin Multiplatform

このガイドでは、購入・復元・プロダクト選択・フローのレンダリングに関するイベント処理について説明します。ボタン処理(フローを閉じる、リンクを開くなど)も別途実装する必要があります。詳細はフローアクションの処理に関するガイドをご覧ください。

Flow Builder または Paywall Builder で設定されたフローやペイウォールは、購入や復元のために追加コードは不要です。ただし、アプリが応答できるイベントが生成されます。これらのイベントには、ボタン操作(閉じるボタン、URL、プロダクト選択など)や購入関連アクションの通知が含まれます。これらのイベントへの対応方法については、以下をご覧ください。

モバイルアプリのフロースクリーンで発生するプロセスを制御・監視するには、AdaptyUIFlowsEventsObserver インターフェースのメソッドを実装し、AdaptyUI.setFlowsEventsObserver() でオブザーバーを登録してください。一部のメソッドはよく使われるシナリオを自動的に処理するデフォルト実装が用意されているため、変更したいメソッドのみオーバーライドしてください。

AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver {
    // override only the methods you want to change
})

これらのメソッドは、フローイベントに応答するカスタムロジックを追加する場所です。view.dismiss() を使用してフローを閉じたり、その他の必要なカスタム動作を実装したりできます。なお、dismiss() はサスペンド関数です。コールバック内では、オブザーバーの mainUiScope を使って起動してください:mainUiScope.launch { view.dismiss() }

ユーザー生成イベント

フローの表示と非表示

フローが表示または非表示になると、次のメソッドが呼び出されます:

override fun flowViewDidAppear(view: AdaptyUIFlowView) {
    // Handle flow appearance
    // You can track analytics or update UI here
}

override fun flowViewDidDisappear(view: AdaptyUIFlowView) {
    // Handle flow disappearance
    // You can track analytics or update UI here
}
  • iOS では、ユーザーがフロー内のウェブペイウォールボタンをタップしてアプリ内ブラウザでウェブペイウォールが開いたときにも、flowViewDidAppear が呼び出されます。
  • iOS では、フローからアプリ内ブラウザで開いたウェブペイウォールが画面から消えたときにも、flowViewDidDisappear が呼び出されます。
イベントの例(クリックして展開)
// Flow appeared
{
  // No additional data
}

// Flow disappeared
{
  // No additional data
}

プロダクトの選択

ユーザーが購入するプロダクトを選択すると、このメソッドが呼び出されます:

override fun flowViewDidSelectProduct(view: AdaptyUIFlowView, productId: String) {
    // Handle product selection
    // You can update UI or track analytics here
}
イベントの例(クリックして展開)
{
  "productId": "premium_monthly"
}

購入の開始

ユーザーが購入プロセスを開始すると、このメソッドが呼び出されます:

override fun flowViewDidStartPurchase(view: AdaptyUIFlowView, product: AdaptyPaywallProduct) {
    // Handle purchase start
    // You can show loading indicators or track analytics here
}

オブザーバーモードでは、フローから開始された購入はAdaptyUIObserverModeResolverに配信されます。

イベントの例(クリックして展開)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

購入の成功、キャンセル、または保留

購入が完了すると、このメソッドが呼び出されます。デフォルトでは何も行われません。購入後もフローは開いたままなので、ユーザーがアクセスを取得したら view.dismiss() を自分で呼び出してください。

override fun flowViewDidFinishPurchase(
    view: AdaptyUIFlowView,
    product: AdaptyPaywallProduct,
    purchaseResult: AdaptyPurchaseResult
) {
    when (purchaseResult) {
        is AdaptyPurchaseResult.Success -> {
            // Check if user has access to premium features
            if (purchaseResult.profile.accessLevels["premium"]?.isActive == true) {
                mainUiScope.launch { view.dismiss() }
            }
        }
        AdaptyPurchaseResult.Pending -> {
            // Handle pending purchase (e.g., user will pay offline with cash)
        }
        AdaptyPurchaseResult.UserCanceled -> {
            // Handle user cancellation
        }
    }
}
イベントの例(クリックして展開)
// Successful purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "Success",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  }
}

// Pending purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "Pending"
  }
}

// User canceled purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "UserCanceled"
  }
}

購入が成功した場合は、フロー画面を閉じることをおすすめします。

購入失敗

エラーにより購入が失敗した場合、このメソッドが呼び出されます。これには、StoreKit/Google Play Billing のエラー(支払い制限、無効なプロダクト、ネットワーク障害)、トランザクション検証の失敗、およびシステムエラーが含まれます。なお、ユーザーによるキャンセルは flowViewDidFinishPurchase をキャンセル結果で呼び出すため、このメソッドはトリガーされません。保留中の支払いもこのメソッドをトリガーしません。

override fun flowViewDidFailPurchase(
    view: AdaptyUIFlowView,
    product: AdaptyPaywallProduct,
    error: AdaptyError
) {
    // Add your purchase failure handling logic here
    // For example: show error message, retry option, or custom error handling
}
イベント例(クリックして展開)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  }
}

リストアの開始

ユーザーがリストア処理を開始すると、このメソッドが呼び出されます:

override fun flowViewDidStartRestore(view: AdaptyUIFlowView) {
    // Handle restore start
    // You can show loading indicators or track analytics here
}

購入の復元成功

購入の復元が成功した場合、このメソッドが呼び出されます。デフォルトでは何も行いません — 復元後もフローは開いたままであり、明示的に閉じるまで表示され続けます:

override fun flowViewDidFinishRestore(view: AdaptyUIFlowView, profile: AdaptyProfile) {
    // Add your successful restore handling logic here
    // For example: show success message, update UI, or dismiss the flow

    // Check if user has access to premium features
    if (profile.accessLevels["premium"]?.isActive == true) {
        mainUiScope.launch { view.dismiss() }
    }
}
イベントの例(クリックして展開)
{
  "profile": {
    "accessLevels": {
      "premium": {
        "id": "premium",
        "isActive": true,
        "expiresAt": "2024-02-15T10:30:00Z"
      }
    },
    "subscriptions": [
      {
        "vendorProductId": "premium_monthly",
        "isActive": true,
        "expiresAt": "2024-02-15T10:30:00Z"
      }
    ]
  }
}

必要な accessLevel をユーザーが持っている場合は、画面を閉じることをおすすめします。確認方法については、サブスクリプションのステータスを参照してください。

復元の失敗

Adapty.restorePurchases() が失敗した場合、このメソッドが呼び出されます:

override fun flowViewDidFailRestore(view: AdaptyUIFlowView, error: AdaptyError) {
    // Add your restore failure handling logic here
    // For example: show error message, retry option, or custom error handling
}
イベントの例(クリックして展開)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Webペイメントナビゲーションの完了

ユーザーがウェブペイウォールを使用して購入プロセスを開始した場合、このメソッドが呼び出されます:

override fun flowViewDidFinishWebPaymentNavigation(
    view: AdaptyUIFlowView,
    product: AdaptyPaywallProduct?,
    error: AdaptyError?
) {
    if (error != null) {
        // Handle web payment navigation error
    } else {
        // Handle successful web payment navigation
    }
}
イベントの例(クリックして展開)
// Successful web payment navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": null
}

// Failed web payment navigation
{
  "product": null,
  "error": {
    "code": "web_payment_failed",
    "message": "Web payment navigation failed",
    "details": {
      "underlyingError": "Network connection error"
    }
  }
}

データの取得とレンダリング

プロダクト読み込みエラー

初期化時にプロダクトを渡さなかった場合、AdaptyUI はサーバーから必要なオブジェクトを自動で取得します。この操作が失敗した場合、AdaptyUI は次のメソッドを呼び出してエラーを通知します。

override fun flowViewDidFailLoadingProducts(view: AdaptyUIFlowView, error: AdaptyError) {
    // Add your product loading failure handling logic here
    // For example: show error message, retry option, or custom error handling
}
イベント例(クリックして展開)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

レンダリングエラーとランタイムエラー

インターフェースのレンダリング中にエラーが発生した場合、またはその他の購入以外のランタイムエラーが発生した場合、このメソッドによって報告されます。デフォルトでは、エラー発生時にフローが閉じられます。フローを開いたままにしたり、独自のハンドリングを追加したりするには、このメソッドをオーバーライドしてください:

override fun flowViewDidReceiveError(view: AdaptyUIFlowView, error: AdaptyError) {
    // Handle the error
    // The default implementation dismisses the flow;
    // once you override this method, dismissal is up to you
}
イベント例(クリックして展開)
{
    "error": {
        "code": "rendering_failed",
            "message": "Failed to render flow interface",
            "details": {
            "underlyingError": "Invalid flow configuration"
        }
    }
}

通常の状況では、このようなエラーは発生しないはずです。もし遭遇した場合は、ぜひお知らせください。

アナリティクスイベント

flowViewDidReceiveAnalyticEvent コールバックは、フローからのカスタムアナリティクスイベント用に予約されています。フローはまだこれらのイベントをコードに送出しないため、実装する必要はありません:

override fun flowViewDidReceiveAnalyticEvent(
    view: AdaptyUIFlowView,
    name: String,
    paramsJsonString: String
) {
    // Reserved for custom analytic events from a flow
}

Android のシステムバックボタン

デフォルトでは、Android のシステムバックボタンやバックジェスチャーでフローを閉じることはできません。デフォルトの flowViewDidPerformAction 実装は、CloseAction の場合のみフローを閉じ、AndroidSystemBackAction は無視します。そのため、ユーザーは Close ボタンやビルダーの on_device_back アクションなど、あなたが定義したパスを通じてフローを離れます。システムバックボタンでフローを閉じたい場合は、アクションを自分で処理してください。

override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) {
    when (action) {
        is AdaptyUIAction.CloseAction ->
            mainUiScope.launch { view.dismiss() } // default behavior
        is AdaptyUIAction.AndroidSystemBackAction ->
            mainUiScope.launch { view.dismiss() } // not handled by default
        is AdaptyUIAction.OpenUrlAction ->
            AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior
        else -> Unit
    }
}

フロー操作の処理に関するガイドで、すべての操作の一覧をご確認ください。

ペイウォールビルダーで設定されたペイウォールは、購入や復元を行うために追加のコードは必要ありません。ただし、アプリが応答できるいくつかのイベントが発生します。これらのイベントには、ボタン操作(閉じるボタン、URL、プロダクト選択など)や、ペイウォール上で行われた購入関連アクションの通知が含まれます。これらのイベントへの対応方法については、以下をご覧ください。

このガイドは新しいペイウォールビルダーのペイウォール専用です。

モバイルアプリのペイウォール画面で発生するプロセスを制御・監視するには、AdaptyUIPaywallsEventsObserver インターフェースのメソッドを実装します。一部のメソッドには、一般的なシナリオを自動的に処理するデフォルト実装が用意されています。

これらのメソッドは、ペイウォールイベントへの応答としてカスタムロジックを追加する場所です。view.dismiss() でペイウォールを閉じたり、必要に応じてその他のカスタム動作を実装したりできます。

ユーザー生成イベント

ペイウォールの表示と非表示

ペイウォールが表示または非表示になったとき、以下のメソッドが呼び出されます:

override fun paywallViewDidAppear(view: AdaptyUIPaywallView) {
    // Handle paywall appearance
    // You can track analytics or update UI here
}

override fun paywallViewDidDisappear(view: AdaptyUIPaywallView) {
    // Handle paywall disappearance
    // You can track analytics or update UI here
}
  • iOSでは、ユーザーがペイウォール内のウェブペイウォールボタンをタップしてインアプリブラウザでウェブペイウォールが開いた場合にも、paywallViewDidAppearが呼び出されます。
  • iOSでは、ペイウォールからインアプリブラウザで開いたウェブペイウォールが画面から消えた場合にも、paywallViewDidDisappearが呼び出されます。
イベントの例(クリックして展開)
// Paywall appeared
{
  // No additional data
}

// Paywall disappeared
{
  // No additional data
}

プロダクトの選択

ユーザーが購入するプロダクトを選択すると、このメソッドが呼び出されます:

override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) {
    // Handle product selection
    // You can update UI or track analytics here
}
イベント例(クリックして展開)
{
  "productId": "premium_monthly"
}

購入開始

ユーザーが購入プロセスを開始すると、このメソッドが呼び出されます:

override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) {
    // Handle purchase start
    // You can show loading indicators or track analytics here
}
イベント例(クリックして展開)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

購入成功、キャンセル、または保留中

購入が成功した場合、このメソッドが呼び出されます。デフォルトでは、ユーザーによってキャンセルされた場合を除き、自動的にペイウォールを閉じます:

override fun paywallViewDidFinishPurchase(
    view: AdaptyUIPaywallView,
    product: AdaptyPaywallProduct,
    purchaseResult: AdaptyPurchaseResult
) {
    when (purchaseResult) {
        is AdaptyPurchaseResult.Success -> {
            // Check if user has access to premium features
            if (purchaseResult.profile.accessLevels["premium"]?.isActive == true) {
                view.dismiss()
            }
        }
        AdaptyPurchaseResult.Pending -> {
            // Handle pending purchase (e.g., user will pay offline with cash)
        }
        AdaptyPurchaseResult.UserCanceled -> {
            // Handle user cancellation
        }
    }
}
イベント例(クリックして展開)
// Successful purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "Success",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  }
}

// Pending purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "Pending"
  }
}

// User canceled purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "UserCanceled"
  }
}

購入が成功した場合は、ペイウォール画面を閉じることをお勧めします。

購入失敗

購入がエラーにより失敗した場合、このメソッドが呼び出されます。これには、StoreKit/Google Play Billing エラー(支払い制限、無効なプロダクト、ネットワーク障害)、トランザクション検証の失敗、システムエラーが含まれます。なお、ユーザーによるキャンセルは、キャンセル結果として paywallViewDidFinishPurchase をトリガーします。また、保留中の支払いはこのメソッドをトリガーしません。

override fun paywallViewDidFailPurchase(
    view: AdaptyUIPaywallView,
    product: AdaptyPaywallProduct,
    error: AdaptyError
) {
    // Add your purchase failure handling logic here
    // For example: show error message, retry option, or custom error handling
}
イベント例(クリックして展開)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  }
}

リストアの開始

ユーザーがリストアプロセスを開始した場合、このメソッドが呼び出されます:

override fun paywallViewDidStartRestore(view: AdaptyUIPaywallView) {
    // Handle restore start
    // You can show loading indicators or track analytics here
}

購入の復元に成功した場合

購入の復元が成功した場合、以下のメソッドが呼び出されます。

override fun paywallViewDidFinishRestore(view: AdaptyUIPaywallView, profile: AdaptyProfile) {
    // Add your successful restore handling logic here
    // For example: show success message, update UI, or dismiss paywall
    
    // Check if user has access to premium features
    if (profile.accessLevels["premium"]?.isActive == true) {
        view.dismiss()
    }
}
イベントの例(クリックして展開)
{
  "profile": {
    "accessLevels": {
      "premium": {
        "id": "premium",
        "isActive": true,
        "expiresAt": "2024-02-15T10:30:00Z"
      }
    },
    "subscriptions": [
      {
        "vendorProductId": "premium_monthly",
        "isActive": true,
        "expiresAt": "2024-02-15T10:30:00Z"
      }
    ]
  }
}

ユーザーが必要な accessLevel を持っている場合は、画面を閉じることをお勧めします。確認方法については、サブスクリプションのステータスを参照してください。

復元失敗時

Adapty.restorePurchases() が失敗した場合、このメソッドが呼び出されます:

override fun paywallViewDidFailRestore(view: AdaptyUIPaywallView, error: AdaptyError) {
    // Add your restore failure handling logic here
    // For example: show error message, retry option, or custom error handling
}
イベント例(クリックして展開)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

ウェブ決済ナビゲーションの完了

ユーザーがウェブペイウォールを使用して購入プロセスを開始した場合、このメソッドが呼び出されます:

override fun paywallViewDidFinishWebPaymentNavigation(
    view: AdaptyUIPaywallView,
    product: AdaptyPaywallProduct?,
    error: AdaptyError?
) {
    if (error != null) {
        // Handle web payment navigation error
    } else {
        // Handle successful web payment navigation
    }
}
イベントの例(クリックして展開)
// Successful web payment navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": null
}

// Failed web payment navigation
{
  "product": null,
  "error": {
    "code": "web_payment_failed",
    "message": "Web payment navigation failed",
    "details": {
      "underlyingError": "Network connection error"
    }
  }
}

データの取得とレンダリング

プロダクトの読み込みエラー

初期化時にプロダクトを渡さない場合、AdaptyUI はサーバーから必要なオブジェクトを自動的に取得します。この操作が失敗した場合、AdaptyUI は次のメソッドを呼び出してエラーを通知します。

override fun paywallViewDidFailLoadingProducts(view: AdaptyUIPaywallView, error: AdaptyError) {
    // Add your product loading failure handling logic here
    // For example: show error message, retry option, or custom error handling
}
イベントの例(クリックして展開)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

レンダリングエラー

インターフェースのレンダリング中にエラーが発生した場合、このメソッドで報告されます:

override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) {
    // Handle rendering error
    // In a normal situation, such errors should not occur
    // If you come across one, please let us know
}
イベント例(クリックして展開)
{
    "error": {
        "code": "rendering_failed",
            "message": "Failed to render paywall interface",
            "details": {
            "underlyingError": "Invalid paywall configuration"
        }
    }
}

通常の状況ではこのようなエラーは発生しないため、もし遭遇した場合はお知らせください。