获取用户下载的原始购买版本

时间:2019-02-04 12:35:16

标签: swift version receipt-validation

所以我将我的应用程序从付费更改为免费,我想让付费客户拥有高级功能。一种方法是只检查他们的原始购买版本的应用程序,看看它是否是付费版本,然后给他们高级功能,但是我只能找到用户当前应用程序的版本,而不是原始购买的版本。 。

我一直在阅读,也许这可能与收据验证有关,但是如果有一种方法可以使用户获得该应用的原始购买版本,请有人帮忙。

这是我用来获取其当前版本的代码,而不是原始购买版本。

UPDATE daily_tasks
SET new_date = TRY_CONVERT(datetime, date);

谢谢

1 个答案:

答案 0 :(得分:0)

正如我在评论中所写的那样,我不确定这是否是正确的过程,但是对于我制作的应用程序,我使用以下代码检查收据:

如有任何疑问,请参阅我也遵循的the docs

重要的是,苹果不鼓励直接通过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

  1. 获取收据。
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
        }
    }
  1. 然后您可以使用
  2. 以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]

  1. 将您的请求收据发送到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()
    }

这是我构建的“可破坏物”结构。 您将在其中找到检查用户购买商品所需的所有信息!

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

    }

希望有帮助! :)