rc-error-handling について
Android 開発者向けに、RevenueCat SDK の購入関連エラー処理を説明するスキルです。例外の選択、エラーコード別の対応、ユーザー向けメッセージ、SDK 内部の再試行動作を扱います。
主な機能
- 01SDK 呼び出しに応じた例外型の選択
- 02安定したエラーコードによる処理
- 03ユーザーキャンセルと購入失敗の区別
- 04技術エラーからユーザー向け文言への変換
- 05重複する再試行ループの回避
活用シーン
- 01キャンセルまたは保留中の購入への対応
- 02ネットワークやストアのエラーへの対応
- 03技術ログを見せずに変換済みメッセージを表示
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ revenuecat/ ai-toolkit --skill rc-error-handlingインストール後、Claude Code で/rc-error-handlingを入力して読み込まれたか確認します。
詳細
エラー処理
フェーズ 1:理解する
Google Play Billing をそのまま使う場合は、すべての BillingResponseCode を列挙し、再試行可能なグループと再試行不可能なグループに分けて、バックオフ再試行のロジックを構築します。RevenueCat では、扱う型が PurchasesError ひとつにまとめられています。
public class PurchasesError(
val code: PurchasesErrorCode,
val underlyingErrorMessage: String? = null,
) {
val message: String // technical description, for logs
}
把握しておくべき重要な点:
PurchasesErrorCodeは、安定した読みやすいコードを持つクロスプラットフォームの列挙型です。error.messageは技術的な文字列です。UI ではなく、ログに記録してください。awaitPurchase()はPurchasesTransactionExceptionをスローします。この例外にはuserCancelled: Booleanフラグが追加されています。- それ以外のすべての
await*呼び出し(awaitOfferings、awaitGetProducts、awaitCustomerInfo、awaitRestore)はPurchasesExceptionをスローします。 - SDK は一時的な課金エラーやネットワークエラーを内部ですでに再試行します。エラーが呼び出し元に届いた時点で、SDK の再試行回数は上限に達しています。独自のバックオフループを追加する必要はありません。実装する再試行は、ユーザーが操作する「もう一度試す」ボタンだけです。
フェーズ 2:計画する
catch ブロックを書く前に、次の 3 点を決めます。
- どの
await*呼び出しをラップしますか?これによって例外の型が決まります。 - どのコードを個別に処理しますか?それ以外はすべて汎用の分岐に入ります。
- 処理する各コードに、どのユーザー向けメッセージを割り当てますか?
この表を使って PurchasesErrorCode の値を分類し、UX での対応を選びます。
| コード | 意味 | 対応 |
|---|---|---|
PurchaseCancelledError | ユーザーがフローを中断しました | 何もしません。userCancelled も true です。 |
ProductAlreadyPurchasedError | ユーザーはすでにこの商品を利用中です | CustomerInfo を更新し、エンタイトルメントを確認します。 |
PaymentPendingError | 購入が保留状態になりました | 保留中のメッセージを表示します。UpdatedCustomerInfoListener を待ちます。 |
NetworkError | 接続の問題によりリクエストが失敗しました | 再試行するようユーザーに促します。 |
StoreProblemError | Google Play の問題です | 再試行するか、Play ストアを更新するよう促します。 |
PurchaseNotAllowedError | デバイスまたはアカウントでは購入できません | 理由を説明するメッセージを表示します。 |
IneligibleError | ユーザーはこのオファーの対象ではありません | 基本プランを表示します。 |
例外型の判断:
| 呼び出し | キャッチする例外 | userCancelled を利用できますか? |
|---|---|---|
awaitPurchase() | PurchasesTransactionException | はい |
awaitRestore() | PurchasesException | いいえ |
awaitOfferings() | PurchasesException | いいえ |
awaitGetProducts() | PurchasesException | いいえ |
awaitCustomerInfo() | PurchasesException | いいえ |
フェーズ 3:実行する
購入エラー
まず userCancelled を確認し、該当する場合は何もせずに戻ります。その後、error.code に応じて分岐します。
try {
val result = Purchases.sharedInstance.awaitPurchase(params)
handleSuccess(result.customerInfo)
} catch (e: PurchasesTransactionException) {
if (e.userCancelled) return
when (e.error.code) {
PurchasesErrorCode.PaymentPendingError -> showPendingMessage()
PurchasesErrorCode.ProductAlreadyPurchasedError -> {
val info = Purchases.sharedInstance.awaitCustomerInfo()
handleSuccess(info)
}
PurchasesErrorCode.NetworkError -> showRetryDialog()
else -> showGenericError(userFacingMessage(e.error))
}
}
購入以外のエラー
PurchasesException をキャッチし、コードに応じて分岐します。利用できる場合は、オフライン時やキャッシュ済みの代替手段を使います。
try {
val offerings = Purchases.sharedInstance.awaitOfferings()
displayOfferings(offerings)
} catch (e: PurchasesException) {
when (e.error.code) {
PurchasesErrorCode.NetworkError -> showOfflineFallback()
else -> logError(e.error)
}
}
コードをユーザー向けメッセージにマッピングする
マッピング関数はひとつにまとめてください。e.error.message を UI に渡してはいけません。
fun userFacingMessage(error: PurchasesError): String = when (error.code) {
PurchasesErrorCode.PurchaseCancelledError -> ""
PurchasesErrorCode.NetworkError ->
"Please check your internet connection and try again."
PurchasesErrorCode.StoreProblemError ->
"There was a problem with Google Play. Please try again."
PurchasesErrorCode.ProductAlreadyPurchasedError ->
"You already have this subscription."
PurchasesErrorCode.PaymentPendingError ->
"Your payment is being processed. We'll notify you when it completes."
else -> "Something went wrong. Please try again."
}
チェックリスト
awaitPurchaseにはPurchasesTransactionExceptionを、それ以外にはPurchasesExceptionを選びました。error.codeによる分岐の前にuserCancelledを確認しました。PaymentPendingError、ProductAlreadyPurchasedError、NetworkErrorをそれぞれ固有のフローで処理しました。error.messageをログに記録し、ユーザーにはuserFacingMessageから取得したメッセージを表示しました。- SDK の呼び出しを囲む再試行ループは追加していません。再試行はユーザー操作によってのみ行われます。
参考資料
コメント
Markdown 対応