In-App Purchases & Subscriptions

アプリ内購入のApple返金リクエストに自動で対応するにはどうすればよいですか?

Apple返金オートメーションが、アプリ開発者の返金リクエスト対応をどのように簡素化し、手作業を減らせるのかをご紹介します。

5 min read
アプリ内購入のApple返金リクエストに自動で対応するにはどうすればよいですか?

Undelivered Consumption Percentage Non Zero Errorのせいで、ほぼ1日を無駄にしました。難しいエラーだったわけではありません。delivery Statusを DELIVERED以外の値に設定したまま、consumption Percentageを60000のままにしていたせいで、Appleはこの組み合わせを問答無用で拒否してきたのです。

回り道になりましたが、本題はここからです。

Apple返金リクエストへの自動返信方法を検討しているなら、それはほとんどのチームが想像するより小さな作業です。難しいのはAPI呼び出し自体ではなく、そこに入れるデータを用意することです。フィールドごとの詳しい解説は Apple CONSUMPTION_REQUEST explainedにあります。この記事では、誰も起きていなくても済むように仕組みを組み上げる方法を扱います。

重要なポイント

  • Apple返金オートメーションは、1つのwebhookと1つのPUTで構成されます。AppleがCONSUMPTION_REQUESTを送信し、あなたは12時間以内に返信します。

  • 必要なのはIn-App Purchaseキーであり、App Store Connect APIキーではありません。両者は互換性がありません。

  • 5つのフィールドのうち3つが必須で、そのうち2つにはエラーとしてしか現れない制約があります。

  • 顧客の同意がない場合は一切返信すべきではなく、同意を後から遡って取得することもできません。

  • エンドポイントはv2に移行しました。本番環境はapi.storekit.apple.comで、sandboxでは12時間ではなく5分しか与えられません。

開発者向けApple返金リクエストの実際の仕組み

顧客はreportaproblem.apple.comを開き、過去90日以内の購入を見つけ、理由を選択して送信します。App Store Server Notifications V2を設定していれば、あなたのエンドポイントは、トランザクション、プロダクト、選択された理由を含む署名付きJWSペイロードとしてCONSUMPTION_REQUESTを受け取ります。

そのwebhookが、apple store in app purchase refundのトリガーのすべてです。ダッシュボードには何も表示されず、確認すべきキューもありません。だからこそ多くのチームは、このシステムのせいで損失を出し始めてから数週間後にようやくその存在に気づくのです。

そこから12時間の制限時間が始まります。

あちこちで繰り返されているので、1つ訂正しておく価値があります。これはconsumable(消費型)に限った話ではありません。Appleの現在のドキュメントによれば、CONSUMPTION_REQUESTはあらゆるプロダクトタイプ、つまりconsumable、non-consumable(非消費型)、non-renewing subscription(自動更新なしのサブスクリプション)、auto-renewable subscription(自動更新サブスクリプション)のいずれでも届きます。古いガイドの中には、今もconsumableとサブスクリプションのみと書かれているものがありますが、それらは古い情報です。

「返信する」とは具体的に何を意味するのか

1つのエンドポイントにJSONボディをPUTします。

PUT https://api.storekit.apple.com/inApps/v2/transactions/consumption/{transactionId}

Sandboxはapi.storekit-sandbox.apple.comで、パスは同じです。成功時には200ではなく202 Acceptedが返されるため、ステータスコードをチェックしている人はここで引っかかります。

認証には、Users and Access内のIntegrationsでApp Store Connect上で生成するIn-App Purchaseキーで署名した、短寿命のES256 JWTを使用します。これはApp Store Connect APIキーではありません。.p8ファイルは作成時に一度だけダウンロードできるため、失くしてしまった場合は新しいキーを生成し、それを使っているすべての箇所をローテーションする必要があります。身をもって知っています。

5つのフィールドと、噛みつく2つのフィールド

