1 Reply
      Latest reply on Jan 14, 2019 2:40 PM by PBK
      vyakushe Level 1 Level 1 (0 points)

        I created an In-App Purchase subscription feature in my app, but the receipt validation is not working properly. I would like to change a boolean based on the status of the purchase (e.g. subscription is active or subscription is expired). If the subscription is active I'd like to change bool to true and enable user button interaction, if expired, then change bool to false and deny button interaction.

         

        View did load code to set the bool:

         

        do {
                    try validateReceipt()
                    // The receipt is valid 
                    print("Receipt is valid")
                    UserDefaults.standard.set(true, forKey: "isSubbed")
                } catch ReceiptValidationError.receiptNotFound {
                    // There is no receipt on the device 
                    UserDefaults.standard.set(false, forKey: "isSubbed")
                } catch ReceiptValidationError.jsonResponseIsNotValid(let description) {
                    // unable to parse the json   
                    print(description)
                } catch ReceiptValidationError.notBought {
                    // the subscription hasn't being purchased 
                    UserDefaults.standard.set(false, forKey: "isSubbed")
                } catch ReceiptValidationError.expired {
                    // the subscription is expired 
                    UserDefaults.standard.set(false, forKey: "isSubbed")
                } catch {
                    print("Unexpected error: \(error).")
                }
        

         

        Here's some code for receipt validation:

         

        enum ReceiptValidationError: Error {
            case receiptNotFound
            case jsonResponseIsNotValid(description: String)
            case notBought
            case expired
        }
        
        func validateReceipt() throws {
            guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) else {
                throw ReceiptValidationError.receiptNotFound
            }
            
            let receiptData = try! Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
            let receiptString = receiptData.base64EncodedString()
            let jsonObjectBody = ["receipt-data" : receiptString, "1111" : String()]
            
            #if DEBUG
            let url = URL(string: "https://sandbox.itunes.apple.com/verifyReceipt")!
            #else
        //    let url = URL(string: "https://buy.itunes.apple.com/verifyReceipt")!
            #endif
            
            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            request.httpBody = try! JSONSerialization.data(withJSONObject: jsonObjectBody, options: .prettyPrinted)
            
            let semaphore = DispatchSemaphore(value: 0)
            
            var validationError : ReceiptValidationError?
            
            let task = URLSession.shared.dataTask(with: request) { data, response, error in
                guard let data = data, let httpResponse = response as? HTTPURLResponse, error == nil, httpResponse.statusCode == 200 else {
                    validationError = ReceiptValidationError.jsonResponseIsNotValid(description: error?.localizedDescription ?? "")
                    semaphore.signal()
                    return
                }
                guard let jsonResponse = (try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [AnyHashable: Any] else {
                    validationError = ReceiptValidationError.jsonResponseIsNotValid(description: "Unable to parse json")
                    semaphore.signal()
                    return
                }
                guard let expirationDate = self.expirationDate(jsonResponse: jsonResponse, forProductId: "com.test.UnlockTools.Subscription1") else {
                    validationError = ReceiptValidationError.notBought
                    semaphore.signal()
                    return
                }
                
                let currentDate = Date()
                if currentDate > expirationDate {
                    validationError = ReceiptValidationError.expired
                }
                
                semaphore.signal()
            }
            task.resume()
            
            semaphore.wait()
            
            if let validationError = validationError {
                throw validationError
            }
        }
            
            func expirationDate(jsonResponse: [AnyHashable: Any], forProductId productId :String) -> Date? {
                guard let receiptInfo = (jsonResponse["latest_receipt_info"] as? [[AnyHashable: Any]]) else {
                    return nil
                }
                
                let filteredReceipts = receiptInfo.filter{ return ($0["product_id"] as? String) == productId }
                
                guard let lastReceipt = filteredReceipts.last else {
                    return nil
                }
                
                let formatter = DateFormatter()
                formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
                
                if let expiresString = lastReceipt["expires_date"] as? String {
                    return formatter.date(from: expiresString)
                }
                
                return nil
            }
        
        }
        
        
        

        Currently the bool does not seem to change. Not sure the try and catch is working properly. I'll provide more code if needed. Any help would be greatly appreciated.