0

I'm getting sporadic reports of users who purchase my auto-renewing subscription and there is no receipt data on their device. After the purchase is made, I parse the receipt and save the expire date into UserDefaults. Then whenever you open the app I check the expire date against today and if its later, you can access my content.

I've created a gist with my relevant code that parses the receipt and saves the expireDate that is kicked off in my appDelegate's handle purchase:

extension AppDelegate: SKPaymentTransactionObserver {

    func paymentQueue(_ queue: SKPaymentQueue,
                      updatedTransactions transactions: [SKPaymentTransaction]) {

        for transaction in transactions {
            switch transaction.transactionState {
            case .purchasing:
                handlePurchasingState(for: transaction, in: queue)
            case .purchased:
                handlePurchasedState(for: transaction, in: queue)
            case .restored:
                handleRestoredState(for: transaction, in: queue)
            case .failed:
                handleFailedState(for: transaction, in: queue)
            case .deferred:
                handleDeferredState(for: transaction, in: queue)
            }
        }
    }

    func handlePurchasingState(for transaction: SKPaymentTransaction, in queue: SKPaymentQueue) {
        print("User is attempting to purchase product id: \(transaction.payment.productIdentifier)")
    }

    func handlePurchasedState(for transaction: SKPaymentTransaction, in queue: SKPaymentQueue) {
        print("User purchased product id: \(transaction.payment.productIdentifier)")

        queue.finishTransaction(transaction)
        SubscriptionService.shared.uploadReceipt { (success) in
            DispatchQueue.main.async {
                //self.updateWatchContext()
                NotificationCenter.default.post(name: SubscriptionService.purchaseSuccessfulNotification, object: nil)
            }
        }
    }

    func handleRestoredState(for transaction: SKPaymentTransaction, in queue: SKPaymentQueue) {
        print("Purchase restored for product id: \(transaction.payment.productIdentifier)")
        queue.finishTransaction(transaction)
        SubscriptionService.shared.uploadReceipt { (success) in
            DispatchQueue.main.async {
                NotificationCenter.default.post(name: SubscriptionService.restoreSuccessfulNotification, object: nil)
            }
        }
    }

    func handleFailedState(for transaction: SKPaymentTransaction, in queue: SKPaymentQueue) {
        queue.finishTransaction(transaction)  //The RW tutorial did NOT finish the transaction, but iOS Slack said I should 
        print("Purchase failed for product id: \(transaction.payment.productIdentifier) on \(String(describing: transaction.transactionDate)) and this transaction is currently listed as \(transaction.transactionState) because of this error \(String(describing: transaction.error))")
    }

    func handleDeferredState(for transaction: SKPaymentTransaction, in queue: SKPaymentQueue) {
        print("Purchase deferred for product id: \(transaction.payment.productIdentifier)")
    }

}

A few users have told me that they cannot unlock the content even though they subscribed (these users also said they had purchased the annual subscription, I also have a monthly option)...I had one user install a beta version with a Firebase integration so that I could push the receipt and expire date to the console to see what it says and it appears that there is no expire date or receipt on his device. Any thoughts on what the issue is and why this seems to only be the case in the UK? My active user base is roughly 600 and I haven't heard of this issue outside of the UK.

GarySabo
  • 5,806
  • 5
  • 49
  • 124
  • There's a subtle bug here: `defaults.set(activeSubscriptions.last?.expiresDate, forKey: expireDateKey)`. Based on your gist I think this should be: `sortedByMostRecentPurchase.first?.expiresDate` – Pranav Kasetti Oct 20 '18 at 20:12
  • Also where is the observer for `purchaseSuccessfulNotification`? – Pranav Kasetti Oct 20 '18 at 20:22
  • 1
    Also you should not call `finishTransaction` until you have successfully processed and persisted the purchase. Does your app have a "restore" button? It should. It provides a simple way for users to re-try IAP activation. – Paulw11 Oct 20 '18 at 20:46
  • @Paulw11 I do have a restore button, see my edited question... `handleRestoredState` gets called when the restore button is tapped. However in the situations described in my original question the restore button does not unlock the content. – GarySabo Oct 21 '18 at 04:01
  • @PranavKasetti can you tell me what you mean by observer for `purchaseSuccessfulNotification`? – GarySabo Oct 21 '18 at 04:01
  • You have the same bug in your restore process; you are finishing the transaction before you have verified the receipt and persisted the purchase. In your closure you should have `if success { queue.finishTransaction(transaction) } else { show some error }` – Paulw11 Oct 21 '18 at 04:16
  • @Paulw11 thank you, I'm not saying they are correct but I modeled my flow from this RW tutorial https://www.raywenderlich.com/659-in-app-purchases-auto-renewable-subscriptions-tutorial and he specifically finishes the transaction prior to verifying receipt? – GarySabo Oct 21 '18 at 11:08
  • That tutorial is incorrect. It is very important that you do not finish the transaction until you have successfully done everything you need to to deliver the purchase to the user. If your app is terminated before you call `finishTransaction` then the transaction will still be in the queue and will be delivered to your payment queue transaction observer again when your app is relaunched. See under [Overview](https://developer.apple.com/documentation/storekit/skpaymenttransactionobserver) – Paulw11 Oct 21 '18 at 12:26
  • @Paulw11 will do, do you think this may be related to the issues in my original question? – GarySabo Oct 21 '18 at 13:47
  • @PranavKasetti my observer for `purchaseSuccessfulNotification` is in my subscribeViewController where my products are loaded and you can tap to purchase. Upon notification of success, the UI changes to show that you are now subscribed. – GarySabo Oct 21 '18 at 14:05

0 Answers0