目次
IAP決済中の×ボタンはNG?UIキャンセル制御とバグ対策
仕様書の1画面、たった1個のボタンをめぐって議論が起きた。ショップ機能の画面仕様レビュー中、購入復元中に表示されるポップアップの×ボタン(処理中断ボタン)についてである。結論だけ言えば「消す」という判断に落ち着いたが、その過程では課金処理特有の非同期性とUI操作が衝突するリスクが具体的に議論された。本稿では、その議論をもとに、IAP(アプリ内課金)処理中のキャンセル制御をどう設計すべきかを整理する。
前提となる仕組みと用語
IAP(In-App Purchase)は、App StoreやGoogle Playといったストアを経由した課金の仕組みを指す。価格情報や決済処理そのものはストア側のシステムが握っており、アプリ内で機能やコンテンツをアンロックする場合はこのIAPの利用が審査要件となっている[1]。Google Playでもデジタルコンテンツの課金にはPlayの課金システムの使用が義務付けられている[2]。つまり、決済処理そのものはアプリの外側、ストアのサーバーで完結する部分がある、という点がまず前提になる。
この構造ゆえにIAP実装では「pending transaction(保留中の取引)」という状態を考慮する必要がある。決済はストア側で確定しているが、アプリ側でのコンテンツ付与処理がまだ完了していない状態のことだ。通信断やアプリの強制終了はこの間に容赦なく発生しうる。もう一つ押さえておきたいのが「購入復元(Restore Purchase)」で、端末変更や再インストール時に過去の購入履歴をストアから取得し、アプリ側の状態に反映する処理を指す。この購入復元も、内部的にはpending transactionの解消と同じ性質の処理を担っている。
課題の背景 – 仕様書に残っていた×ボタン
今回の題材となったショップ機能の画面仕様書には、購入復元中に表示されるポップアップに×ボタンが用意されていた。レビュー担当者はここに懸念を示し、「購入復元は手動で中断できない方が良いのではないか」と課題管理表に追記する提案をした。理由は単純で、購入復元処理は完了するかタイムアウトするまでポップアップを閉じさせない設計の方が、状態の整合性を保ちやすいという判断である。
これに対してもう一人のエンジニアからは、もう一段踏み込んだ指摘が返ってきた。決済処理が完了した瞬間にアプリを強制終了した場合の復旧フローが、そもそも仕様に存在していないという点である。「サーバー側は成功したけどクライアントだけ失敗のような状態」——これは決済が確定しているのにコンテンツ付与が反映されないまま取り残される不整合状態で、二重課金や付与漏れに直結しかねない。×ボタンの是非を議論する前に、そもそもこの不整合状態を回復する仕組みが必要だという指摘だった。
成功基準として置いたのは、決済完了からコンテンツ付与までの間にアプリが落ちたり通信が切れたりしても、後続の処理(ログイン時や購入復元時)で状態が正しく回復できることである。×ボタンをどう扱うかは、この基準を満たした上での話になる。
従来のポップアップ設計との比較
一般的なモーダルやポップアップの設計では、ユーザーがいつでも操作を中断して閉じられることが望ましいとされる。処理をキャンセルできない画面は、ユーザーに「操作を奪われている」感覚を与えやすく、UXの観点からは避けたいパターンとされてきた。実際、今回のプロジェクトでも「すべてのウインドウに×ボタンをつける」という方針が採られていた。これは仕様の一貫性を保ち、デバッグの手間を減らすための判断だったという背景がある。すべての画面で×の有無を個別に確認する必要がなくなるためだ。
しかし決済処理中のポップアップだけは、この一般則が当てはまらない。決済という不可逆な処理が進行中に中断操作を許すと、クライアント側の処理を止めても決済自体はストア側で進んでしまう可能性がある。中断ボタンを押した直後に決済が確定してしまえば、ユーザーの意図(中断)とシステムの結果(購入成立)が食い違う。この食い違いは、単なる表示バグより厄介な、金銭が絡む不整合として扱う必要がある。
📦 旧状態(Before)
- 購入復元中のポップアップに×ボタンを表示
- 他の画面と同様、処理中でもいつでも閉じられる想定
- 決済完了後にアプリが落ちた場合の復旧フローが未定義
✅ 新状態(After)
- 購入復元・購入処理中のポップアップから×ボタンを除外
- 完了かタイムアウトまでポップアップを閉じさせない
- ログイン時・購入復元時に未反映の決済レコードを確認し反映する回復処理を追加
決済処理中に×ボタンを消すという設計判断
議論の結論として、購入処理中・購入復元中のポップアップは「×ボタンなし」の例外として扱うことになった。決め手になったのは、×を押しても何も起きない状態が、そのまま「押しても反応しないバグ」に見えてしまうという指摘である。ボタンが画面に存在する以上、ユーザーは押せば何かが起きると期待する。処理中だからと無視する実装にすると、その振る舞い自体が不具合報告の対象になりかねない。ボタンを機能させないくらいなら、最初から出さない方が誠実だという判断だった。
一方で、「すべての画面に×をつける」という元々の方針にも合理性があった点は無視できない。デバッグ対象を画面ごとに個別判断する手間を省くための取り決めであり、決済処理という一箇所の例外を認めるかどうかは、その手間の増加分と天秤にかけて判断されている。最終的には、決済処理中の不整合リスクの方が優先度が高いと判断され、この画面に限って例外を設ける形で合意した。仕様上の一貫性を崩す判断は、理由を明文化しておかないと後で「なぜここだけ違うのか」と再度問われることになる。今回はその経緯を課題管理表に残し、先方への確認事項として提出する運びとなった。
サーバー成功・クライアント失敗をどう回復するか
×ボタンを消しただけでは、決済完了とコンテンツ付与のタイミングがずれるリスクそのものは解消されない。アプリの強制終了や通信断は、ユーザー操作を制限しても起こりうるからだ。そこで合意されたのが、ログイン時と購入復元時の両方で「未反映の決済済みレコード」を確認し、あれば反映するという回復経路である。決済処理中に発生した不整合を、後続のタイミングで拾い直す設計だ。
このとき、ログイン時の回復処理ではダウンロードまでは行わず、決済レコードの反映のみにとどめる方針とした。ダウンロード実行までまとめてしまうと、ログインのたびに重い処理が走りかねないためである。処理の粒度を分けることで、ログイン時は軽量な整合性チェックに専念できる。
この図が示す通り、回復のトリガーは1つに絞らず、ログインと購入復元の2つの経路を用意している。片方の経路だけに依存すると、ユーザーがそのアクションを取らない限り不整合が解消されないままになるためだ。最初はログイン時の処理だけで足りると考えていたが、購入復元というもう一つの入口を確認する過程で、そちらでも同様の反映処理が必要だと判明した経緯がある。
現場の声
担当エンジニアより:
「この処理中にアプリを落とした時の対処と同様で
サーバー側は成功したけどクライアントだけ失敗のような状態
を解決するフローは必要です
(だいたいログインでやるって言ってた処理のつもり)
ただ×させる必要もないので、確認して問題ないなら消しましょうか。
×ボタンを全部につけるっていうのはデバッグの手間を減らすための案(すべてのウインドウで正しく×があるかないかを確認する必要があるかどうか)なので、
そこを担保できるなら消しても問題ないです。」
レビュー担当エンジニアより:
「こちらは、×押して何も反応しないとバグ扱いになるかと思うので
ここだけポップアップの例外ですが、×なしが良いかと思いました。
課題管理表に記載し、先方確認します。」
現状の制約・未対応項目
今回の議論で合意できたのは「×ボタンを消す」という表面上のUI変更と、「ログイン時・購入復元時に未反映レコードを確認・反映する」という回復フローの方針までである。具体的な反映処理のリトライ回数やタイムアウト秒数、決済サーバーとの照合APIの詳細な仕様は、この時点ではまだ課題管理表に記載され先方確認待ちの状態だった。加えて、購入済みだがダウンロード未のコンテンツをショップ画面上でどう表示するかという論点も並行して議論されており、この論点は表示仕様に影響するため別途整理が必要な項目として残っている。UI側の応急処置と、バックエンド側の整合性担保処理は、切り離さずセットで仕様化する必要がある。
よくある質問
Q. 決済処理中のポップアップだけ×ボタンをなくすと、仕様の一貫性が崩れないか。
A. 崩れる。だからこそ理由を明文化して課題管理表に残し、例外として扱う合意を取ることが必要になる。すべての画面に機械的に同じルールを適用するより、リスクの高い箇所だけ例外を認め、その根拠を残す方が実装後のトラブルシュートがしやすい。
Q. ×ボタンを消すだけでサーバー成功・クライアント失敗の問題は解決するか。
A. 解決しない。×ボタンの除去はユーザー操作による中断リスクを塞ぐだけであり、通信断やアプリ強制終了によるpending transactionの発生自体は防げない。ログイン時・購入復元時の回復フローとセットで初めて成立する対策である。
Q. なぜ回復のトリガーをログイン時と購入復元時の2つに分けたのか。
A. どちらか一方に依存すると、ユーザーがそのアクションを取らない限り不整合状態が解消されないためである。ユーザーの行動パターンに依存せず回復させるには、複数の入口を確保する設計が必要になる。
まとめ
今回の結論は、購入処理中・購入復元中のポップアップに限り×ボタンを排除し、決済完了からコンテンツ付与までの不整合はログイン時と購入復元時の2経路で回復させる、というものだった。発端は「×ボタンを消してよいか」という一見小さな仕様確認だったが、議論を進める中で、UIの見た目の話ではなく決済処理特有の非同期性そのものに焦点が移っていった。すべての画面に一律で×ボタンをつけるという既存方針を崩す判断だったため、その理由を課題管理表に残し、先方への確認事項として明文化する形を取っている。決済処理を含む画面のUI設計では、汎用的なポップアップの流儀をそのまま当てはめず、バックエンドの状態遷移まで踏まえて例外を作る判断が要る場面がある。同じ論点で立ち止まる人が減れば幸いである。
























