すべての記事
Deep dive読了まで 8 分

StoreKit 2 の返金検知は、トランザクション上のたった一つのプロパティに行き着きます。それが revocationDate です

Apple があなたの顧客に返金すると、その返金はサーバーのジョブが走る前に、すでにトランザクションの revocationDate としてアプリ内部に存在しています。本記事では、StoreKit 2 の返金検知が端末上のどこに現れるか、revocationDate と revocationReason が何を伝えるか、そしてなぜクライアントは速さのため、サーバーは真実のためにあるのかを説明します。

暗い開発者のデスクで光るスマートフォンと、その隣の機械式時計。StoreKit 2 の返金検知がアプリ内部に現れる様子を表しています

要点

  • StoreKit 2 では、返金された購入はその Transaction 上に nil ではない revocationDate を持つため、アプリはサーバーを呼び出さずに自力で返金を検知できます。
  • revocationDate は、App Store がトランザクションを返金したとき、または顧客が Family Sharing を通じてその購入を失ったときに設定されるため、nil ではない日付が常に返金を意味するとは限りません。
  • revocationReason は理由を伝えます。developerIssue は顧客があなたのアプリ内の問題を挙げたことを意味し、other はそれ以外のすべての返金理由を含みます。
  • Transaction.currentEntitlements はすでに返金・取り消し済みの購入を除外しているため、最もきれいなクライアント側のアクセス判定は、ある製品がそこにまだ現れるかどうかだけです。
  • Transaction.updates は、起動時にリスニングを始めた場合にのみ、アプリが閉じている間に起きた返金を届けます。つまり Task がなければ返金を取りこぼします。
  • クライアント側の検知はアプリが開いている間しか発火しません。だからこそ App Store Server Notifications V2 の REFUND が、返金済みユーザーへの提供にお金を払い続けるのを止める権威ある信号であり続けます。
  • 返金は取り消されることがあり、その場合トランザクションから失効フィールドが削除されるので、あなたは切ったアクセスを復元することを求められます。

ほとんどのアプリは、Apple の返金を自社サーバーから、App Store Server Notification を通じて知り、同じ返金がすでにアプリ内部に存在していることには気づきません。それはトランザクション上の revocationDate というプロパティにあり、これを読めば、バックエンドのジョブを待たずに、返金済みの顧客が次にアプリを開いた瞬間にアクセスを切れます。StoreKit 2 の返金検知は、多くのチームが飛ばすクライアント側の信号です。ここでは返金が端末上のどこに正確に現れるか、それが何を伝え、何を伝えないか、そしてなぜそれがサーバー通知の代わりではなく、その隣に置かれるべきかを説明します。

StoreKit 2 の中で返金が現れる場所

StoreKit 2 はトランザクションを署名付きの値として渡します。返金はトランザクションを削除しません。印を付けるのです。Transaction 上の二つのプロパティがその印を担い、健全な購入の生涯を通じてどちらも nil のままです。そのどちらかが nil ではなくなったとき、App Store はその購入を取り戻したのです。

revocationDate が切り替わるフィールドです

revocationDate はオプショナルな Date です。Apple 自身の説明は正確です。それは App Store がそのトランザクションを返金した、または Family Sharing から取り消した日付です。まだ有効な購入では nil です。返金が処理された瞬間、それはその返金のタイムスタンプを保持します。この一つの確認、revocationDate が nil でないか、それがクライアント側の返金検知のすべてです。それ以外はすべて、その上に積み重なる細部にすぎません。

revocationReason は Apple がなぜ取り消したかを伝えます

revocationReason は日付の隣にあり、その原因を説明します。StoreKit は返金にとって意味のある二つの値を与えます。developerIssue は、顧客が Apple に、返金はあなたのアプリ内の実在するまたは知覚された問題によるものだと伝えたことを意味します。other は残りのすべての理由を含みます。三つ目の値 upgradedToBundle はそもそも返金ではありません。顧客がサブスクリプションのバンドルに移ったために App Store が取り消したトランザクションを示すものです。行動する前に理由を読んでください。数える価値があるのは developerIssue だからです。それがまとまって現れるのは、あなた自身の製品がどこで壊れたかを教えているのです。

プロパティnil ではない値が意味すること
revocationDateDate?App Store がこの日にこのトランザクションを返金した、または Family Sharing を通じて取り消した
revocationReasondeveloperIssue理由顧客があなたのアプリ内の実在するまたは知覚された問題を挙げた
revocationReasonother理由Apple が項目化しない何らかの別の理由で返金が起きた
revocationReasonupgradedToBundle理由返金ではない。顧客がサブスクリプションのバンドルに切り替えたためトランザクションが取り消された

