大多数时间恢复都可以,但是 有时latest_receipt_info
中没有关于当前有效的自动更新的记录(尝试购买时活动订阅,我会收到一条消息,说明我已经订阅了。有人遇到过吗?
其次,伙计们,您可以查看我的代码吗?当然,我不应该直接通过App Store
服务器验证收据,但是我相信这不是问题。我怀疑代码是否有错误(例如我上面写的收据中没有最新记录),但我可能错了。也许我错过了一些重要的事情。
P.S。我首先怀疑的一件事是我可能无法正确比较日期(从收据中提取最新的到期日期后,我将其与Date()进行了这种比较:
if expirationDate > Date() {
// There is an active subscription
}
我想也许我必须将Date()
和expirationDate转换为GMT+0
然后进行比较,但是两个日期似乎都是GMT + 0。
代码如下:
交易观察者
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
var purchased = false
var restored = false
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
purchased = true
queue.finishTransaction(transaction)
case .restored:
restored = true
queue.finishTransaction(transaction)
case .deferred:
queue.finishTransaction(transaction)
case .failed:
purchaseProductCompletionHandler?(IAPError.purchaseFailed)
queue.finishTransaction(transaction)
}
}
if purchased {
processReceipt(completion: { error, subscriptionExpirationDate, appIsPurchased in
if let error = error {
self.purchaseProductCompletionHandler?(error)
} else {
self.purchaseProductCompletionHandler?(nil)
}
})
} else if restored {
processReceipt(completion: { error, subscriptionExpirationDate, appIsPurchased in
self.restorePurchasesCompletionHandler?(error, subscriptionExpirationDate, appIsPurchased)
})
}
}
验证收据并对其进行解析的代码
private func processReceipt(completion: @escaping (_ error: Error?, _ subscriptionExpirationDate: Date?, _ appIsPurchased: Bool?) -> Void) {
guard let receiptUrl = Bundle.main.appStoreReceiptURL, let receiptData = try? Data(contentsOf: receiptUrl) else {
completion(IAPError.receiptNotFound, nil, nil)
return
}
makeReceiptValidationRequest(receiptData: receiptData, url: receiptValidationProductionUrl, completion: { error, data in
if let error = error {
completion(error, nil, nil)
return
}
guard let data = data else {
completion(IAPError.receiptValidationRequestCompletedWithoutErrorAndData, nil, nil)
return
}
guard let receiptStatus = try? self.extractReceiptStatus(data) else {
completion(IAPError.jsonProcessingFailed, nil, nil)
return
}
if receiptStatus == 21007 {
self.makeReceiptValidationRequest(receiptData: receiptData, url: self.receiptValidationSandboxUrl, completion: { error, data in
if let error = error {
completion(error, nil, nil)
return
}
guard let data = data else {
completion(IAPError.receiptValidationRequestCompletedWithoutErrorAndData, nil, nil)
return
}
let purchasesInfo = self.processReceiptValidationRequestResponse(data: data)
completion(purchasesInfo.error, purchasesInfo.subscriptionExpirationDate, purchasesInfo.appIsPurchased)
})
} else {
let purchasesInfo = self.processReceiptValidationRequestResponse(data: data)
completion(purchasesInfo.error, purchasesInfo.subscriptionExpirationDate, purchasesInfo.appIsPurchased)
}
})
}
makeReceiptValidationRequest()
private func makeReceiptValidationRequest(receiptData: Data, url: URL, completion: @escaping (_ error: Error?, _ data: Data?) -> Void) {
let payload = ["receipt-data": receiptData.base64EncodedString().toJSON(),
"password": secretKey.toJSON()]
guard let serializedPayload = try? JSON.dictionary(payload).serialize() else {
completion(IAPError.receiptValidationRequestCreationFailed, nil)
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = serializedPayload
URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
if let error = error {
completion(error, nil)
} else if let data = data {
completion(nil, data)
} else {
completion(IAPError.receiptValidationRequestCompletedWithoutErrorAndData, nil)
}
}).resume()
}
processReceiptValidationRequestResponse()
private func processReceiptValidationRequestResponse(data: Data) -> (error: Error?, subscriptionExpirationDate: Date?, appIsPurchased: Bool?) {
guard let purchasesInfo = try? extractPurchasesInfo(data) else {
return (IAPError.jsonProcessingFailed, nil, nil)
}
if purchasesInfo.receiptStatus != 0 {
return (IAPError.invalidReceipt, nil, nil)
}
if let subscriptionExpirationDate = purchasesInfo.subscriptionExpirationDate, subscriptionExpirationDate > Date() {
UserDefaults.standard.set(subscriptionExpirationDate, forKey: PurchasesInfoKey.subscriptionExpirationDate)
}
UserDefaults.standard.set(purchasesInfo.freeTrialWasUsedEarlier, forKey: PurchasesInfoKey.freeTrialWasUsedEarlier)
UserDefaults.standard.set(purchasesInfo.appIsPurchased, forKey: PurchasesInfoKey.appIsPurchased)
return (nil, purchasesInfo.subscriptionExpirationDate, purchasesInfo.appIsPurchased)
}
extractPurchasesInfo()
private func extractPurchasesInfo(_ data: Data) throws -> (receiptStatus: Int, subscriptionExpirationDate: Date?, freeTrialWasUsedEarlier: Bool?, appIsPurchased: Bool?) {
let jsonData = try JSON(data: data)
let receiptStatus = try jsonData.getInt(at: "status")
if receiptStatus != 0 {
return (receiptStatus, nil, nil, nil)
}
var subscriptionExpirationDate: Date?
var freeTrialWasUsedEarlier = false
var appIsPurchased = false
if let receipt = jsonData["receipt"], let inApps = try? receipt.getArray(at: "in_app") {
for inApp in inApps {
let productId = try? inApp.getString(at: "product_id")
if productId == ProductId.oneTimePurchase.rawValue {
appIsPurchased = true
break
}
}
}
if let latestReceiptInfo = try? jsonData.getArray(at: "latest_receipt_info") {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
for receipt in latestReceiptInfo {
guard let productId = try? receipt.getString(at: "product_id") else {
continue
}
if let isTrialPeriod = try? receipt.getString(at: "is_trial_period"), isTrialPeriod == "true", [, ProductId.oneMonthWithFreeTrial.rawValue, ProductId.oneYearWithFreeTrial.rawValue].contains(productId) {
freeTrialWasUsedEarlier = true
} else if productId == ProductId.oneTimePurchase.rawValue {
appIsPurchased = true
}
guard let expiresDateString = try? receipt.getString(at: "expires_date"), let expiresDate = dateFormatter.date(from: expiresDateString) else {
continue
}
if subscriptionExpirationDate == nil || expiresDate > subscriptionExpirationDate! {
subscriptionExpirationDate = expiresDate
}
}
}
return (receiptStatus, subscriptionExpirationDate, freeTrialWasUsedEarlier, appIsPurchased)
}