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

编程入门 行业动态 更新时间:2024-10-09 08:25:48
本文介绍了验证iOS应用内购买收据始终返回错误21002的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

我正在服务器端验证我的消耗性应用内购买.

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

.onChange(of:self.storeObserver.paymentStatus){状态为开关状态{案例购买:打印(付款状态:正在购买")案例.失败:self.creatingGame = false打印(付款状态:失败")案例.deferred:打印(付款状态:已延迟")案例.restored:打印(付款状态:已恢复")案例购买://获取收据(如果有)如果Bundle.main.appStoreReceiptURL == nil {打印("appStoreReceiptURL为零")}如果让appStoreReceiptURL = Bundle.main.appStoreReceiptURL,FileManager.default.fileExists(atPath:appStoreReceiptURL.path){做 {让receiveData =尝试数据(contentsOf:appStoreReceiptURL,选项:.alwaysMapped)让receiveString = receiveData.base64EncodedString(选项:[])print(" receiptString:\(receiptString)")//读取receiveDatacreateGame(receiptString:receiveString)}catch {print(无法读取错误出现的收据数据:" + error.localizedDescription)}}打印(付款状态:已购买")默认:打印(付款状态:默认")}}私人功能createGame(receiptString:String){让数据:[String:Any?] = ["gameName":self.gameName,"receipt":receiveString]callFunction(name:"validateReceipt",data:data){结果,输入错误}

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

receiptString:MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwGggCSAOIIBTDGCAUgwDwIBAAIBAQQHDAVYY29kZTALAgEBAgEBBAMCAQAwGwIBAgIBAQQTDBFjb20ucXVpemNoYW1waW9uczALAgEDAgEBBAMMATEwEAIBBAIBAQQIXd + 6fwYAAAAwHAIBBQIBAQQUCo9PL6ReAWL/RqZoNgvev/Ns0N4wCgIBCAIBAQQCFgAwIgIBDAIBAQQaFhgyMDIxLTAyLTIwVDIxOjA5OjE3KzExMDAwegIBEQIBAQRyNVAwDAICBqUCAQEEAwIBATAwAgIGpgIBAQQnDCVjb20ucXVpemNoYW1waW9ucy5nYW1lUmVnaXN0cmF0aW9uQVU1MA0CAganAgEBBAQMAjE0MB8CAgaoAgEBBBYWFDIwMjEtMDItMjBUMjE6MDk6MTdaMCICARUCAQEEGhYYNDAwMS0wMS0wMVQxMTowMDowMCsxMTAwAAAAAAAAoIIDeDCCA3QwggJcoAMCAQICAQEwDQYJKoZIhvcNAQELBQAwXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0QREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0MB4XDTIwMDQwMTE3NTIzNVoXDTQwMDMyNzE3NTIzNVowXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQLLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA23 + QPCxzD9uXJkuTuwr4oSE + yGHZJMheH3U + 2pPbMRqRgLm/5QzLPLsORGIm + gQptknnb + Ab5g1ozSVuw3YI9UoLrnp0PMSpC7PPYg/7tLz324ReKOtHDfHti6z1n7AJOKNue8smUIoa4YnRcnYLOUzLT27As1 + 3lbq5qF1KdKvvb0GlfgmNuj09zXBX2O3v1dp3yJMEHO8JiHhlzoHyjXLnBxpuJhL3MrENuziQawbE/A3llVDNkci6JfRYyYzhcdtKRfMtGZYDVoGmRO51d1tTz3isXbo + X1ArXCmM3cLXKhffIrTX5Hior6htp8HaaC1mzM8pC1As48L75l8SwQIDAQABozswOTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIChDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAsgDgPPHo6WK9wNYdQJ5XuTiQd3ZS0qhLcG64Z5n7s4pVn + 8dKLhfKtFznzVHN7tG03YQ8vBp7M1imXH5YIqESDjEvYtnJbmrbDNlrdjCmnhID + nMwScNxs9kPG2AWTOMyjYGKhEbjUnOCP9mwEcoS + tawSsJViylqgkDezIx3OiFeEjOwMUSEWoPDK4vBcpvemR/ICx15kyxEtP94x9eDX24WNegfOR/Y6uXmivDKtjQsuHVWg05G29nKKkSg9aHeG2ZvV6zCuCYzvbqw45taeu3QIE9hz1wUdHEXY2l3H9qWBreYHY3Uuz/rBldDBUvig/1icjXKx0e7CuRBac9TzGCAY8wggGLAgEBMGQwXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0AdEBMA0GCWCGSAFlAwQCAQUAMA0GCSqGSIb3DQEBCwUABIIBALlN1kURKNigANTeoN67kCxQxhjHZ6LKG5ToRMyh3TwNelXxcRWwlqSvROT0XRbzVz0qvHrxu + ts9YXYTNqFO/3XdfdOke1XY/RK0hrlevS0P + E + Tot4BUfbazaUea17/A6wNqoDw8aWKcfYZFK95EET96jaqZmr2ykqTqRTnfzVjpQRvfuZJ2srVcsNc8ZcEqTPE4l2MW2sr2gYBq4lscJTtBEvQAKpWo93q6UsveriTnvbaVenfImIDTGYZ0edaS3egkfmDoycaDqfFJIYqxwa7E3Fl58l2 + EI/4Z2ux4luwpZDjU/UxQ4XcDSuv3 + Za7snaq4SWFAoQqG7jXtLigAAAAAAAA =

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

exports.validateReceipt =函数.https.onCall(异步(数据,上下文)=> {如果(!context.auth){抛出新函数.https.HttpsError('permission-denied','该函数必须在认证时调用.');}如果(!data.receipt){抛出新函数.https.HttpsError('permission-denied','receipt is required');}//现在,我们从Apple取回收据让身体= {'receipt-data':data.receipt,//'password':'MY_SECRET_PASSWORD',//消耗性IAP不需要'排除旧交易':true};const options = {方法:发布",正文:JSON.stringify(body),标头:{'Content-Type':'application/json'},};返回validateReceiptData('buy.itunes.apple/verifyReceipt',选项,数据,上下文);});函数validateReceiptData(URL,选项,数据,上下文){var重试= 0返回fetch(url,options).then(result => {返回result.json();}).then(data => {if(data.status === 21007&&retries === 0){重试+ = 1//使用沙箱网址重试console.log(尝试沙盒URL");返回validateReceiptData('sandbox.itunes.apple/verifyReceipt',选项,数据,上下文);}console.log(`data.status:$ {data.status}`);//显示状态码21002//处理结果如果(data.status!== 0){console.log(状态码不是0,所以收据无效");//函数返回此处返回false;}const LatestReceiptInfo = data.latest_receipt_info [0];console.log(`收据数据有效:$ {latestReceiptInfo}`);如果(data.type ==="join"){返回joinGame(数据,上下文)}否则,如果(data.type ===创建"){返回createGame(数据,上下文)}返回400;});}

如您所见,上面的代码尝试使用生产 verifyReceipt 端点,如果失败并显示沙箱错误(21007),它将尝试沙箱端点.但是,它永远不会尝试沙盒端点,因为第一次尝试时会出现其他错误:

21002收据数据属性中的数据格式错误,或者服务遇到临时问题.再试一次.

我不知道为什么会发生此错误.我正在沙盒中测试是否有任何区别.

知道为什么我会不断收到此错误吗?

在持续测试的三天内,我一直遇到相同的错误,尝试所有操作,每次仍然得到21002.我很迷茫.

解决方案

看起来像您正在尝试在模拟器(建议在wwdc2020上)中使用storekit本地测试环境来验证收货,对吗?我的意思是您以这种方式在应用程序中获取收据,无论您是通过应用程序还是某些单独的后端应用程序(是的,我被选中)中的api调用来检查此收据都没关系

如果是这样,它将无法正常工作

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

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

I am validating my consumable in-app purchase on the server-side.

That is, I get the receipt from the client-side via:

.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)") prints the following:

receiptString: MIAGCSqGSIb3DQEHAqCAMIACAQExDzANBglghkgBZQMEAgEFADCABgkqhkiG9w0BBwGggCSAOIIBTDGCAUgwDwIBAAIBAQQHDAVYY29kZTALAgEBAgEBBAMCAQAwGwIBAgIBAQQTDBFjb20ucXVpemNoYW1waW9uczALAgEDAgEBBAMMATEwEAIBBAIBAQQIXd+6fwYAAAAwHAIBBQIBAQQUCo9PL6ReAWL/RqZoNgvev/Ns0N4wCgIBCAIBAQQCFgAwIgIBDAIBAQQaFhgyMDIxLTAyLTIwVDIxOjA5OjE3KzExMDAwegIBEQIBAQRyNVAwDAICBqUCAQEEAwIBATAwAgIGpgIBAQQnDCVjb20ucXVpemNoYW1waW9ucy5nYW1lUmVnaXN0cmF0aW9uQVU1MA0CAganAgEBBAQMAjE0MB8CAgaoAgEBBBYWFDIwMjEtMDItMjBUMjE6MDk6MTdaMCICARUCAQEEGhYYNDAwMS0wMS0wMVQxMTowMDowMCsxMTAwAAAAAAAAoIIDeDCCA3QwggJcoAMCAQICAQEwDQYJKoZIhvcNAQELBQAwXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0QREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0MB4XDTIwMDQwMTE3NTIzNVoXDTQwMDMyNzE3NTIzNVowXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQLLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA23+QPCxzD9uXJkuTuwr4oSE+yGHZJMheH3U+2pPbMRqRgLm/5QzLPLsORGIm+gQptknnb+Ab5g1ozSVuw3YI9UoLrnp0PMSpC7PPYg/7tLz324ReKOtHDfHti6z1n7AJOKNue8smUIoa4YnRcnYLOUzLT27As1+3lbq5qF1KdKvvb0GlfgmNuj09zXBX2O3v1dp3yJMEHO8JiHhlzoHyjXLnBxpuJhL3MrENuziQawbE/A3llVDNkci6JfRYyYzhcdtKRfMtGZYDVoGmRO51d1tTz3isXbo+X1ArXCmM3cLXKhffIrTX5Hior6htp8HaaC1mzM8pC1As48L75l8SwQIDAQABozswOTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIChDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDAzANBgkqhkiG9w0BAQsFAAOCAQEAsgDgPPHo6WK9wNYdQJ5XuTiQd3ZS0qhLcG64Z5n7s4pVn+8dKLhfKtFznzVHN7tG03YQ8vBp7M1imXH5YIqESDjEvYtnJbmrbDNlrdjCmnhID+nMwScNxs9kPG2AWTOMyjYGKhEbjUnOCP9mwEcoS+tawSsJViylqgkDezIx3OiFeEjOwMUSEWoPDK4vBcpvemR/ICx15kyxEtP94x9eDX24WNegfOR/Y6uXmivDKtjQsuHVWg05G29nKKkSg9aHeG2ZvV6zCuCYzvbqw45taeu3QIE9hz1wUdHEXY2l3H9qWBreYHY3Uuz/rBldDBUvig/1icjXKx0e7CuRBac9TzGCAY8wggGLAgEBMGQwXzERMA8GA1UEAwwIU3RvcmVLaXQxETAPBgNVBAoMCFN0b3JlS2l0MREwDwYDVQQLDAhTdG9yZUtpdDELMAkGA1UEBhMCVVMxFzAVBgkqhkiG9w0BCQEWCFN0b3JlS2l0AdEBMA0GCWCGSAFlAwQCAQUAMA0GCSqGSIb3DQEBCwUABIIBALlN1kURKNigANTeoN67kCxQxhjHZ6LKG5ToRMyh3TwNelXxcRWwlqSvROT0XRbzVz0qvHrxu+ts9YXYTNqFO/3XdfdOke1XY/RK0hrlevS0P+E+Tot4BUfbazaUea17/A6wNqoDw8aWKcfYZFK95EET96jaqZmr2ykqTqRTnfzVjpQRvfuZJ2srVcsNc8ZcEqTPE4l2MW2sr2gYBq4lscJTtBEvQAKpWo93q6UsveriTnvbaVenfImIDTGYZ0edaS3egkfmDoycaDqfFJIYqxwa7E3Fl58l2+ei/4Z2ux4luwpZDjU/UxQ4XcDSuv3+Za7snaq4SWFAoQqG7jXtLigAAAAAAAA=

And then the receipt string is sent to the server:

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('buy.itunes.apple/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('sandbox.itunes.apple/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; }); }

As you can see, the above code tries the production verifyReceipt endpoint, and if that fails with a sandbox error (21007), it tries the sandbox endpoint. However it never tries the sandbox endpoint as a different error comes up the first try:

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

I have no idea why this error occurs. I am testing in sandbox if that makes any difference.

Any idea why I keep getting this error?

Edit: I have been getting the same error for 3 days with constant testing, trying everything and still getting a 21002 every time. I'm quite lost.

解决方案

looks like you trying to verify receipt with storekit local testing environment in simulator (proposed on wwdc2020), right? i mean you getting receipt in application this way, doesn't matter if you will check this receipt with api call from your application or the some separate backend application (yep, i'm checked)

if so, it will not work

you should do all the things without this new feature, as it was on 13 and below (by creating products in appstoreconnect and so on), this way receipt verification works as it should.

p.s. i faced the same problem with testing in-app purchases in the simulator locally

更多推荐

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

本文发布于:2023-11-26 09:10:36,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1633507.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:收据   错误   iOS

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!