currentEntitlements はすでに返金済みの購入を除外します

失効フィールドを自分で読む必要が常にあるわけではありません。Transaction.currentEntitlements は、顧客が今この瞬間になお権利を持つ購入のシーケンスであり、Apple はあなたが認めるべきでないものを外すようにそれを構築します。App Store が返金または取り消した製品はそこに現れません。期限切れのサブスクリプションも、使い切った瞬間に消える消耗型も現れません。

それが currentEntitlements を最もきれいなアクセス判定にします。顧客が何を所有しているかをそれに尋ね、まさにそれを付与すれば、返金は一度の revocationDate 確認もなしに権利をあなたのために取り除きます。失効フィールドは、細部、つまり日付と理由が欲しいとき、イベントを記録したり反応したりするためのものです。権利のリストは、明かりを灯し続けるかどうかという素朴な問いのためのものです。

実践における StoreKit 2 の返金検知、起動時とアプリ実行中

アプリが端末上で返金を捕まえられる瞬間は二つあり、それぞれ別のコードが必要です。一つはアプリが開いていて、返金がライブで、または別の端末で起きるときです。もう一つは起動時で、閉じている間に変わったすべてを取り戻すときです。二つ目を逃すと、あなたの StoreKit 2 の返金検知は、ちょうど多くの返金が落ちる場所に穴を持ちます。顧客は返金を求めるとき、あなたのアプリを開いていることがめったにないからです。

起動時にリスニングを始めなければ、閉じている間に起きた返金を取りこぼします

Transaction.updates は、システムがあなたのアプリの外で、または別の端末でトランザクションを作成または更新するたびに、そのトランザクションを、返金を含めて発する非同期シーケンスです。Apple の指示は単刀直入です。アプリが起動したらすぐにそれを反復する Task を始めなさい、さもなければ起動時に一度だけ届くトランザクションを逃すかもしれません。夜間に落ちた返金は、アプリが次に開くとき updates を通じて届きますが、それを受け取るリスナーがすでに動いている場合に限ります。リスナーがなければイベントもなく、返金は何か別のものが照合するまで見えないままです。

同じ端末での購入は updates を通じて来ません

手作業で返金をテストする人を捕まえる罠が一つあります。同じ端末で行われた通常の購入は updates を通じて届きません。StoreKit はそれを購入呼び出しの結果から直接返します。updates は帯域外の変化のためのものです。返金、Ask to Buy の承認、オファーコードの引き換え、そして別の場所で行われた購入です。だから返金処理は購入フローの周りではなく、updatescurrentEntitlements の周りに組み立ててください。返金は、販売がたどった経路を通って戻ってくることは決してないからです。

暗い表面に置かれたしわくちゃの紙のレシートに、薄い赤いスタンプが押されている。StoreKit 2 が失効日で印を付ける返金済みトランザクションを表しています

クライアント側の検知があなたのためにできないこと

端末上で返金を読むのは速く、無料ですが、上限があります。それがないふりをすることが、収益が漏れる原因です。端末は StoreKit が伝えたことしか知らず、StoreKit はあなたのアプリが動いている間しか話しません。返金を受け取り、二度とあなたのアプリを開かない顧客は、あなたのクライアント側の確認が決して見ない顧客です。

revocationDate は常に返金を意味するわけではありません

同じフィールドは Family Sharing でも切り替わります。顧客が共有された購入へのアクセスを失うとき、主催者が外したり共有が終わったりして、そのトランザクションもまた revocationDate を得ます。だから nil ではない日付は、顧客がもうこの購入を持たないことを意味し、それはまさにアクセス制御に必要なものですが、常にお金が戻ってきたことを意味するわけではありません。収益のために返金を数えているなら、その数字を信じる前に、Family Sharing の失効を本物の返金から分けてください。

返金は取り消されることがあります

返金は常に最終ではありません。Apple はそれを取り消すことができ、そのときトランザクションから失効フィールドが削除され、購入は再び有効になります。返金でアクセスを切ったなら、取り消しでそれを復元することが求められます。端末上ではそれはクリーンなトランザクションを伴うもう一つの updates イベントとして現れ、サーバー上では別個の REFUND_REVERSED 通知です。返金だけを処理すると、有効なレシートを持ちながらアクセスのない、支払い済みの顧客を置き去りにします。

遅れた取り消しが実際にいくらかかるか

