レシートの難読化
ユーザーが購入しようとしているコンテンツがすでにデバイス上に存在する場合、アプリケーションはこれをアンロックするだけでよいかどうかを判断する必要があります。
Unity IAP には、未購入のコンテンツを非表示にしたり、Google Play ストアや Apple App Store を介してレシートを検証、解析したりするためのツールが備わっています。
暗号化キーの難読化
レシート検証は、既知の暗号化キーを使用して実行されます。アプリケーションでは、暗号化された Google Play の公開鍵や Apple の証明書がこれに当たります。
ユーザーがこれらを置き換えることができてしまうと、レシート検証のチェックが無力化してしまうため、ユーザーが容易にこれらのキーを見つけて改変できないようにすることが重要です。
Unity IAP には、アプリケーション内で暗号化キーを難読化するのに役立つツールが用意されています。このツールを使用すると、キーがわかりにくく乱雑化され、キーにアクセスするのが非常に困難になります。このツールを使用するには、Unity のメニューバーで、Services > In-App Purchasing > IAP Receipt Validation Obfuscator の順に移動します。
このウィンドウでは、Apple のルート証明書、StoreKit Test 証明書 (Unity IAP にバンドルされている)、および Google Play の公開鍵 (アプリケーションの Google Play 開発者コンソールのサービス & API ページからコピー) をそれぞれ C# クラス (AppleTangle、AppleStoreKitTestTangle、GooglePlayTangle) に暗号化しています。これらは次のセクションで使用するために、プロジェクトに追加されています。
Apple ストアのみを対象とする場合は Google Play の公開鍵を提供する必要はなく、その逆も同様です。
レシートの検証
CrossPlatformValidator
クラスを使用すると、Google Play ストアと Apple ストアの両方にわたって検証を行えます。
このクラスには、Google Play の公開鍵、または Apple の証明書 1 つ、あるいは両方のプラットフォームにわたって検証する場合はそれら両方を指定する必要があります。Apple のルート証明書と "StoreKit Test"(*) 証明書の両方を指定することはできません。ランタイムかビルドタイムのスイッチを使用して選択した 1 つのみを渡す必要があります。
CrossPlatformValidator
は、以下の 2 つのチェックを実行します。
- 署名検証を通じて、レシートの真正性をチェックします。
- レシートのアプリケーションバンドル ID をアプリケーション内の ID と比較します。これらが一致しない場合は、InvalidBundleId 例外が発生します。
この検証ツールは Google Play および Apple のプラットフォームで生成されたレシートのみを検証します。エディターで生成されたフェイクも含め、他のプラットフォームで生成されたレシートの場合は、IAPSecurityException が発生します。
CrossPlatformValidator
オブジェクトは、購入の処理に遅れないように作成してください。Unity IAP の初期化中に、以前のセッションで保留中の購入がストアから取得されて処理される可能性があることに注意が必要です。このタイプの永続化されたオブジェクトを使用している場合は、Unity IAP を初期化する前に作成してください。
秘密鍵を指定していないプラットフォームのレシートを検証しようとすると、MissingStoreSecretException がスローされます。
public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
{
bool validPurchase = true; // レシート検証のないプラットフォームの場合は有効と推定します。
// Unity IAP の認証ロジックは以下のプラットフォームにのみ含まれます。
# if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
// エディターの難読化ウィンドウで用意した秘密鍵を使用して認証ツールを
// 準備します。
var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
AppleTangle.Data(), Application.bundleIdentifier);
try {
// Google Play では、結果は単一のプロダクト ID です。
// Apple ストアでは、レシートに複数のプロダクトが含まれます。
var result = validator.Validate(e.purchasedProduct.receipt);
// 参照用に、レシートの一覧を表示します。
Debug.Log("Receipt is valid. Contents:");
foreach (IPurchaseReceipt productReceipt in result) {
Debug.Log(productReceipt.productID);
Debug.Log(productReceipt.purchaseDate);
Debug.Log(productReceipt.transactionID);
}
} catch (IAPSecurityException) {
Debug.Log("Invalid receipt, not unlocking content");
validPurchase = false;
}
# endif
if (validPurchase) {
// ここで適切なコンテンツをアンロックします。
}
return PurchaseProcessingResult.Complete;
}
Apple の証明書を選択する: Apple ルートまたは StoreKit Test
(*) Unity IAP は、StoreKit Test ストアシミュレーションによる購入のレシート検証に対応しています。
Apple の Xcode 12 には、"StoreKit Test" の機能スイートが実装されており、開発者は Apple App Store Connect の Sandbox 構成を使用することなく、より簡単に IAP をテストできます。
レシート検証の CrossPlatformValidator
を作成するときは、通常の AppleTangle
クラスの代わりに AppleStoreKitTestTangle
クラスを使用します。どちらの Tangle クラスも Receipt Validation Obfuscator によって生成されます。
public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
{
bool validPurchase = true;
# if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
// Apple の証明書を 1 つ選択します。AppleStoreKitTestTangle では、
// Storekit 構成ファイルを使用するように設定されたアクティブな Xcode スキームが必要です。
// ここではコードまたは Project Settings >
// Player > Scripting Define Symbols で定義されたシンボルを使用して、
// 次回 Xcode でテストする際に使用する Apple IAP システムを選択します。
# if !DEBUG_STOREKIT_TEST
var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
AppleTangle.Data(), Application.bundleIdentifier);
# else
var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
AppleStoreKitTestTangle.Data(), Application.bundleIdentifier);
# endif
try {
validator.Validate(e.purchasedProduct.receipt);
} catch (IAPSecurityException) {
validPurchase = false;
}
# endif
if (validPurchase) { }
return PurchaseProcessingResult.Complete;
}
詳細な検証
レシートの有効性だけでなく、それに含まれる情報をチェックすることも重要です。ユーザーが購入していないコンテンツにアクセスを試みるときの常套手段は、他のプロダクトやアプリケーションのレシートを指定する方法です。これらのレシートは本物であるため、検証を通過してしまいます。そのため、CrossPlatformValidator によって解析されたプロダクト ID に基づいて判断する必要があります。
ストア固有の情報
購入レシートのフィールドは、ストアによって異なります。ストア固有のフィールドにアクセスするには、IPurchaseReceipt
を GooglePlayReceipt
と AppleInAppPurchaseReceipt
という 2 つの異なるサブタイプにダウンキャストします。
var result = validator.Validate(e.purchasedProduct.receipt);
Debug.Log("Receipt is valid. Contents:");
foreach (IPurchaseReceipt productReceipt in result) {
Debug.Log(productReceipt.productID);
Debug.Log(productReceipt.purchaseDate);
Debug.Log(productReceipt.transactionID);
GooglePlayReceipt google = productReceipt as GooglePlayReceipt;
if (null != google) {
// これは Google の注文 ID です。
// サンドボックスでテストしている場合は、これが null になります。
// なぜなら Google のサンドボックスでは注文 ID が発行されないためです。
Debug.Log(google.transactionID);
Debug.Log(google.purchaseState);
Debug.Log(google.purchaseToken);
}
AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
if (null != apple) {
Debug.Log(apple.originalTransactionIdentifier);
Debug.Log(apple.subscriptionExpirationDate);
Debug.Log(apple.cancellationDate);
Debug.Log(apple.quantity);
}
}
Apple の未加工レシートの解析
AppleValidator
クラスを使用すると、Apple のレシートに関する詳細情報を抽出できます。このクラスは iOS バージョン 7.0 以降の アプリケーションのレシートでのみ機能し、Apple 非推奨のトランザクションレシートでは機能しません。
# if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
// IAP の初期化中に IAppleConfiguration への参照を取得します。
var appleConfig = builder.Configure<IAppleConfiguration>();
var receiptData = System.Convert.FromBase64String(appleConfig.appReceipt);
AppleReceipt receipt = new AppleValidator(AppleTangle.Data()).Validate(receiptData);
Debug.Log(receipt.bundleID);
Debug.Log(receipt.receiptCreationDate);
foreach (AppleInAppPurchaseReceipt productReceipt in receipt.inAppPurchaseReceipts) {
Debug.Log(productReceipt.transactionIdentifier);
Debug.Log(productReceipt.productIdentifier);
}
# endif
AppleReceipt
タイプは Apple の ASN1 レシート形式をモデルにしています。フィールドの説明については、Apple のドキュメント を参照してください。