Unity IAPを使ってて思ったこと

あの時つまづいたことを思い出しながら書く
まとまっていない、間違っていたらご了承ください

:tada: Unity IAP(1.9.0)からコードレスで実装できるようです :tada:
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">アセットストアから利用できるUnity IAPを最新（今日リリースされたばかり）のものに更新すると、コードレスIAPが利用できるようになります。コードを、一行も書かずに、クリックやボタンだけでIAPが実装できる！便利な世の中になりました。</p>&mdash; ユニティ・テクノロジーズ・ジャパン (@unity_japan) <a href="https://twitter.com/unity_japan/status/793594475670929408">2016年11月1日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
ドキュメントは下記から  
https://docs.google.com/document/d/1597oxEI1UkZ1164j1lR7s-2YIrJyidbrfNwTfSI1Ksc/edit

# Unity IAP
### どこから手を付ける？
とっかかりはこれ（神）
基本的なことはコレが全て（P68〜）
[Unite 2016 Tokyo トレーニングデイ - Unity サービス実装ワークショップ資料]
(http://japan.unity3d.com/unite/unite2016/files/Unite2016Tokyo_WS_courseA_All.pdf)

サンプルプロジェクト
[Unite2016TokyoWS01](https://github.com/unity3d-jp/Unite2016TokyoWS01)

### そしてどうすれば？
Purchaser.csをいじる。たぶんやっていく中で多少（？）改造する
結果的にやったことは
・Unity IAPのシステムの初期化や購入結果のコールバックを受け取る
・呼び出し元に処理結果を返す
・サーバにレシートを渡す

### アップデートはどこ？
ServiceのIn-App PurchaseでImportを押す（Unityのバージョンとは別管理のため）

### アプリをアンインストールしたらどうなるの？
もちろんConfirmした消費型アイテムはアプリから消える
ただ、Pending状態なら消費型アイテムでもUnity IAPの初期化時に課金の情報がまたやってくる
どうやらクラウドセーブ機能とか(iOSだと何だろ？)使ってるっぽい
なのでアイテムが付与されないまま終わるってことは理論上はないっぽい

### 消費しなかったアイテムはどうする？
Initializeを呼び出した時に、ProcessPurchaseが呼び出される
その時点で消費してもよし、リストに積んで後でなんかのタイミングでやってもよし
Purchaserの設計しだい

### Initializeするタイミング
ゲーム起動時にInitializeすると、アイテムの状態によってはゲーム起動直後にiTunesのパスワード求められる可能性もあるのでリジェクト影響あるかもと思った。
例えばタイトル画面の後とか、購入画面に入った時とかに気のきいた一言を添えて出せば良いのかな？


### サーバと連携するためには？
一旦、[ProcessPurchase](http://docs.unity3d.com/ja/current/ScriptReference/Purchasing.IStoreListener.ProcessPurchase.html)メソッドでは[Pending](http://docs.unity3d.com/ja/current/ScriptReference/Purchasing.PurchaseProcessingResult.Pending.html)を返す
サーバとのやり取りを終えた後に[ConfirmPendingPurchase](http://docs.unity3d.com/ja/current/ScriptReference/Purchasing.IStoreController.ConfirmPendingPurchase.html)を呼び出す

参考：[Is it OK to do network calls in ProcessPurchase?](http://answers.unity3d.com/questions/1134229/is-it-ok-to-do-network-calls-in-processpurchase.html)

### ローカルでレシート検証できる？
一応できるけどやらなかった
サーバ側で処理する場合はこの辺りはやらなくて良い
参考：[レシート検証](http://docs.unity3d.com/ja/current/Manual/UnityIAPValidatingReceipts.html)

### Unibillとの相性は？
移行してないのでまだわからないがUnibillとしてはUnity IAPが置き換えのものらしい
<blockquote class="twitter-tweet" data-lang="ja"><p lang="en" dir="ltr"><a href="https://twitter.com/darkmobius">@darkmobius</a> Unity IAP is Unibill&#39;s replacement, available for free in Unity 5.3</p>&mdash; Outline Games (@outlinegames) <a href="https://twitter.com/outlinegames/status/699949926818693120">2016年2月17日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

### 簡単？
簡単かどうかは良いじゃないか
作ってて面白かったよ
ただ、実機での動作確認がもどかしい

# Android編
### テスト課金ができない :innocent: 
ほんとうに色々
・apkをアルファ版、ベータ版でいいので公開する
・課金アイテムを有効にする（デフォルトは無効）
・クローズドテストの場合は招待された後にリンクをクリックする
・時間を置く（コレ大事）
・Google Playアプリを設定からデータ消去で消す（データの復元はすぐできるので思い切ってやる）
・UnityCloudBuildのapkはバージョンがどんどん上がっていくので、Google Playのバージョンと差異が出る。同じバージョンコードでやろう

### 課金途中でアプリのタスクを殺して再度課金すると強制終了する
コルーチンの中でConfirmPendingPurchase呼んでた
凡ミス

### Google Play開発者サービスを無効にして課金を試す
まだやってないけど見ておいたほうが良いかも。
無効にしているキッズ多いし

### Google Playのデータを消去して動きを見る
まだやってない
バージョン違いで変なことならないように石橋を叩く

### developerPayloadどうやって送るの？
以下の第二引数に渡す

```lang:InitiatePurchaseのオーバロードメソッド
StoreController.InitiatePurchase (product, developerPayload);
```

### レシート情報がエンコードされてる
product.receiptの文字列をそのままサーバに渡す時は注意
クライアントかサーバ側でバックスラッシュとか一部のダブルクォーテーションとかを取り除いてやる

# iOS編
### 機内モードでリアクションがない
押した直後に圏外の確認を入れてメッセージとか出した

```lang:圏外チェック
private static bool HasNetworkConnection ()
{
	return Application.internetReachability != NetworkReachability.NotReachable;
}
```
（※Google Playは圏外の旨の表示が出る）

### App内課金のオフで動くか？
普段使わないので忘れがち。一応確認する

### [上記の発展型]アイテム購入→App内課金をオフ→購入ボタンを押す
前回購入したレシートの情報がOnPurchaseFailedにやってくる
Purchaserをカスタマイズしてる場合はちょっと注意
（※消費型でPendingのリストを保存していたのでリストアの時にちょっとハマった）

### リストアって？
iOSだけやる。通信の影響で購入失敗したアイテムや、再インストール時にアイテムを復元する（非消費型とサブスクリプション）
[リストア](http://docs.unity3d.com/ja/current/Manual/UnityIAPRestoringTransactions.html)

### 消費型アイテムのリストアは？
RestorePurchaseを呼び出しても何もならないので、どうやら消費型は自分で管理する必要がある様子
[UnityPurchasing.Initialize](http://docs.unity3d.com/ja/current/ScriptReference/Purchasing.UnityPurchasing.Initialize.html)の時に購入されていないProductがやって来るので、保存するか消費する
参考：[UnityIAPのリストア処理(Android)](http://qiita.com/JmzSpR/items/0bb8d0e604d8c7577275)

### 課金直後にスワイプしてタスクから消す
消費型アイテムは消費するまで再度同じものは買えない。
自力で消費する(ConfirmPendingPurchase)

### 「既に購入されているアイテムです」とでる
購入直後に強制終了とか、課金直後にタスクから消すとたまにでる
消費型アイテムは自力で消費する（ConfirmPendingPurchase）

### Payloadってなに？
コレが重要。
レシート検証APIに送ると、購入情報が返ってくる。

参考：[メモ: Unity 5.3 の In-App Purchasing](http://qiita.com/kivatek/items/e93e9cf11eba3fba176c)

# サーバ側
Node.jsでやった（あまり今回は関係ないけど）

検証フローとしてはこれでOK
[iOS/Androidアプリ内課金の不正なレシートによる有料会員登録を防ぐ](http://inside.pixiv.net/entry/2014/12/09/111310)
想像以上にチーターがいるのでサーバ重要

### アプリから送ってもらうデータ
Android、iOSともにPayload一式（AndroidはJSONの文字列がエンコードされているのに注意）

### iOSのレシート検証APIが21002で返ってくる
Unity IAPの[Product.receipt](http://docs.unity3d.com/ja/current/ScriptReference/Purchasing.Product-receipt.html)にあるPayloadをそのまま使う

### それでもiOSのレシート検証APIが21002で返ってくる
Content-Type：application/x-www-form-urlencodedで送っていた。
Content-Type：application/jsonにして、JSONで送る

```lang:リクエストボディ
{"receipt-data", "PAYLOAD..."}
```

### Androidのテストアカウントでレシート検証（Google Play Developer API）がNG
同じレシートを使い続けて検証しているとなった
1日くらい経つとレシートの情報が破棄される？
（5月25日に仕様変えるよっていうメール来てたからもう大丈夫かもしれない）

### 書き込み直前に重複するorderId(Android) or TransactionId（iOS）がないかチェック
ミリ秒間隔でAPIへアクセスされる場合があるので、直前にチェックする（LoopbackだとfindOrCreate使った）
日頃サーバを触っている人なら大丈夫だとは思う

### AndroidのテストアカウントのレシートにorderIdがない
仕様。
課金した後に飛んでくるテストメールを見ると、TransactionIdの頭から24桁目までを仮のorederIdとして使っているみたい。
本番アカウントではorderIdが載ってくるので大丈夫。
参考：[In-app Billing Reference](https://developer.android.com/google/play/billing/billing_reference.html#getBuyIntent)(orderIdの項目参照)


# 結局できたもの
公開してはみるが消費型アイテム+通信前提でしか確認してないので参考まで
[Purchaser.cs](https://gist.github.com/YoshihideSogawa/f7c118127ce50e593a5b4a12e8426d6e)

### 使い方
事前にConsumableProducts等を指定して、

```lang:
Purchaser.Instance.Initialize();
// 購入の時
Purchaser.BuyFailureReason reason = Purchaser.Instance.BuyProductID (UNITY_ITEM_ID, "userId", OnSuccessPurchase, OnFailurePurchase);
// リストアの時
Purchaser.BuyFailureReason reason = Purchaser.Instance.RestorePurchases (OnSuccessPendingPurchase);
// 購入後消費する時
Purchaser.Instance.ConfirmPendingPurchase (product);
```

こんな感じ

# まとめ
簡単にアプリ内課金の実装ができた
Importした時のPurchaserとはまるで別人

おわり