【问题标题】:StoreKit receipt validation status 21002StoreKit 收据验证状态 21002
【发布时间】:2020-12-17 18:34:37
【问题描述】:

我已经尝试了多种方法来使用带有 storeKit 的模拟器和 testFlight 来验证 storeKit 收据,但似乎除了状态 21002 之外什么都没有。我上次使用的代码起源于 (Implementing Receipt Validation in Swift 3)。似乎很多人都遇到过这个问题,有人说无法使用沙箱进行验证。苹果文档表明可以做到吗?问题似乎出在下面代码底部的 func receiveValidation10() 上。其他一切似乎都很好。任何帮助表示赞赏。谢谢!

import StoreKit
import UIKit
  
let SUBSCRIPTION_SECRET = "xxx"
#if DEBUG
    let certificate = "StoreKitTestCertificate"
#else
    let certificate = "AppleIncRootCertificate"
#endif

class purchaseViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
private var models = [SKProduct]()
var pChoice = "Trial"
var pDate = today
var eDate = today
var myProducts: SKProduct?

@IBAction func back(_ sender: Any) { dismiss(animated: true, completion: nil)
}

@IBAction func home(_ sender: Any) { nav(to: "home")
}

@IBAction func basic(_ sender: Any) {
    pChoice = "Basic"
    eDate=dateAdd(date1: today, inc: 1, metric: "yr")
    if SKPaymentQueue.canMakePayments() {  // needed? https://www.youtube.com/watch?v=qyKmpr9EjwU
        let payment = SKPayment(product: models[whichProduct(title: "com.markv.REX3.basic")])
        SKPaymentQueue.default().add(self) // needed? https://www.youtube.com/watch?v=qyKmpr9EjwU
        SKPaymentQueue.default().add(payment)
    }
}

@IBAction func premium(_ sender: Any) {
    pChoice = "Premium"
    eDate=dateAdd(date1: today, inc: 1, metric: "yr")
    if SKPaymentQueue.canMakePayments() {
        let payment = SKPayment(product: models[whichProduct(title: "com.markv.REX3.premium")])
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().add(payment)
    }
}

@IBAction func unlimited(_ sender: Any) {
    pChoice = "Unlimited"
    eDate="None"
    if SKPaymentQueue.canMakePayments() {
        let payment = SKPayment(product: models[whichProduct(title: "com.markv.REX3.unlimited")])
        SKPaymentQueue.default().add(self)
        SKPaymentQueue.default().add(payment)
    }
}

@IBAction func cancel(_ sender: Any) {
    UIApplication.shared.open(URL(string: "Https://apps.apple.com/account/subscriptions")!)
}

func fetchProduct(upgrade: String) {
    let request = SKProductsRequest(productIdentifiers: [upgrade])
    request.delegate = self
    request.start()
}
func whichProduct(title: String) -> Int {
        for i in 0 ..< models.count {
        let product = models[i]
        if title == product.productIdentifier {
            return i
        }
    }
    print("No product match")
    return models.count
}


enum Product: String, CaseIterable {
    case unlimited = "com.markv.ddd.unlimited"
    case basic = "com.markv.ddd.basic"
    case premium = "com.markv.ddd.premium"
    
}

private func fetchProducts() {
    let request = SKProductsRequest(productIdentifiers: Set(Product.allCases.compactMap({ $0.rawValue })))
    request.delegate = self
    request.start()
}

func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    DispatchQueue.main.async {
        print("Count:\(response.products.count)")
        self.models = response.products
    }
}

func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    
    for transaction in transactions {
        switch transaction.transactionState {
        case .purchasing:
            print("purchasing")
            // no op
            break
        case .purchased, .restored:
            
            SKPaymentQueue.default().finishTransaction(transaction)
            SKPaymentQueue.default().remove(self)  // needed? https://www.youtube.com/watch?v=qyKmpr9EjwU
            print("Purchase or Restore, version=",pChoice)
            print("Method1:")
            receiptValidation10()
            break
        case .failed, .deferred:
            SKPaymentQueue.default().finishTransaction(transaction)
            SKPaymentQueue.default().remove(self)
            print("no payment")
            //dismiss(animated: true)
            break
        default:
            SKPaymentQueue.default().finishTransaction(transaction)
            SKPaymentQueue.default().remove(self)
            print("no payment")
            //dismiss(animated: true)
            break
        }
    }
}
 func receiptValidation10() {
        // method by Yasin Aktimur, updated by Not Batman, says working for swift 4, 11/17/2017, updated 1/17/18 https://stackoverflow.com/questions/39711350/implementing-receipt-validation-in-swift-3
    //
    #if DEBUG
            let urlString = "https://sandbox.itunes.apple.com/verifyReceipt"
        #else
            let urlString = "https://buy.itunes.apple.com/verifyReceipt"
        #endif
    
    let receiptPath = Bundle.main.appStoreReceiptURL?.path
    if FileManager.default.fileExists(atPath: receiptPath!){
        var receiptData:NSData?
        do{
            receiptData = try NSData(contentsOf: Bundle.main.appStoreReceiptURL!, options: NSData.ReadingOptions.alwaysMapped)
        }
        catch{
            print("ERROR: " + error.localizedDescription)
        }
        let base64encodedReceipt = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithCarriageReturn)
        let requestDictionary = ["receipt-data":base64encodedReceipt!,"password":SUBSCRIPTION_SECRET]
        guard JSONSerialization.isValidJSONObject(requestDictionary) else {  print("requestDictionary is not valid JSON");  return }
        do {
            let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
            let validationURLString = urlString  // this works but as noted above it's best to use your own trusted server
            guard let validationURL = URL(string: validationURLString) else { print("the validation url could not be created, unlikely error"); return }

            let session = URLSession(configuration: URLSessionConfiguration.default)
            var request = URLRequest(url: validationURL)
            request.httpMethod = "POST"
            request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
            let queue = DispatchQueue(label: "itunesConnect")
            queue.async {
                let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
                    if let data = data , error == nil {
                        do {
                            let appReceiptJSON = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary
                            print("success. here is the json representation of the app receipt: \(String(describing: appReceiptJSON))")
                            //self.getExpireInfo(appReceiptJSON!)
                            
                        } catch let error as NSError {
                            print("json serialization failed with error: \(error)")
                        }
                    } else {
                        print("the upload task returned an error: \(error ?? "couldn't upload" as! Error)")
                    }
                }
                task.resume()
            }

        } catch let error as NSError {
            print("json serialization failed with error: \(error)")
        }
    }
}

