ios - 验证 iOS 应用内购买的收据总是返回错误 21002
问题描述
我正在服务器端验证我的消耗品应用内购买。
也就是说,我通过以下方式从客户端获取收据:
.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。我很迷茫。
解决方案
看起来您正试图在模拟器中使用 storekit 本地测试环境验证收据(在 wwdc2020 上提出),对吧?我的意思是您以这种方式在应用程序中获得收据,无论您是否会通过应用程序或某个单独的后端应用程序的 api 调用检查此收据(是的,我已检查)
如果是这样,它将不起作用
你应该在没有这个新功能的情况下做所有的事情,就像它在 13 和更低版本(通过在 appstoreconnect 中创建产品等)一样,这样收据验证就可以正常工作。
ps 我在本地模拟器中测试应用内购买时遇到了同样的问题
推荐阅读
- vue.js - Vue 组件范围样式未显示 b-form-radio-group 按钮变体
- r - 用于 ARDL 包输出的 Stargazer:“错误:无法识别的对象类型”
- android - Android 工作管理器在我的 oppo 手机中无法使用
- powershell - 在 PowerShell 中使用 PrintDocument 的问题
- sql - 使用 mariaDB 更新父表时出现错误 1452
- loops - 两个 AutoHotkey 循环以不同的时间间隔发送文本
- python-3.x - 关于OOP自参数未填写
- kotlin - 嵌套协方差如何在 Kotlin 中工作?
- java - 如何使用 netbeans 将 javafx 添加到 java 15.01 sdk
- c# - 索引未超出范围时的“索引超出范围异常”