4

I have two auto-renewing subscriptions, monthly and yearly (iOS). I can make a purchase when I use a new fresh sandbox user. Although I have to enter my password three times. Here's the flow:

  1. Tap on a subscription
  2. Enter Password
  3. Promted to enter password again
  4. get "cannot connect to iTunes Store" error
  5. try again and enter password
  6. purchase succeeds.

Moving on, so once it succeeds, I'm now subscribed and my UI is updated via a listener in appDelegate which posts a notification that I subscribe to. But when I try to switch subscriptions, from monthly to yearly, or vice versa, it always gives me the "Cannot connect to iTunes Store" error. No UI updates. Here's the flow:

  1. Tap on the other subscription
  2. Prompted to enter iTunes password
  3. Receive the "confirm purchase" dialog which states I'm modifying my subscription
  4. Tap continue
  5. Receive the "You're all set" success alert.
  6. Dismiss alert
  7. Receive "Cannot connect to iTunes Store" error
  8. My listener wasn't called, UI wasn't updated, etc.

But then if I dismiss the error and tap the subscription again, I get an alert that says I'm already subscribed to that plan, even though the error was thrown and my listener didn't pick up the change.

I'm using firebase. I followed the quickstart and the Firebase specific instructions in the RevenueCat docs. All of my debug logs seem to be fine, no non-200 statuses, no invalid product Ids. Here are some code snippets:

AppDelegate:

Purchases.debugLogsEnabled = true
Purchases.configure(withAPIKey: Constants.revenueCatKey)
Purchases.shared.delegate = self

FirebaseApp.configure()

authHandle = Auth.auth().addStateDidChangeListener() { (auth, user) in

        if let uid = user?.uid {

            Purchases.shared.identify(uid, { (info, error) in
                if let e = error {
                    print("sign in error: \(e.localizedDescription)")
                } else {
                    print("User \(uid) signed in")
                }
            })
          }
            ...
        }
    }

extension AppDelegate: PurchasesDelegate {

    func purchases(_ purchases: Purchases, didReceiveUpdated purchaserInfo: PurchaserInfo) {

        if self.currentUser != nil {

            if purchaserInfo.activeSubscriptions.contains(Constants.audiomeshSubscriptions.monthly) {
                guard let myRef = DataService.instance.REF_PRIVATE else { return }
                myRef.updateData(["plan" : "paidMonthly"]) { err in
                    if let err = err {
                        print("error updating user-private with new subscription: \(err)")
                    } else {

                        NotificationCenter.default.post(name: Notification.Name(rawValue: "purchaserInfoUpdated"), object: nil)
                    }
                }


            }
            else if purchaserInfo.activeSubscriptions.contains(Constants.audiomeshSubscriptions.yearly) {
                //do the same for yearly subscription

            }
            else {
                 //handle non-paying users here
            }
        } 
    }
}

UpgradeController (the purchasing UI):

@objc func purchaseButtonSelected(sender: AudiomeshButton) {
    let buttonTag = sender.tag
    guard let option = options?[buttonTag] else { return }

    let product:SKProduct = option.product

    Purchases.shared.makePurchase(product, { (transaction, purchaserInfo, error) in

        if let error = error {
            print("error making purchase: \(error)")

        } else {
                print("Purchase Successful")
        }
    })
}
mnearents
  • 646
  • 1
  • 6
  • 31

2 Answers2

9

So this one is actually relatively easy to answer but the answer is rather unsatisfying.

Upgrades and crossgrades don't work in Sandbox.

This error is almost always returned in that case. The good news is that it works on production and RevenueCat tracks all the cases correctly.

Jacob Eiting
  • 1,004
  • 6
  • 9
  • Ok, that's totally fine. I guess my only remaining concern is the issue of entering my password twice, receiving an unknown error, then trying again and succeeding after a third password entry. – mnearents Mar 02 '19 at 23:28
  • Thanks for your answer... can you confirm this means that a cross grade would not normally return an error in the makePurchase completion block, when running in production? – Bradley Thomas Apr 09 '19 at 15:36
  • Hey! That's correct @BradThomas, in production everything works as expected. – Jacob Eiting May 06 '19 at 23:50
5

So that's the generic NSError message that gets thrown for all SKErrors. Error code 2 is "payment cancelled". However, that's also the error that gets thrown when you're already subscribed to an item.

Are you sure you're letting the yearly subscription expire before you try to resubscribed? With annual subscriptions they will renew every hour, 6 times, before expiring.

To see the specific SKError, you'll do something like:

if let error = error as? SKError  {
                print("SKError - ")
                switch error.code {  // https://developer.apple.com/reference/storekit/skerror.code
                case .unknown:
                    print("unknown error")
                case .paymentCancelled:
                    print("cancelled error")
                case .clientInvalid:
                    print("client invalid")
                case .paymentInvalid:
                    print("payment invalid")
                case .paymentNotAllowed:
                    print("payment not allowed")
                case .cloudServiceNetworkConnectionFailed:
                    print("cloud service network connection failed")
                case .cloudServicePermissionDenied:
                    print("cloud service permission denied")
                case .storeProductNotAvailable:
                    print("store product not available")
                case .cloudServiceRevoked:
                    print("cloud service revoked")
                }
            }

Once you know the SKError that's being returned I can update my answer if needed with more information on what could be going on.

enc_life
  • 4,973
  • 1
  • 15
  • 27
  • So for the changing of one plan to another, I am getting `.unknown` but I did read in your discussion board that you can't test upgrading/downgrading in the sandbox environment. So I'll just trust that it's working. As for testing with a fresh sandbox user first-time purchase: after entering my password twice (is that normal?) I am getting `.unknown` there as well. However, reselecting the product, entering my password (for the third time) succeeds. – mnearents Mar 02 '19 at 19:20