我正在服务器端验证我的消费品应用内购买。
也就是说,我通过以下方式从客户端获取收据:
.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 等中创建产品),这样收据验证就可以正常工作。
附注我在本地模拟器中测试应用内购买时遇到了同样的问题
首先,你的收据肯定是格式错误的,21002状态码意味着它格式错误。您也可以在这里查看,https://www.revenuecat.com/apple-receipt-checker。
你的 swift 和 js 代码似乎 100% 合法,所以不用担心!
问题可能是您的收据文件已损坏,您可以从设备中完全删除该应用程序并重新安装吗?
或者在不同的设备上尝试。