【问题讨论】:

  • 您为什么希望收据存在于您的 App Bundle 中?

标签: swift xcode storekit


【解决方案1】:

我无法找到上面的 func receiveValidation10() 方法的问题,但我找到了一个非常相似的方法,它在下面提供。一路上我发现收据验证不能在 xcode 中完成,而必须通过 testflight 在沙箱中完成。下面的代码适用于沙箱,但不适用于 xcode 模拟。那里有很多时间。 下面的方法剩下的一个问题是它似乎从收据中获取了一个随机的过期日期,而不是最近的一个。如果有人在这里有建议那就太好了。

// Debabrata Roy 于 2019 年 12 月 24 日发布的以下来源 //https://mobikul.com/in-app-subscription-receipt-validation-in-ios-using-swift/?unapproved=93334&moderation-hash=e45d82e3ff1795281893824bb6f8c908#comment-93334

func receiptValidation() {  // This method works!
let verifyReceiptURL = "https://sandbox.itunes.apple.com/verifyReceipt"
let receiptFileURL = Bundle.main.appStoreReceiptURL
let receiptData = try? Data(contentsOf: receiptFileURL!)
let recieptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
let jsonDict: [String: AnyObject] = ["receipt-data" : recieptString! as AnyObject, "password" : SUBSCRIPTION_SECRET as AnyObject]
    
    do {
        let requestData = try JSONSerialization.data(withJSONObject: jsonDict, options: JSONSerialization.WritingOptions.prettyPrinted)
        let storeURL = URL(string: verifyReceiptURL)!
        var storeRequest = URLRequest(url: storeURL)
        storeRequest.httpMethod = "POST"
        storeRequest.httpBody = requestData
        let session = URLSession(configuration: URLSessionConfiguration.default)
        let task = session.dataTask(with: storeRequest, completionHandler: { (data, response, error) in  //removed [weak self]
            
            do {
                if let jsonResponse = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary{
                    actLog(newPrint:"Response :"+String(describing: jsonResponse))
                if let date = getExpirationDateFromResponse(jsonResponse) {
                    actLog(newPrint:"EXPIRATON DATE="+String(describing:date))
                }
                
                if let lProd = getProductFromResponse(jsonResponse) {
                    actLog(newPrint:"LATEST PRODUCT ID="+String(describing:lProd))
                }

                    
                }
            } catch let parseError {
                actLog(newPrint:String(describing:parseError))
            }
        })
        task.resume()
    } catch let parseError {
        actLog(newPrint:String(describing:parseError))
    }
}

func getExpirationDateFromResponse(_ jsonResponse: NSDictionary) -> Date? {
actLog(newPrint:"This date came into getExire func:\n="+String(describing: jsonResponse))
if let receiptInfo: NSArray = jsonResponse["latest_receipt_info"] as? NSArray {
    actLog(newPrint:"last receipt exists")
    let lastReceipt = receiptInfo.lastObject as! NSDictionary
    
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
        // Unfortunately this provides an expire date but not the most recent one :((
        if let expiresDate = lastReceipt["expires_date"] as? String {
            actLog(newPrint:"expire date available")
            actLog(newPrint:String(describing: expiresDate))
            return formatter.date(from: expiresDate)
        }
    actLog(newPrint:"expire data not available")
    return nil
    }
    else {
        actLog(newPrint:"could not get latest_receipt_info")
        return nil
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-16
    • 2015-12-26
    • 1970-01-01
    • 1970-01-01
    • 2021-01-10
    • 2017-01-11
    • 2019-12-18
    • 1970-01-01
    相关资源
    最近更新 更多