iOS Question In-app Subscriptions and listing owned products

Paul Leischow

Member
Licensed User
Longtime User
In B4A we can initialize Billing Manager

Dim manager As BillingManager3
manager.Initialize("manager", key)

and to get a list of owned products we can call
manager.GetOwnedProducts which will trigger
manager_OwnedProducts (Success As Boolean, purchases As Map)

Are we not able to do something similar in B4I ?
I can't seem to find something similar to GetOwnedProducts.
 

Paul Leischow

Member
Licensed User
Longtime User
Yes, I saw the RestoreTransactions command but it doesn't seem very practical if the user must log in. Apple doesn't make things very easy or seamless when managing in-app purchases like subscriptions compared to Android.

From what I've read it looks like the developer must keep their own database of users and subscription start & end dates and the app must query that on every start... and Apple seems to do nothing. Is this assumption correct?
What are others doing to deal with stuff like monthly or yearly subscriptions?

I'm kinda stumped here ;)
 
Upvote 0

Computersmith64

Well-Known Member
Licensed User
Longtime User
You can use receipt validation to determine whether products have been purchased.
https://developer.apple.com/library...html#//apple_ref/doc/uid/TP40010573-CH105-SW1

I have no idea how this is implemented in B4i though.

What I do for subscriptions is keep a local record of the expiry date which I update with a background receipt validation call to App Store when the app starts. If for any reason the app can't connect to App Store I use my local record. The receipt validation is only really required for when a subscription has gone past its expiry date because if a user cancels a subscription it's still current until the original expiry date. For a user to get a refund on a subscription they have to contact Apple & it's apparently very rare for them to issue one - however if they did, then the receipt validation would show that the subscription had expired (actually I think you wouldn't get a receipt back).

- Colin.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Upvote 0

Computersmith64

Well-Known Member
Licensed User
Longtime User
I don't think that it is related. You cannot get a list of the already purchased products without calling restore transactions.

For subscriptions you can make a URLRequest to https://buy.itunes.apple.com/verifyReceipt & get the expiration date from the response. Here's the code in Swift:

B4X:
func checkForReceipt(_ restoring: Bool) {

        let receiptUrl = Bundle.main.appStoreReceiptURL
        let fileExists = FileManager.default.fileExists(atPath: receiptUrl!.path)

        if fileExists {
            getReceipt(urlString: "https://buy.itunes.apple.com/verifyReceipt") { (completion) in
                if completion.value(forKey: "status") as! Int == 21007 {
                    print("Going to sandbox for receipt validation")
                    self.getReceipt(urlString: "https://sandbox.itunes.apple.com/verifyReceipt") { (completion) in
                        guard let expDate = self.expirationDateFromResponse(completion) else {return}
                        print("SANDBOX: \(expDate)")
                    }
                } else if completion.value(forKey: "status") as! Int == 0 {
                    guard let expDate = self.expirationDateFromResponse(completion) else {return}
                    print("PRODUCTION: \(expDate)")
                }
            }

            if restoring {
                updateSub()
            } else {
                delegate?.setSubView()
            }
        } else {
            print("No Receipt")
            if restoring {
                let alert = UIAlertView(title: "Oops!", message: "You haven't purchased a myPets subscription yet.", delegate: nil, cancelButtonTitle: "OK")
                alert.show()
            }

        }

    }

  
    func getReceipt(urlString: String, completion: @escaping (NSDictionary) -> Void) {
        var jsonResponse: NSDictionary?
        let receiptURL = Bundle.main.appStoreReceiptURL
        let receiptData = try? Data(contentsOf: receiptURL!)

        guard let receiptToString = receiptData?.base64EncodedString(options: []) else {return}
        let dict = ["receipt-data" : receiptToString, "password" : [your shared secret code]] //**
        do {
            let request = try JSONSerialization.data(withJSONObject: dict, options: []) as Data
            var storeRequest = URLRequest(url: URL(string:urlString)!) //NSMutableURLRequest(url: storeURL)
            storeRequest.httpMethod = "POST"
            storeRequest.httpBody = request
            let session = URLSession(configuration: URLSessionConfiguration.default)
            let task = session.dataTask(with: storeRequest, completionHandler: { (data, response, error) in
                if let error = error {
                    print(error.localizedDescription)
                    return
                }
                do {
                    jsonResponse = try (JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary)!
                    completion(jsonResponse!)
                } catch {
                    //handle NSJSONSerialization errors
                    print(error.localizedDescription)
                }
            })
            task.resume()
        } catch {
            print(error.localizedDescription)
            //handle NSJSONSerialization errors
        }

func expirationDateFromResponse(_ jsonResponse: NSDictionary) -> Date? {

        if let receiptInfo: NSArray = jsonResponse["latest_receipt_info"] as? NSArray {
            guard let lastReceipt = receiptInfo.lastObject as? NSDictionary else {return nil}
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
            guard let expirationDate: Date = formatter.date(from: (lastReceipt["expires_date"] as! String)) else {return nil}
            currentSub = lastReceipt["product_id"] as! String
            currentSubExpiry = expirationDate
            saveSub()
            return expirationDate
        } else {
            return nil
        }
    }

Here's the result I get on my test device with an expired subscription:
Going to sandbox for receipt validation
SANDBOX: 2018-12-23 10:37:31 +0000

& here's the result I get after I take out a test subscription:
Going to sandbox for receipt validation
SANDBOX: 2019-04-26 10:19:10 +0000


- Colin.
 
Upvote 0
Top