版本:Unity 6 (6000.0)
语言:English
购买收据
商店扩展

收据验证

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

验证点

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

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

本地验证

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

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

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

混淆加密密钥

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

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

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

The Obfuscator window
混淆器窗口

此窗口将 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

AppleReceipt类型对 Apple 的 ASN1 收据格式进行建模。有关其字段的说明,请参阅Apple 的文档

购买收据
商店扩展