Unity
課金
IAP
IAB
UnityIAP

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

More than 1 year has passed since last update.

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

:tada: Unity IAP(1.9.0)からコードレスで実装できるようです :tada:



ドキュメントは下記から

https://docs.google.com/document/d/1597oxEI1UkZ1164j1lR7s-2YIrJyidbrfNwTfSI1Ksc/edit

Unity IAP

どこから手を付ける?

とっかかりはこれ(神)
基本的なことはコレが全て(P68〜)
Unite 2016 Tokyo トレーニングデイ - Unity サービス実装ワークショップ資料

サンプルプロジェクト
Unite2016TokyoWS01

そしてどうすれば?

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

アップデートはどこ?

ServiceのIn-App PurchaseでImportを押す(Unityのバージョンとは別管理のため)

アプリをアンインストールしたらどうなるの?

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

消費しなかったアイテムはどうする?

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

Initializeするタイミング

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

サーバと連携するためには?

一旦、ProcessPurchaseメソッドではPendingを返す
サーバとのやり取りを終えた後にConfirmPendingPurchaseを呼び出す

参考:Is it OK to do network calls in ProcessPurchase?

ローカルでレシート検証できる?

一応できるけどやらなかった
サーバ側で処理する場合はこの辺りはやらなくて良い
参考:レシート検証

Unibillとの相性は?

移行してないのでまだわからないがUnibillとしてはUnity IAPが置き換えのものらしい


簡単?

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

Android編

テスト課金ができない :innocent:

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

課金途中でアプリのタスクを殺して再度課金すると強制終了する

コルーチンの中でConfirmPendingPurchase呼んでた
凡ミス

Google Play開発者サービスを無効にして課金を試す

まだやってないけど見ておいたほうが良いかも。
無効にしているキッズ多いし

Google Playのデータを消去して動きを見る

まだやってない
バージョン違いで変なことならないように石橋を叩く

developerPayloadどうやって送るの?

以下の第二引数に渡す

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

レシート情報がエンコードされてる

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

iOS編

機内モードでリアクションがない

押した直後に圏外の確認を入れてメッセージとか出した

圏外チェック
private static bool HasNetworkConnection ()
{
    return Application.internetReachability != NetworkReachability.NotReachable;
}

(※Google Playは圏外の旨の表示が出る)

App内課金のオフで動くか?

普段使わないので忘れがち。一応確認する

[上記の発展型]アイテム購入→App内課金をオフ→購入ボタンを押す

前回購入したレシートの情報がOnPurchaseFailedにやってくる
Purchaserをカスタマイズしてる場合はちょっと注意
(※消費型でPendingのリストを保存していたのでリストアの時にちょっとハマった)

リストアって?

iOSだけやる。通信の影響で購入失敗したアイテムや、再インストール時にアイテムを復元する(非消費型とサブスクリプション)
リストア

消費型アイテムのリストアは?

RestorePurchaseを呼び出しても何もならないので、どうやら消費型は自分で管理する必要がある様子
UnityPurchasing.Initializeの時に購入されていないProductがやって来るので、保存するか消費する
参考:UnityIAPのリストア処理(Android)

課金直後にスワイプしてタスクから消す

消費型アイテムは消費するまで再度同じものは買えない。
自力で消費する(ConfirmPendingPurchase)

「既に購入されているアイテムです」とでる

購入直後に強制終了とか、課金直後にタスクから消すとたまにでる
消費型アイテムは自力で消費する(ConfirmPendingPurchase)

Payloadってなに?

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

参考:メモ: Unity 5.3 の In-App Purchasing

サーバ側

Node.jsでやった(あまり今回は関係ないけど)

検証フローとしてはこれでOK
iOS/Androidアプリ内課金の不正なレシートによる有料会員登録を防ぐ
想像以上にチーターがいるのでサーバ重要

アプリから送ってもらうデータ

Android、iOSともにPayload一式(AndroidはJSONの文字列がエンコードされているのに注意)

iOSのレシート検証APIが21002で返ってくる

Unity IAPのProduct.receiptにあるPayloadをそのまま使う

それでもiOSのレシート検証APIが21002で返ってくる

Content-Type:application/x-www-form-urlencodedで送っていた。
Content-Type:application/jsonにして、JSONで送る

リクエストボディ
{"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(orderIdの項目参照)

結局できたもの

公開してはみるが消費型アイテム+通信前提でしか確認してないので参考まで
Purchaser.cs

使い方

事前にConsumableProducts等を指定して、

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とはまるで別人

おわり

  • Android編
  • iOS編
  • サーバ側
  • 結局できたもの
  • まとめ