Version: 6000.3
语言: 中文
采购收据
商店扩展

收据验证

收据验证可帮助您防止用户访问他们尚未购买的内容。

验证点

最佳做法是在分发应用程序内容时验证收据。

  • 本地验证:对于客户端内容,其中所有内容都包含在应用程序中,并在购买后启用,验证应在目标设备上进行,而无需连接到远程服务器。Unity IAP 旨在支持应用程序内的本地验证。有关详细信息,请参阅下面的本地验证
  • 远程验证:对于服务器端内容,其中内容在购买后下载,应在发布内容之前在服务器上进行验证。Unity 不支持服务器端验证;但是,可以使用第三方解决方案,例如 Nobuyori Takahashi 的 IAP 项目

本地验证

重要提示:虽然 Unity IAP 提供了本地验证方法,但本地验证更容易受到欺诈。尽可能在服务器端验证敏感事务被认为是最佳实践。有关更多信息,请参阅 AppleAndroid 关于防欺诈的文档。

如果用户购买的内容已存在于设备上,则应用程序只需决定是否解锁它。

Unity IAP 提供了一些工具来帮助您隐藏内容,并通过 Google Play 和 Apple 商店验证和解析收据。

混淆加密密钥

使用已知的加密密钥执行收据验证。对于您的应用程序,这是一个加密的 Google Play 公钥和/或 Apple 的根证书。

如果用户可以替换这些密钥,他们可能会失败您的收据验证检查,因此让用户难以轻松找到和修改这些密钥非常重要。

Unity IAP 提供了一个工具,可以帮助您混淆应用程序中的加密密钥。这会混淆或混乱密钥,使用户更难访问它们。在 Unity 菜单栏中,转到窗口 > Unity IAPUnity In App Purchase
的缩写 术语表
> IAP 收据验证混淆器

混淆器窗口
混淆器窗口

此窗口将 Apple 的根证书(与 Unity IAP 捆绑在一起)和您的 Google Play 公钥(来自应用程序的 Google Play 开发者控制台的“服务和 API”页面)编码为两个不同的 C# 文件:AppleTangleGooglePlayTangle。这些将添加到您的项目中,以便在下一节中使用。

请注意,如果您仅针对 Apple 商店,则不必提供 Google Play 公钥,反之亦然。

验证收据

使用CrossPlatformValidator类,用于在 Google Play 和 Apple 商店中进行验证。

如果希望跨两个平台进行验证,则必须向此类提供 Google Play 公钥或 Apple 的根证书,或两者兼而有之。

CrossPlatformValidator执行两项检查:

  • 通过签名验证检查收据的真实性。
  • 收据上的应用程序捆绑标识符将与应用程序中的应用程序捆绑标识符进行比较。如果它们不匹配,则会引发 InvalidBundleId 异常。

请注意,验证器仅验证在 Google Play 和 Apple 平台上生成的收据。在任何其他平台上生成的收据(包括在编辑器中生成的虚假收据)都会抛出 IAPSecurityException

如果您尝试验证尚未为其提供密钥的平台的收据,则会引发 MissingStoreSecretException

public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
{
    bool validPurchase = true; // Presume valid for platforms with no R.V.

    // Unity IAP's validation logic is only included on these platforms.
#if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
    // Prepare the validator with the secrets we prepared in the Editor
    // obfuscation window.
    var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
        AppleTangle.Data(), Application.bundleIdentifier);

    try {
        // On Google Play, result has a single product ID.
        // On Apple stores, receipts contain multiple products.
        var result = validator.Validate(e.purchasedProduct.receipt);
        // For informational purposes, we list the receipt(s)
        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) {
        // Unlock the appropriate content here.
    }

    return PurchaseProcessingResult.Complete;
}

重要的是,您不仅要检查收据是否有效,还要检查其中包含哪些信息。用户尝试在不购买的情况下访问内容的一种常见技术是提供来自其他产品或应用程序的收据。这些收据是真实的,并且确实通过了验证,因此您应该根据 CrossPlatformValidator 分析的产品 ID 做出决策。

特定于商店的详细信息

不同的商店在其购买收据中有不同的字段。要访问特定于商店的字段,IPurchaseReceipt可以降调为两种不同的子类型:GooglePlayReceiptAppleInAppPurchaseReceipt.

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) {
        // This is Google's Order ID.
        // Note that it is null when testing in the sandbox
        // because Google's sandbox does not provide Order IDs.
        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 收据的详细信息。请注意,此类仅适用于 7.0 版及更高版本的 iOS 应用收据,不适用于 Apple 已弃用的交易收据。

#if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
// Get a reference to IAppleConfiguration during IAP initialization.
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

AppleReceipttype 模型 Apple 的 ASN1 收据格式。有关其字段的说明,请参阅 Apple 的文档

采购收据
商店扩展