はじめに
Unity IAPとは
- Unity In App Purchase
- Unity用課金ライブラリ
- 最新バージョンが4.9.4(2023/08/01)
Changelog | In App Purchasing | 4.9.4
記事の内容
やること
- AppStore(iOS) / PlayStore(Android) 両方に対応
- Androidのコンビニ払い対応
- 消費型商品の購入と消費
やらないこと
- Codeless IAP
- クライアントでのレシート検証(CrossPlatformValidate)
- AppleのAsk-to-buy対応
- 定期購入、非消費購入の対応
- 購入のリストア
購入の流れ
- 準備
- 初期化
- 商品の購入
- レシート検証
- 購入の確認
1. 準備
IDetailedStoreListener
を実装したクラスを用意する。
以下ではDetailedStoreListenerImpl
とした:
public class DetailedStoreListenerImpl : IDetailedStoreListener
IDetailedStoreListener
はIStoreListener
の拡張で、以下のメソッドが追加されている:
public interface IDetailedStoreListener : IStoreListener
{
void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription);
}
IStoreListener
のOnPurchaseFailed
は既にObsolete
なので、前述のものを利用する。
public interface IStoreListener
{
[Obsolete("Use IDetailedStoreListener.OnPurchaseFailed for more detailed callback.", false)]
void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason);
}
初期化成功時にIStoreController
とIExtensionProvider
が返ってくるので、DetailedStoreListenerImpl
で保存しておく。
void OnInitialized(IStoreController controller, IExtensionProvider extensions);
public IStoreController StoreController { get; private set; } = null;
public IExtensionProvider ExtensionProvider { get; private set; } = null;
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
StoreController = controller;
ExtensionProvider = extensions;
}
2. 初期化
UnityIAPを使用するために必要な初期化は2段階ある:
1. Unity Gaming Serviceの初期化
// Unity Gaming Serviceの初期化
await UnityServices.InitializeAsync();
2. Unity InAppPurchaseの初期化
// StoreListenerの作成
var storeListener = new DetailedStoreListenerImpl();
// Buidlerの作成
var module = StandardPurchasingModule.Instance();
var builder = ConfigurationBuilder.Instance(module);
var googleConfig = builder.Configure<IGooglePlayConfiguration>();
googleConfig.SetDeferredPurchaseListener(product => storeListener.OnPurchaseDeferred(product));
// 商品の登録
foreach (var id in productIds) builder.AddProduct(id, ProductType.Consumable);
// IAPの初期化
UnityPurchasing.Initialize(storeListener, builder);
StoreListenerは、前述したIDetailedStoreListener
を実装したクラスのインスタンス(=DetailedStoreListenerImpl
)が必要。
Builderには、遅延購入時にStoreListener
のOnPurchaseDeferred
を発火させるよう設定してをセット。
その後商品情報を登録。消費型のためProductType
はConsumable
とする。
そしてStoreListener
/Builder
の両方を使ってIAPを初期化ができる。
成功するとOnInitialized
コールバックが返るので、IStoreController
とIExtensionProvider
を保存しておく。
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
this.StoreController = controller;
this.ExtensionProvider = extensions;
}
3. 商品の購入
初期化時に得たIStoreController
に対し、Product
を渡して商品を購入する。
this.StoreController.InitiatePurchase(product);
これを実行すると端末のネイティブ課金UIが出る。
成功すると、IDetailedStoreListener.ProcessPurchase
がコールバックとして呼ばれる。
この後レシートを検証する必要があるため、ProcessPurchase
内では一旦PurchaseProcessingResult.Pending
を返す。
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent)
{
// ~中略~
return PurchaseProcessingResult.Pending;
}
4. レシート検証
レシートの内容をAPサーバに送り、そこから各ストアに送信して検証する。
当記事では詳説しない。
Appleの場合
UntiyEngine.Purchasing.Product
のreceipt
がstring
だからといって直接AppStoreに送らない。
実際にAppStoreが求めているreceipt-data
はreceipt
の中のPayload
部分のみなので、jsonを分解して渡す。(1ミス)
var r = JsonUtility.FromJson<AppleReceipt>(Product.receipt);
[Serializable]
public class AppleReceipt
{
public string Payload;
public string Store;
public string TransactionID;
}
receipt-data
(Required) The Base64-encoded receipt data.
ちなみにverifyReceipt
エンドポイントはもう[Deprecated]
なので、APサーバもリプレイス頑張ろう。
5. 購入の確認
レシートの妥当性が検証されたら、購入を確認(Confirm)し、消耗品を消費する。
this.StoreController.ConfirmPendingPurchase(product);
その他処理
購入のキャンセル
購入のネイティブUIをユーザがキャンセルすると、PurchaseFailureReason.UserCancelled
を持ったPurchaseFailureDescription
を引数として、IDetailedStoreListener
のOnPurchaseFailed
が呼び出される。
public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
{
if (failureDescription.reason == PurchaseFailureReason.UserCancelled)
{
// UserCancelled
}
}
購入の失敗
購入が失敗すると、PurchaseFailureReason.PaymentDeclined
を持ったPurchaseFailureDescription
を引数として、IDetailedStoreListener
のOnPurchaseFailed
が呼び出される。
public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription)
{
if (failureDescription.reason == PurchaseFailureReason.PaymentDeclined)
{
// PaymentDeclined
}
}
コンビニ払い
Androidのコンビニ払いに対応する。
遅延された購入の通知
初期化で示した例では、以下のように遅延購入のコールバックを登録したが、ユーザーに通知する必要がなければ特に行わなくてもよい。
googleConfig.SetDeferredPurchaseListener(product => storeListener.OnPurchaseDeferred(product));
OnPurchaseDeferred
はProduct
を引数に取るが、このProduct
のhasReceipt
はfalse
である(※1)。ダイアログなどでユーザーにコンビニ払いを促す。
public void OnPurchaseDeferred(Product product)
{
Dialog.Show("コンビニ支払いしてください。");
// Debug.Log($"コンビニ払いを選択しました: {product.definition.id}");
}
※1 GPBLのPurchaseState
にはPending
があるのでPending
でレシートを付けてくれると嬉しいのだが…
※2 上記ドキュメントにPending
はConstant Value: 2 (0x00000002)
と記載があるが、実際にPending
状態のProduct
のレシートを参照するとPurchaseState
は4
になっている。
遅延購入の購入処理
コンビニ払いが行われたならば、よきタイミングでProcessPurchase
が発火し、通常購入と同じように購入処理がなされる。基本的に予見できないので、いつProcessPurchase
が発火しても良いように設計しておく。
大きくパターン分けをすると「再起動した場合」「起動したままの場合」に分けられると思われるが、いずれの場合もProcessPurchase
をトリガーに検証&付与のフローが適切に実装されていれば、あまり問題になることはない。ただ購入フローの最後に「◯◯を付与しました」のようなダイアログを出すようになっている場合は、いつ出るかタイミングが掴めない以上、少し検討が必要かもしれない。
コンビニ払いのキャンセル
コンビニ支払いを選択した後にPlayストアで手動でキャンセルしたり、コンビニ払いを期限までに行わなかったなどの理由で自動でキャンセルした場合、キャンセルされた購入のProcessPurchase
は呼ばれることはない。Pending
状態のレシートをサーバに送ったりしている場合、サーバ側では期限切れを判断してPending
レシートを消去するなどの処理が必要である。
コンビニ払いの未消費
コンビニ払いをしたのに、その後アプリを72時間起動せず、ProcessPurchase
で消費されなかった場合、キャンセル扱いになる。この場合のキャンセルはPlayストアの残高に充当される。