トランザクションの中断と再開について、さらにケースを見ていきます。未完状態のトランザクションが既にある状態で、新しいペイメントをリクエストした場合の挙動について。厄介です。
同一プロダクトに対する複数のトランザクション
前述した複数デバイスを使っているときを除いて、StoreKitでは同じプロダクトに対する購入トランザクションを複数同時に進める事はできないようになっているようです。既に進行中/中断状態のトランザクションがある状態で新しい支払いリクエストをキューに積むと、そのトランザクションはfailすることになります。勿論この挙動自体はそうあるべきものですが、この時の処理の流れには注意をしなければいけません。
発生するパターン
同一のデバイス上で一つのプロダクトに対する複数のトランザクションが発生してしまうパターンは二つ考えられます。
パターン1. 前回のトランザクションがfinishしていない状態で次の支払いをリクエストしてしまった場合
当然ですが、Paymentをキューに積んでfinishTransaction:する前に新しいPaymentをキューに積めば複数のトランザクションが発生してしまいます。基本的には、ユーザが購入ボタンを押したら購入手続きが完了するまでボタンをDisableにする等の対策で防げる問題ですが。
パターン2. 中断された未完状態のトランザクションが再開/完了していない状態で新しく支払いをリクエストしてしまった場合
防ぐのが難しいのがこっちのパターンです。前述の通り、アプリケーションが起動してから未完状態のトランザクションが再開されるまでは最短でも10秒程度、ネットの調子が不安定な場合などではそれ以上の時間がかかり、しかもその間アプリケーション側からは未完状態のトランザクションがあるかどうかの情報を取得できません。その間に新しい支払いリクエストをキューに積んでしまうと、やはりトランザクションが複数ある状態になってしまいます。
なにが起こるのか
ユーザから見ると、同一のプロダクトに対する購入トランザクションが複数進行してしまったときは右のようなダイアログが表示されます。
内部的には後から追加されたほうのトランザクションが「失敗」します。
トランザクションが失敗すると、ObserverのpaymentQueue:updatedTransactions:が呼ばれ、失敗したことがアプリケーション側に通知されます。失敗したトランザクションはtransactionStateがSKPaymentTransactionStateFailedとなっているので、アプリケーション側はトランザクションがこのステートになっていたら失敗したと判断してその旨をユーザに伝えたり失敗したそのtransactionをfinishさせる等します。
トランザクションが失敗すると同時に、元々あったトランザクションも進行して(問題が発生しなければ)完了します。つまり、アプリケーション側から見ると
- ユーザが購入ボタン等を押して購入手続き(トランザクション)が開始した
- (今回の)トランザクションに失敗したことが通知される
- (前回の)トランザクションが成功したことが通知される
という流れになります。
ユーザの視点から考えると、この1〜3の流れは連なった一連の流れのように見えるので、2.で「失敗」を通知されたタイミングでアプリケーション側でエラーダイアログ等を出したりインジケータを消すべきではなく、3を受信したタイミングで「購入成功」ダイアログを出したりインジケータを消すべきです。
アプリケーション側で2.の「トランザクション失敗」を受け取ったときに、それが「既にトランザクションがあったから失敗した」のか、それとも別の理由で失敗したのかを取得すれば、前者の時にその失敗をUI的にスルーしてしまえば万事OKなのですが、この判定が厄介です。
上記現象が発生したときのtransaction.errorの内容は、
Error Domain=SKErrorDomain Code=2 "iTunes Storeに 接続できません" UserInfo=0x158820 {NSLocalizedDescription=iTunes Storeに 接続できません}
となっていました(iOS 5で確認)。SKErrorDomainにおけるCode=2はiOS 5においてはSKErrorPaymentCancelled、すなわち支払いがキャンセルされたことを表すエラーコードです。
ユーザがキャンセルしたときの挙動
では今度は、ちょっと一旦未完トランザクションの話を忘れて、最初の購入ダイアログで「キャンセル」した場合の挙動をみてみましょう。
ここでキャンセルを押すと、トランザクションは失敗して、それがObserverを通じてアプリケーションに通知されます。そのときのtransaction.errorを見てみると、
Error Domain=SKErrorDomain Code=2 "iTunes Storeに 接続できません" UserInfo=0x33f300 {NSLocalizedDescription=iTunes Storeに 接続できません}
さっきと一緒…。
というわけで、「トランザクションが複数進行してしまったからキャンセルされた」のか「ユーザがキャンセルを押したからキャンセルされた」のか、判定ができないのです。
ユーザがキャンセルを押して購入をキャンセルした場合は、その時点でインジケータを消す等してUIを元に戻すべきです。逆にトランザクションの重複が原因のときは購入完了するまでUIをそのままにしておくべきです。しかしStoreKitからの通知できた情報を見るだけではそこを判断できない。
いよいよこの厄介な問題をどう解決するかを考えていかなければいけないのですが、長くなってきたのでまた次のポストで…。
「StoreKit Consumableプロダクトの二重購入」への2件のフィードバック