iOS In-App Purchase プログラミングでハマった話

こんにちは。Supership株式会社の @koogawa です。最近は「[Poptalk\-タレントとファンのチャットコミュニティ](https://itunes.apple.com/jp/app/id1120468251)」というiOSアプリを開発しました。

Supership Advent Calendar 6日目となる今夜は、Poptalkの開発において非常に苦労した点を共有したいと思います。主に In-App Purchase（アプリ内課金）の話になります。

# 何が起こったのか

一言で言うと「お金を払ったのにアイテムが追加されない」ケースが発見されました。

**※幸い、本リリース前のテスト運用時に発見・修正できたので現在は安心してお使い頂けます**

## 発生条件

* 使用しているデバイスで **初めて** 課金処理をする
* Apple IDにクレジットカードを登録している

を両方満たす場合に発生することがわかりました。



## 通常のフロー

まずは通常の購入フローを見てみましょう。

### 1. App内課金の確認アラートが表示される

ここでは「ポップコイン」というアイテムを購入しようとしています。

<img width="375" alt="65ba8d28-0a0c-8aff-d4c5-8ed6d82c2f93.png" src="https://qiita-image-store.s3.amazonaws.com/0/8292/cf2e81bf-b272-87ea-4df4-491c217a717e.png">

「購入する」をタップします。



### 3. 購入完了アラートが表示される

![スクリーンショット 2016-12-03 0.03.32.png](https://qiita-image-store.s3.amazonaws.com/0/8292/b850c599-7591-a968-21a0-cd3c1ca63b10.png)


正常に処理が完了し、アイテムが追加されます。

## うまくいかない場合のフロー

次にうまくいかない場合のフローを見てみましょう。

### 1. App内課金の確認アラートが表示される

ここまでは一緒。

<img width="375" alt="65ba8d28-0a0c-8aff-d4c5-8ed6d82c2f93.png" src="https://qiita-image-store.s3.amazonaws.com/0/8292/cf2e81bf-b272-87ea-4df4-491c217a717e.png">

「購入する」をタップします。

### 3. お支払情報が必要ですアラートが表示される

前述の発生条件を満たしている場合、このアラートが表示されます。

<img width="375" alt="b386c2ce-7fab-9fa2-f245-fb9e881503ed.png" src="https://qiita-image-store.s3.amazonaws.com/0/8292/4785bf91-00a3-2ef4-5687-8d4ca7573f58.png">


「続ける」をタップします。

### 4. クレジットカード入力画面に遷移する

この時点で **Poptalkアプリを離れ、App Storeアプリが起動した状態になっている** ことに注目してください。

![1f939543-11fd-2d3e-3fb4-8d68d33df67f.png](https://qiita-image-store.s3.amazonaws.com/0/8292/b723c893-7358-2295-acff-b32fa57861ac.png)


セキュリティコード等を入力して「完了」をタップします。

### 5. ふたたび「確認が必要です」が表示される

Poptalkアプリを起動していないのに、アイテムが買えてしまいそうな雰囲気です。

<img width="375" alt="image" src="https://qiita-image-store.s3.amazonaws.com/0/8292/a39ecf3e-5b19-6285-ca50-46672d09a519.png">

そして、ここで「購入する」を押すと、予想通り **Poptalkアプリが起動していない状態で購入が完了** してしまいます。

### 5. 購入完了アラートが表示される

買えちゃったよ

<img width="375" alt="5f603d43-3247-add0-354c-f8bae22d4aac.png" src="https://qiita-image-store.s3.amazonaws.com/0/8292/97cf9b65-2711-e7f9-b813-a3482e47bd5e.png">


このあと AppStore アプリを終了し、Poptalkアプリに戻ってもアイテムは追加されていません。つまり、**お金だけ請求される状態** になってしまいます。これは非常にまずい状況です。

## 内部的には何が起こっていたのか

調査の結果、先述した「うまくいかない場合のフロー」の「3. お支払情報が必要ですアラートが表示される」において、ユーザーが「続ける」をタップした時点で **課金処理がエラーで終了** していました。

具体的には [SKPaymentTransactionObserver](https://developer.apple.com/reference/storekit/skpaymenttransactionobserver) プロトコル の `updatedTransactions` メソッドの中で、 `transactionState` が `SKPaymentTransactionStateFailed` になっていました。（ちなみに、このときのエラーコードは `SKErrorPaymentCancelled` であるため、ユーザが意図的に課金処理をキャンセルしたときと見分けがつきません😢）

そして、ユーザーがPoptalkアプリに戻ってきたタイミングで `updatedTransactions` メソッドが**再び動き出し**、 `transactionState` が `SKPaymentTransactionStatePurchased` になるところまで確認できました。このタイミングでトランザクション処理が再実行されることは想定していなかったため、そこではレシートの保存処理を一切行っていませんでした。


# どう対処したか

まずは、再びトランザクションが動き出した際にもしっかりレシートを保存するように修正しました。レシートがサーバー側で正常に処理され、アイテムが追加されるまではレシートを消さない設計になっていたので、これでひとまずお金が消えることはありません。

また、念のためレシートがアプリ側に残っている場合はアイテムを買えない状態にしました。（二重請求を防ぐため）

この対応により、上記のケースでも正常にアイテムが追加されるようになりました。

# 他社のアプリはどう対応しているのか

今回のケースのように「課金処理中にアプリを離れ、App Storeに遷移してしまう」場合は、**「購入する」を押さずに「キャンセル」をタップしてください** と促しているアプリもあるようです。

参考：[メンテナンス情報｜ガンホーゲームズ](http://mobile.gungho.jp/maintenance/pad/pua59k0000003equ.html)



# まとめ

* いつ `updatedTransactions` が呼ばれた場合でもレシートは保存するようにしておく
* アプリ起動時にレシートが残っている場合はユーザーにリトライを促す
* レシートが処理されるまではアイテムを買えない状態にする（二重請求を防ぐため）

この記事が何かお役に立てたら光栄です。

明日の [Supership株式会社 Advent Calendar 2016](http://qiita.com/advent-calendar/2016/supership) は @notsunohito です。明日もどうぞお楽しみに！

# リンク

* [失敗しない iOS In\-App Purchase プログラミング \- A Day In The Life](http://glassonion.hatenablog.com/entry/20111201/1322697417)
* [iTunes 使用許諾更新のとき一旦キャンセルしてほしい話](https://gist.github.com/keiichironagano/75ddc71cda53208a9134)