ボディはConsumption Requestです。3つが必須、2つが任意です。

  • Customer Consented (必須)。本当に同意を得ている場合にのみtrueにします。これがすべての前提条件になるため、詳細は後述します。

  • Delivery Status (必須)。購入したものが届き、正常に機能したかどうかを示します。

  • Sample Content Provided (必須)。購入前に無料サンプル、トライアル、機能情報が提供されていたかどうかを示します。

  • Consumption Percentage (任意)。ミリ単位でどれだけ消費したかを示します。0から100000の範囲で、45.2パーセントなら45200になります。

  • Refund Preference (任意)。GRANT_FULL、GRANT_PRORATED、DECLINE、またはNO_PREFERENCEのいずれかです。

それぞれ半日ずつ無駄にしかねない、2つの制約があります。

Delivery StatusがDELIVERED以外の場合、Consumption Percentageは0でなければなりません。それ以外の値にするとUndelivered Consumption Percentage Non Zero Errorで失敗します。これが私の1日を奪った原因です。

また、auto-renewable subscriptionに対してGRANT_PRORATEDを送信する場合は、consumptionPercentageを一切含めないでください。Appleは請求期間内の経過時間からこれを計算し、含めるとConsumption Percentage Auto Renewable Subscription Errorで拒否されます。エラー名が少なくとも内容を説明してくれているのは、予想以上に親切でした。

同意は単なる形式ではなく、関門である

この点についてAppleのドキュメントは珍しく直接的です。顧客が同意していない場合は、通知に返信してはいけません。Customer Consentedをfalseにして返信するのではありません。返信しないのです。

また、リクエストが届いた後から同意を取得することもできません。同意は事前に存在していなければならず、つまりこの機能をリリースする前に、最初の争われた返金が起きる前に、利用規約やプライバシーポリシーにその文言を入れておく必要があります。Appleのガイダンスでは、同意は自由に、具体的に、十分な情報に基づいて、明確に与えられるべきであり、オプトインはオプトアウトより優れており、それを取得する責任はすべてあなたにあるとされています。

実務的には、購入データや利用データを返金リクエストの審査のためにAppleと共有する場合がある、という一文と、それを撤回する手段が必要です。必ず自身で法務レビューを受けてください。私は弁護士ではなく、これは法的アドバイスではありません。

開発者はApp Storeの返金リクエストをどのように自動化するのか

私が構築する順序で、パイプラインを示します。

  1. App Store ConnectのApp Information内でProduction Server URLを設定し、Version 2を選択します。Consumption requestはV1経由では届きません。

  2. どのフィールドも信頼する前に、Appleのルート証明書に対してJWSの署名チェーンを検証します。

  3. トランザクションをユーザーに照合します。購入時に付与したappAccountTokenがあれば近道になります。それがない場合、プロダクトと日付から逆引きすることになり、すぐに煩雑になります。

  4. 同意を確認します。同意がなければ返信しません。記録して次に進みます。

  5. 実際の利用状況を取得します。推定値やデフォルト値ではいけません。「これをどれだけ利用したか」に答えられないなら、それが自動化における本当のギャップです。

  6. 上記2つの制約を適用しながらConsumptionRequestを構築します。

  7. それをPUTし、202を確認し、送信したペイロードを保存します。

ステップ5でほとんどの試みが止まります。API呼び出し自体は半日の作業です。しかし、7週間前に行われた購入についてユーザーごとの正確な消費データを持っているかどうかは、まだ解決していないかもしれないデータモデリングの課題です。

本番環境で機能し続けるための条件

ハッピーパス以外に、4つのポイントがあります。

冪等性。Appleは通知を再送するため、あなたのハンドラーは同じ通知を2回受け取ることになります。notification UUIDで重複排除しないと、重複した返信を送信してしまいます。

