正如我在 cmets 中所写,我不确定这是否是正确的流程,但对于我制作的应用,我使用以下代码检查收据:
有任何疑问请参考the docs我也关注了。
还需要注意的是,Apple 不鼓励直接通过 AppStore 服务器验证(因为无法验证身份,这可能导致中间人攻击)
使用受信任的服务器与 App Store 进行通信。使用您自己的服务器可让您将应用设计为仅识别和信任您的服务器,并确保您的服务器与 App Store 服务器连接。无法直接在用户设备和 App Store 之间建立受信任的连接,因为您无法控制该连接的任何一端,因此很容易受到中间人攻击。
但是,如果可以帮助您,这里有两个 Apple 端点(调试/生产)。
#if DEBUG
private let appStoreValidationURL = URL(string: "https://sandbox.itunes.apple.com/verifyReceipt")!
#else
private let appStoreValidationURL = URL(string: "https://buy.itunes.apple.com/verifyReceipt")!
#endif
同时,关于您需要传递收据的应用程序的SharedSecret,您可以找到有用的信息here。
- 检索收据。
private func loadReceipt() throws -> Data {
guard let url = Bundle.main.appStoreReceiptURL else {
throw ReceiptValidationError.noReceiptData
}
do {
let data = try Data(contentsOf: url)
return data
} catch {
print("Error loading receipt data: \(error.localizedDescription)")
throw ReceiptValidationError.noReceiptData
}
}
- 然后您可以使用 JSON 格式读取内容
[...]
// Handle the try. I skipped that to make easier to read
let data = try! loadReceipt()
let base64String = data.base64EncodedString(options: [])
// Encode data in JSON
let content: [String : Any] = ["receipt-data" : base64String,
"password" : sharedSecret,
"exclude-old-transactions" : true]
- 将您的请求收据发送到 Apple 服务器进行验证。
private func validateLastReceipt(_ data: Data) {
let base64String = data.base64EncodedString(options: [])
// Encode data in JSON
let content: [String : Any] = ["receipt-data" : base64String,
"password" : sharedSecret,
"exclude-old-transactions" : false]
let json = try! JSONSerialization.data(withJSONObject: content, options: [])
// build request
let storeURL = self.appStoreValidationURL
var request = URLRequest(url: storeURL)
request.httpMethod = "POST"
request.httpBody = json
// Make request to app store
URLSession.shared.dataTask(with: request) { (data, res, error) in
guard error == nil, let data = data else {
self.delegate?.validator(self, didFinishValidateWith: error!)
return
}
do {
let decoder = JSONDecoder()
let response = try decoder.decode(ReceiptAppStoreResponse.self, from: data)
} catch {
// Handle error
}
}.resume()
}
这里是我构建的 Decodables 结构。 您将在此处找到检查用户购买商品所需的所有信息!
private struct ReceiptAppStoreResponse: Decodable {
/// Either 0 if the receipt is valid, or one of the error codes listed in Table 2-1.
///
/// For iOS 6 style transaction receipts, the status code reflects the status of the specific transaction’s receipt.
///
/// For iOS 7 style app receipts, the status code is reflects the status of the app receipt as a whole. For example, if you send a valid app receipt that contains an expired subscription, the response is 0 because the receipt as a whole is valid.
let status: Int?
/// A JSON representation of the receipt that was sent for verification.
// let receipt: String?
/// Only returned for receipts containing auto-renewable subscriptions. For iOS 6 style transaction receipts,
/// this is the base-64 encoded receipt for the most recent renewal. For iOS 7 style app receipts, this is the latest
/// base-64 encoded app receipt.
let latestReceipt: String?
/// Only returned for receipts containing auto-renewable subscriptions. For iOS 6 style transaction receipts,
/// this is the JSON representation of the receipt for the most recent renewal. For iOS 7 style app receipts,
/// the value of this key is an array containing all in-app purchase transactions.
/// This excludes transactions for a consumable product that have been marked as finished by your app.
let latestReceiptInfo: [ReceiptInfo]?
/// Only returned for iOS 6 style transaction receipts, for an auto-renewable subscription.
/// The JSON representation of the receipt for the expired subscription.
// let latestExpiredReceiptInfo: String?
/// Only returned for iOS 7 style app receipts containing auto-renewable subscriptions.
/// In the JSON file, the value of this key is an array where each element contains the pending renewal information
/// for each auto-renewable subscription identified by the Product Identifier.
/// A pending renewal may refer to a renewal that is scheduled in the future or a renewal that failed
/// in the past for some reason.
// let pendingRenewalInfo: String?
/// Retry validation for this receipt. Only applicable to status codes 21100-21199
// let isRetryable: Bool?
enum CodingKeys: String, CodingKey {
case status
// case receipt
case latestReceipt = "latest_receipt"
case latestReceiptInfo = "latest_receipt_info"
// case latestExpiredReceiptInfo = "latest_expired_receipt_info"
// case pendingRenewalInfo = "pending_renewal_info"
// case isRetryable = "is-retryable"
}
}
struct ReceiptInfo: Decodable {
let originalTransactionID: String?
let productID: String?
let expiresDateMS: String?
let originalPurchaseDateMS: String?
let isTrialPeriod: String?
let isInIntroOfferPeriod: String?
let purchaseDateMS: String?
enum CodingKeys: String, CodingKey {
case originalTransactionID = "original_transaction_id"
case productID = "product_id"
case expiresDateMS = "expires_date_ms"
case originalPurchaseDateMS = "original_purchase_date_ms"
case isTrialPeriod = "is_trial_period"
case isInIntroOfferPeriod = "is_in_intro_offer_period"
case purchaseDateMS = "purchase_date_ms"
}
func getExpireDate() -> Date? {
let nf = NumberFormatter()
guard let expDateString = self.expiresDateMS, let expDateValue = nf.number(from: expDateString) else {
return nil
}
/// It's expressed as milliseconds since 1970!!!
let date = Date(timeIntervalSince1970: expDateValue.doubleValue / 1000)
return date
}
希望对您有所帮助! :)