返金が単に販売価格があなたの口座から出ていくだけであることはめったにありません。それが清算されるまでに、あなたはたいていその購入を提供するためにすでに実際のお金を使っており、その支出は戻ってきません。生成された画像は GPU の分を消費しました。チャットの回答は、トークンごとに請求されたモデル API 呼び出しを消費しました。アップロードは、あなたが今なお保持するために支払っているストレージを消費しました。その購入がクリエイターへの支払いを賄ったなら、そのお金はすでに出ていっています。どれも返金では戻りません。

クライアント側の検知は、あなたがまだ制御できる一つの部分、つまり将来の支出について、その窓を狭めます。購入が返金されたと早く知るほど、それを提供するのを早く止められます。しかし端末はアプリが開いている間しか教えてくれないので、二度と戻らない返金済みユーザーは、あなたがサーバー側で付与したアクセスを保ち続け、バックグラウンドのジョブや同期された端末が彼のために動くたびに、静かにあなたに代償を払わせます。クライアントは取り消しを速くします。それを保証するわけではありません。

速さのためにクライアントを、真実のためにサーバーを使う

きれいな設計は、両方の信号をそれぞれが得意なことに使います。端末上では、Transaction.updatescurrentEntitlements が、返金済みの顧客がアプリを開いた瞬間に、即時のローカルな反応を与えます。UI のため、そして往復なしで権利の変更を仕上げるために向いています。サーバー上では、App Store Server Notifications V2 が REFUND メッセージを送り、それはアプリが二度と開かれるかどうかにかかわらず届きます。返金済みアカウントにバックエンドがお金を使うのを確実に止める唯一の信号です。

信号どこに存在するかいつ発火するか何のために信頼するか
トランザクション上の revocationDate端末、StoreKit 2アプリがそのトランザクションを読む特定の購入が返金または取り消されたことを伝える
Transaction.updates端末、StoreKit 2アプリの実行中に返金が落ちる、またはリスニングしていれば起動時その場にいる顧客のために即座に反応する
currentEntitlements端末、StoreKit 2顧客が今何を所有するかを確認する自分で返金を追跡せずにアクセスを制御する
REFUND 通知あなたのサーバー、App Store Server Notifications V2Apple が返金を処理する、アプリが開いていてもいなくても二度と戻らない顧客へのサーバー側の支出を止める

電話を手にしている顧客のために端末の信号を、その場にいない顧客のためにサーバー通知を組み込んでください。返金が両方の場所に現れるのは意図的です。そのどちらか一方だけを読むことが、返金済みアカウントが、販売がすでに消えた後もあなたに代償を払わせ続ける原因です。

よくある質問

StoreKit 2 で返金をどう検知しますか?
トランザクションの revocationDate を確認します。有効な購入では nil で、App Store がそのトランザクションを返金すると日付を保持するので、nil ではない revocationDate が、購入が返金または取り消された信号です。
revocationDate と revocationReason の違いは何ですか?
revocationDate は App Store が購入を取り戻したときで、revocationReason はその理由です。顧客があなたのアプリ内の問題を挙げたときは developerIssue、それ以外は other です。
返金済みの購入は currentEntitlements になお現れますか?
いいえ。Transaction.currentEntitlements は App Store が返金または取り消した購入を除外するので、返金済みの製品は顧客の権利から自力で外れ、それが安全なアクセス判定になります。
アプリが閉じている間に起きた返金を StoreKit はアプリに教えますか?
起動時からリスニングした場合のみです。Transaction.updates はそれらの変化を起動時に一度届けるので、アプリが起動するときにそれを反復する Task を始めなければ、何か別のものが照合するまで返金は取りこぼされます。
クライアント側の返金検知だけで十分ですか?
いいえ。端末はあなたのアプリが動いている間しか返金を知らないので、二度とアプリを開き直さない顧客はそれには見えません。App Store Server Notifications V2 の REFUND が、いずれにせよあなたに届く信号です。
revocationDate は常に顧客が返金されたことを意味しますか?
いいえ。revocationDate は顧客が Family Sharing を通じて購入を失ったときにも設定されるので、nil ではない日付は彼らがもうその購入を持たないことを意味しますが、常にお金が返されたことを意味するわけではありません。

出典と参考資料

RefundHalt

App Store と Google Play の返金を自動処理

続きを読む

次の返金リクエストは、すでに向かってきています。

異議を申し立てられなかった返金について、もう 1 通のサポートメールを読む時間で RefundHalt を設定できます。