2

We're implementing Apple's new "Promoted In-App Purchases" system, allowing users to click a "Buy" button inside Apple's App Store that triggers a purchase of an IAP.

The system calls for the app to implement the SKPaymentTransactionObserver's paymentQueue:shouldAddStorePayment:forProduct: delegate method, which returns a boolean. The documentation says,

Return true to continue the transaction in your app.

Return false to defer or cancel the transaction.

If we simply return false, the user sees our app come to the foreground and then nothing else happens. By default, the OS doesn't pop up a message saying "Purchase canceled" or anything like that; it leaves that decision up to the app developer, I suppose.

The App Store Promoted IAP purchase request can arrive at any time, including when the user is in the middle of a flow that shouldn't be interrupted. It's a perfect case to return false from this method. When I do that, I'd like to show the user an alert message explaining the problem with UIAlertController.

The problem is, I have no context view controller to use inside my paymentQueue:shouldAddStorePayment:forProduct:. Normally, when I want to show an alert from inside a view controller, I'd call [self presentViewController:alert animated:YES completion:nil];, but self in this delegate method is a SKPaymentTransactionObserver, not a view controller, so that won't work.

I'm not even sure that there is a guaranteed active view controller when this delegate method fires. For all I know, the delegate method could fire prior to the applicationDidBecomeActive event.

What's the best way to show an alert (or any other snippet of UI) when returning false from paymentQueue:shouldAddStorePayment:forProduct:?

Dan Fabulich
  • 37,506
  • 41
  • 139
  • 175

2 Answers2

1

You can get the application window's root view controller and use that to present the alert.

Objective-C

id *rootController = [[[[UIApplication sharedApplication]delegate] window] rootViewController];
[rootViewController presentViewController:alertController animated:YES completion:nil];

Swift

let appDelegate  = UIApplication.sharedApplication().delegate
let viewController = appDelegate.window!.rootViewController
viewController.presentViewController(alertController, animated: true, completion: nil)
Dan Fabulich
  • 37,506
  • 41
  • 139
  • 175
1

Have a shared instance of a class that conforms to SKPaymentTransactionObserver. Store the promoted payment in an optional variable of this shared instance.

class StoreHelper: NSObject, SKPaymentTransactionObserver {

    static let shared = StoreHelper()

    private var promotedPayment: SKPayment?

    var hasPromotedPayment: Bool {
        return promotedPayment != .none
    }

    override init() {
        super.init()

        SKPaymentQueue.default().add(self)
    }

    func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
        promotedPayment = payment

        return false
    }

    func beginPurchaseForPromotedPayment() {
        ...
    }
}

Place the presentation code in the viewDidAppear of the first view controller that appears.

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if StoreHelper.shared.hasPromotedPayment {
        // your presentation code

        StoreHelper.shared.beginPurchaseForPromotedPayment()

        // after completion, remove promoted product from store helper
    }
}

Happy Coding

Eric Armstrong
  • 646
  • 6
  • 17