実際に誰かを呼び出すアラート。ハンドラーが静かに失敗するのは、ハンドラーが存在しないよりも悪い状態です。なぜなら、あなたは自分が守られていると思い込んでしまうからです。

sandboxのテストハーネス。sandboxでは12時間ではなく5分しか与えられないため、手作業で気軽にテストすることはできません。スクリプト化してください。

そして、ケースごとに送信内容を記録しておくこと。返金が却下されたときには、どのpreferenceを送信し、なぜそうしたのかを知りたくなります。あなたのDECLINEに反して返金が承認された場合も、その事実を知っておきたいはずです。

正直な見積もりとしては、webhookの配線を過去に構築した経験があれば数日程度の接続作業で済み、それを信頼できるようになるまでには数週間かかります。その大部分はApple側の問題ではありません。

あるいは、他の何かに任せる

すでにRevenueCatやAdaptyを使っているなら、まずそちらが提供している機能を確認してください。どちらも返金対応機能を追加しており、レシートや顧客のマッピングも既に整っています。

Apple返金管理に特化して作られたソフトウェアなら、さらに先まで対応できます。 Refund Sensorは、すでにお持ちのApp Store Connectキー上で動作し、時間枠内にトランザクションの証拠を添えてすべてのリクエストに返信し、ケースごとに結果を記録します。SDKの導入もコード変更も不要で、接続にかかる時間は約5分です。比較検討したい場合は、 Apple refund management toolsでより広い選択肢を比較していますので参考にしてください。

よく聞かれる、顧客側の視点

App Storeでの購入に対する返金リクエストの方法について、ユーザー側の視点で説明すると、reportaproblem.apple.comにアクセスし、サインインして、過去90日以内の購入を見つけ、理由を選択します。Appleは顧客に対し、48時間以内に返答すると伝えています。

StoreKitのrefund request sheetを使えば、同じフォームをアプリ内に表示することもでき、不満を持つユーザーを★1レビューではなくAppleへ誘導できます。これによって返金を承認することはできません。それができるのはAppleだけです。 Apple Support's guide to requesting a refundに、顧客が目にする内容が記載されています。

App Store返金管理は過大に語られがちなので、はっきり言っておきます。返金を承認または拒否することはできません。Appleが判断を下す間に証拠を提示し、preferenceを表明することはできます。それがあなたに許されたすべての影響力であり、それでも、ほとんどの開発者が実際に使っている範囲よりは大きいものです。

参考情報

よくある質問

はい。CONSUMPTION_REQUEST通知とSend Consumption Informationエンドポイントは、まさにそのために存在します。返金を承認または拒否することはできませんが、Appleは送信された情報を判断材料の一つとして利用します。

App Store Server Notifications V2を受信し、JWS署名を検証し、トランザクションをユーザーに照合し、同意を確認し、実際の消費データを組み立てて、12時間以内にv2のconsumptionエンドポイントにPUTします。それ以外はすべて冪等性、アラート、記録管理の話です。

Users and Access内のIntegrationsにあるIn-App Purchaseキーが必要です。App Store Connect APIキーではこれらの呼び出しを認証できず、時計のずれのように見える401エラーが返されます。

いいえ、任意項目です。ただし、deliveryStatusがDELIVEREDでない場合は0にする必要があり、プロレーション(比例配分)されるauto-renewable subscriptionの場合は完全に省略する必要があります。

返信しないでください。Appleのガイダンスはこの点について明確であり、リクエストが届いた後に同意を取得することはできません。

それは判断材料の一つであり、保証ではありません。Appleはさまざまな要因を使用すると述べています。実際には、十分に消費された購入に対して根拠のあるDECLINEを提示した場合、誰も返信しなかったリクエストよりもはるかに高い確率で却下されます。

#:Google Play chargeback#Google Play chargeback handling#Chargeback automation#Google Play refunds#App revenue protection#Chargeback management for apps
Refund SensorRefund Sensor TeamRefund defense for App Store and Google Play developers