App Store のレシート は、デバイスのローカルストレージに保管されており、以下のようにして読み取ることができます。
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
string receipt = builder.Configure<IAppleConfiguration>().appReceipt;
アプリ内課金はデバイスの設定で制限されている場合があります。以下のようにして、確認できます。
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
bool canMakePayments = builder.Configure<IAppleConfiguration>().canMakePayments;
Apple のプラットフォーム上では、前回のトランザクションを取得するには、アプリケーションでリストアするかどうかのボタンを表示して、ユーザーからパスワードを入力してもらわなくてはなりません。このプロセス中、IStoreListener の ProcessPurchase メソッドが、ユーザーがすでに所有しているアイテムすべてに実行されます。
/// <summary>
/// Your IStoreListener implementation of OnInitialized.
/// </summary>
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
extensions.GetExtension<IAppleExtensions> ().RestoreTransactions (result => {
if (result) {
// This does not mean anything was restored,
// merely that the restoration process succeeded.
} else {
// Restoration failed.
}
});
}
Apple はサーバーから App Store の新しいレシートを取得する方法 SKReceiptRefreshRequest を提供しています。これは通常、ローカルストレージに現在レシートがキャッシュされていない場合に使用されます。
この方法にはパスワードが必要だということに気を付けてください。
Unity IAP では以下のようにこのメソッドを使用します。
/// <summary>
/// Your IStoreListener implementation of OnInitialized.
/// </summary>
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
extensions.GetExtension<IAppleExtensions> ().RefreshAppReceipt (receipt => {
// This handler is invoked if the request is successful.
// Receipt will be the latest app receipt.
Console.WriteLine(receipt);
},
() => {
// This handler will be invoked if the request fails,
// such as if the network is unavailable or the user
// enters the wrong password.
});
}
iOS 8 に、Ask to Buy という新しいペアレンタルコントロール機能が導入されました。
Ask to Buy を使った購入では、保護者の承認を待機します。このとき、Unity IAP は以下のようにアプリケーションに通知を送信します。
/// This is called when Unity IAP has finished initialising.
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
extensions.GetExtension<IAppleExtensions>().RegisterPurchaseDeferredListener(product => {
Console.WriteLine(product.definition.id);
});
}
下のサンプルクラスは Sandbox のアプリストアで Ask-to-Buy シミュレーションを使用可能にするために IAppleExtensions にアクセスする方法を示しています。
using UnityEngine;
using UnityEngine.Purchasing;
public class AppleSimulateAskToBuy : MonoBehaviour {
public void SetSimulateAskToBuy(bool shouldSimulateAskToBuy) {
if (Application.platform == RuntimePlatform.IPhonePlayer) {
IAppleExtensions extensions = IAPButton.IAPButtonStoreManager.Instance.ExtensionProvider.GetExtension<IAppleExtensions>();
extensions.simulateAskToBuy = shouldSimulateAskToBuy;
}
}
}
購入が承認または拒否されると、ストアの通常の ProcessPurchase または OnPurchaseFailed リスナーメソッドが呼び出されます。
IAppleExtensions を使用して、特定の Product の最新のトランザクションレシートの文字列にアクセスします。ノート: トランザクションレシートは Mac ビルドには使用できません。Mac ビルドでトランザクションレシートを要求すると、空の文字列が返されます。
#if UNITY_PURCHASING
using System;
using UnityEngine;
using UnityEngine.Purchasing;
public class AskToBuy : MonoBehaviour, IStoreListener
{
// Unity IAP objects
private IStoreController m_Controller;
private IAppleExtensions m_AppleExtensions;
public AskToBuy ()
{
var builder = ConfigurationBuilder.Instance (StandardPurchasingModule.Instance ());
builder.AddProduct ("100_gold_coins", ProductType.Consumable, new IDs {
{ "100_gold_coins_google", GooglePlay.Name },
{ "100_gold_coins_mac", MacAppStore.Name }
});
UnityPurchasing.Initialize (this, builder);
}
/// <summary>
/// This will be called when Unity IAP has finished initialising.
/// </summary>
public void OnInitialized (IStoreController controller, IExtensionProvider extensions)
{
m_Controller = controller;
m_AppleExtensions = extensions.GetExtension<IAppleExtensions> ();
// On Apple platforms we need to handle deferred purchases caused by Apple's Ask to Buy feature.
// On non-Apple platforms this will have no effect; OnDeferred will never be called.
m_AppleExtensions.RegisterPurchaseDeferredListener (OnDeferred);
}
/// <summary>
/// This will be called when a purchase completes.
/// </summary>
public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
{
if (Application.platform == RuntimePlatform.IPhonePlayer ||
Application.platform == RuntimePlatform.tvOS) {
string transactionReceipt = m_AppleExtensions.GetTransactionReceiptForProduct (e.purchasedProduct);
Console.WriteLine (transactionReceipt);
// Send transaction receipt to server for validation
}
return PurchaseProcessingResult.Complete;
}
/// <summary>
/// Called when Unity IAP encounters an unrecoverable initialization error.
///
/// Note that this will not be called if Internet is unavailable; Unity IAP
/// will attempt initialization until it becomes available.
/// </summary>
public void OnInitializeFailed (InitializationFailureReason error)
{
}
/// <summary>
/// Called when a purchase fails.
/// </summary>
public void OnPurchaseFailed (Product i, PurchaseFailureReason p)
{
}
/// <summary>
/// iOS Specific.
/// This is called as part of Apple's 'Ask to buy' functionality,
/// when a purchase is requested by a minor and referred to a parent
/// for approval.
///
/// When the purchase is approved or rejected, the normal purchase events
/// will fire.
/// </summary>
/// <param name="item">Item.</param>
private void OnDeferred (Product item)
{
Debug.Log ("Purchase deferred: " + item.definition.id);
}
}
#endif // UNITY_PURCHASING
App Store のレシートとは異なり、トランザクションレシートをローカルで検証することはできません。代わりに、検証のためにレシートの文字列をリモートサーバーに送信する必要があります。すでにリモートサーバーを使用して App Store のレシートを検証している場合は、トランザクションレシートを同じ Apple エンドポイントに送信して JSON 応答を受信します。
JSON 応答の例
{
"receipt": {
"original_purchase_date_pst": "2017-11-15 15:25:20 America/Los_Angeles",
"purchase_date_ms": "1510788320209",
"unique_identifier": "0ea7808637555b2c633eb07aa1cb0894c821a6f9",
"original_transaction_id": "1000000352597239",
"bvrs": "0",
"transaction_id": "1000000352597239",
"quantity": "1",
"unique_vendor_identifier": "01B57C2E-9E91-42FF-9B0D-4983175D6694",
"item_id": "1141751870",
"original_purchase_date": "2017-11-15 23:25:20 Etc/GMT",
"product_id": "100.gold.coins",
"purchase_date": "2017-11-15 23:25:20 Etc/GMT",
"is_trial_period": "false",
"purchase_date_pst": "2017-11-15 15:25:20 America/Los_Angeles",
"bid": "com.unity3d.unityiap.demo",
"original_purchase_date_ms": "1510788320209"
},
"status": 0
}
Apple は、開発者がアプリケーションのプロダクトページを通して ゲーム内購入 をプロモーションすることを許可しています。従来のアプリ内課金とは異なり、Apple のプロモーション購入は iOS および tvOS の App Store から直接開始されます。その後、アプリストアはアプリケーションを起動してトランザクションを完了するか、アプリケーションがインストールされていない場合はそれをダウンロードするようにユーザーに促します。
IAppleConfiguration SetApplePromotionalPurchaseInterceptor コールバックメソッドは Apple のプロモーション購入をインターセプトします。購入を Apple に送信する前に、このコールバックを使用してペアレンタルゲートを設けたり、分析イベントを送信したり、他の機能を実行することができます。コールバックは、ユーザーが購入しようとした Product を使用します。プロモーション購入を続行するには、IAppleExtensions.ContinuePromotionalPurchases() を呼び出す必要があります。これにより、待機中の支払いが開始されます。
コールバックを設定しない場合は、プロモーション購入は即座に通過し、結果により ProcessPurchase が呼びだされます。
ノート: 他のプラットフォームでこれらの API を呼び出しても効果はありません。
private IAppleExtensions m_AppleExtensions;
public void Awake() {
var module = StandardPurchasingModule.Instance();
var builder = ConfigurationBuilder.Instance(module);
// On iOS and tvOS we can intercept promotional purchases that come directly from
// the App Store.
// On other platforms this will have no effect; OnPromotionalPurchase will never be
// called.
builder.Configure<IAppleConfiguration>().
SetApplePromotionalPurchaseInterceptorCallback(OnPromotionalPurchase);
Debug.Log("Setting Apple promotional purchase interceptor callback");
}
public void OnInitialized(IStoreController controller, IExtensionProvider extensions) {
m_AppleExtensions = extensions.GetExtension<IAppleExtensions>();
foreach (var item in controller.products.all) {
if (item.availableToPurchase) {
// Set all these products to be visible in the user's App Store
m_AppleExtensions.SetStorePromotionVisibility(item, AppleStorePromotionVisibility.Show);
}
}
}
private void OnPromotionalPurchase(Product item) {
Debug.Log("Attempted promotional purchase: " + item.definition.id);
// Promotional purchase has been detected.
// Handle this event by, e.g. presenting a parental gate.
// Here, for demonstration purposes only, we will wait five seconds before continuing
// the purchase.
StartCoroutine(ContinuePromotionalPurchases());
}
private IEnumerator ContinuePromotionalPurchases() {
Debug.Log("Continuing promotional purchases in 5 seconds");
yield return new WaitForSeconds(5);
Debug.Log("Continuing promotional purchases now");
m_AppleExtensions.ContinuePromotionalPurchases (); // iOS and tvOS only
}
Apple ストアでテストをするには ITunes Connect でテストアカウントを作成し、そのテストアカウントで iTunes に接続しなければいけません。
iOS デバイスやラップトップで App Store からサインアウトしてアプリを起動すると、課金やリストアを行うときにログインするよう求められます。
NoProductsAvailable が原因で初期化に失敗した時は、以下の項目を確認してください。
デスクトップの Mac ビルドをビルドする場合は、Unity のビルド設定で Mac App Store validation を選択する必要があります。
アプリケーションをビルドしたら、バンドル ID とバージョン文字列で info.plist ファイルを更新します。.app ファイル上で右クリックし、show package contents を選択し、info.plist ファイルを探し、CFBundleIdentifier 文字列をアプリケーションのバンドル ID に更新します。
その後、アプリケーションに署名をし、パッケージ化し、インストールします。OSX ターミナルで以下のコマンドを実行する必要があります。
codesign -f --deep -s "3rd Party Mac Developer Application: " your.app/Contents/Plugins/unitypurchasing.bundle
codesign -f --deep -s "3rd Party Mac Developer Application: " your.app
productbuild --component your.app /Applications --sign "3rd Party Mac Developer Installer: " your.pkg
バンドルに署名するには、Contents.meta ファイルが存在する場合、まずこれを削除する必要があります。your.app/Contents/Plugins/unitypurchasing.bundle/Contents.meta
正しくパッケージをインストールするには、新しく作成したパッケージを実行する前にパッケージ化していない .app ファイルを削除しておく必要があります。
そして、アプリケーションフォルダーからアプリケーションを起動します。初回は iTunes アカウントの詳細を入力するよう求められるので、iTunes Connect のテストユーザーアカウントでログインします。そうすると、Sandbox 環境でテスト購入できるようになります。