验证 iOS 应用内购买收据始终返回错误 21002

问题描述 投票:0回答:3

我正在服务器端验证我的消费品应用内购买。

也就是说,我通过以下方式从客户端获取收据:

    .onChange(of: self.storeObserver.paymentStatus) { status in
        switch status {
        case .purchasing:
            print("Payment status: purchasing")
        case .failed:
            self.creatingGame = false
            print("Payment status: failed")
        case .deferred:
            print("Payment status: deferred")
        case .restored:
            print("Payment status: restored")
        case .purchased:
            // Get the receipt if it's available
            if Bundle.main.appStoreReceiptURL == nil {
                print("appStoreReceiptURL is nil")
            }
            if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
                FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {
                do {
                    let receiptData = try Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)

                    let receiptString = receiptData.base64EncodedString(options: [])
                    print("receiptString: \(receiptString)")
                    // Read receiptData
                    createGame(receiptString: receiptString)
                }
                catch { print("Couldn't read receipt data with error: " + error.localizedDescription) }
            }
            
            print("Payment status: purchased")
        default:
            print("Payment status: default")
        }
    }

private func createGame(receiptString: String){
    let data: [String:Any?] = [
        "gameName": self.gameName,
        "receipt": receiptString
    ]
    callFunction(name: "validateReceipt", data: data){ result, err in
    }

print("receiptString: (receiptString)") 打印以下内容:

receiptString: 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

然后将收据字符串发送到服务器:

exports.validateReceipt = functions.https.onCall(async (data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('permission-denied', 'The function must be called while authenticated.');
  }
  if (!data.receipt) {
    throw new functions.https.HttpsError('permission-denied', 'receipt is required');
  }
  // Now we fetch the receipt from Apple
  let body = {
    'receipt-data': data.receipt,
    // 'password': 'MY_SECRET_PASSWORD', // Not needed for Consumable IAP's
    'exclude-old-transactions': true
  };
  const options = {
    method: 'post',
    body: JSON.stringify(body),
    headers: {'Content-Type': 'application/json'},
  };
  return validateReceiptData('https://buy.itunes.apple.com/verifyReceipt', options, data, context);
});

function validateReceiptData(url, options, data, context) {
    var retries = 0

  return fetch(url, options).then(result => {
    return result.json();
      }).then(data => {
        if (data.status === 21007 && retries === 0) {
            retries += 1
          // Retry with sandbox URL
          console.log("Try sandbox URL");
          return validateReceiptData('https://sandbox.itunes.apple.com/verifyReceipt', options, data, context);
        }
        console.log(`data.status: ${data.status}`); // prints status code 21002
        // Process the result
        if (data.status !== 0) {
            console.log("The status code is not 0, so the receipt is invalid"); // function returns here
          return false;
        }
        const latestReceiptInfo = data.latest_receipt_info[0];
        console.log(`Receipt data is valid: ${latestReceiptInfo}`);
        if (data.type === "join"){
            return joinGame(data, context)
        } 
        else if (data.type === "create"){
            return createGame(data, context)
        }
        return 400;
      });
}

如您所见,上面的代码尝试生产

verifyReceipt
端点,如果失败并出现沙箱错误 (21007),它将尝试沙箱端点。然而,它从不尝试沙箱端点,因为第一次尝试时出现了不同的错误:

21002
The data in the receipt-data property was malformed or the service experienced a temporary issue. Try again.

我不知道为什么会出现这个错误。我正在沙箱中测试这是否有任何区别。

知道为什么我总是收到此错误吗?

编辑:我已经连续 3 天遇到同样的错误,不断进行测试,尝试了所有方法,但每次仍然得到 21002。我已经彻底迷失了。

ios swiftui in-app-purchase storekit ios14
3个回答
7
投票

看起来您正在尝试在模拟器中使用 storekit 本地测试环境验证收据(在 wwdc2020 上提出),对吧?我的意思是,您以这种方式在应用程序中获得收据,无论您是否使用应用程序或某些单独的后端应用程序的 api 调用来检查此收据(是的,我已检查)

如果是这样,它将不起作用

您应该在没有此新功能的情况下完成所有操作,就像 13 及以下版本一样(通过在 appstoreconnect 等中创建产品),这样收据验证就可以正常工作。

附注我在本地模拟器中测试应用内购买时遇到了同样的问题


3
投票

首先,你的收据肯定是格式错误的,21002状态码意味着它格式错误。您也可以在这里查看,https://www.revenuecat.com/apple-receipt-checker

你的 swift 和 js 代码似乎 100% 合法,所以不用担心!

问题可能是您的收据文件已损坏,您可以从设备中完全删除该应用程序并重新安装吗?

或者在不同的设备上尝试。


0
投票

我有同样的问题,并且犯了一个错误:从 iTunes 帐户同步包后,我忘记在编辑方案中选择“无”。

© www.soinside.com 2019 - 2024. All rights reserved.