App doesn't handle redemption of in-app purchase promo code of consumable products
Asked Answered
K

3

20

My app has in-app purchase built in (I followed this tutorial), the purchase functionality works. However, when I redeem the promo code in App Store for one of the in-app purchase products, my app doesn't response to it. Even the App Store says the product has been successfully redeemed, my app doesn't response to it.

Has anyone who has in-app purchase tested if your app can process the promo code? Would you mind share the solution?

I started with this:

override func viewDidLoad() {
    NotificationCenter.default.addObserver(self,
        selector: #selector(applicationDidBecomeActive(notification:)),
        name: NSNotification.Name.UIApplicationDidBecomeActive,
        object: nil
    )
}

func applicationDidBecomeActive(notification: NSNotification){
    let store = IAPHealper()

    //what needs to be put here?
}


extension IAPHelper: SKPaymentTransactionObserver {

    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch (transaction.transactionState) {
            case .purchased:
                completeTransaction(transaction)
                break
            case .failed:
                failedTransaction(transaction)
                break
            case .restored:
                restoreTransaction(transaction)
                break
            case .deferred:
                break
            case .purchasing:
                break
            }
        }
    }

    fileprivate func completeTransaction(_ transaction: SKPaymentTransaction) {
        print("completeTransaction...")
        deliverPurchaseNotificatioForIdentifier(transaction.payment.productIdentifier)
        defaultQueue.finishTransaction(transaction)
        purchaseCompletionHandler?(true, transaction)
    }

    fileprivate func restoreTransaction(_ transaction: SKPaymentTransaction) {
        guard let productIdentifier = transaction.original?.payment.productIdentifier else { return }

        print("restoreTransaction... \(productIdentifier)")
        deliverPurchaseNotificatioForIdentifier(productIdentifier)
        defaultQueue.finishTransaction(transaction)
    }

    fileprivate func failedTransaction(_ transaction: SKPaymentTransaction) {
        print("failedTransaction...")

        if transaction.error!._code != SKError.paymentCancelled.rawValue {
            print("Transaction Error: \(String(describing: transaction.error?.localizedDescription))")
            purchaseCompletionHandler?(false, transaction)
        }

        defaultQueue.finishTransaction(transaction)
    }

    fileprivate func deliverPurchaseNotificatioForIdentifier(_ identifier: String?) {
        guard let identifier = identifier else { return }
        purchasedProductIdentifiers.insert(identifier)
        //NSNotificationCenter.defaultCenter().postNotificationName(IAPHelper.IAPHelperPurchaseNotification, object: identifier)
    }

    public func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]){
        print("Removed from queue.")
        print(transactions)
    }
}

Thanks.

Kaki answered 2/6, 2017 at 20:3 Comment(1)
Please go on throw this video It will help you a lot.Lodged
K
1

@Eatton provided very helpful information on the other thread. I just want to summarize the solution to handling redemption of Promo Code for consumable products.

I. You should use SwiftyStoreKit, and put this code in AppDelegate:

SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
    for purchase in purchases {
        switch purchase.transaction.transactionState {
        case .purchased, .restored: 
            if purchase.needsFinishTransaction {
                SwiftyStoreKit.finishTransaction(purchase.transaction)
            }
            // Unlock content
            self.unlockIAPContent(productID: purchase.productId)
        case .failed, .purchasing, .deferred:
            break // do nothing
        }
    }
}

II. If you want to call the logic in the any ViewController, please consider to use NotificationCenter, put the code under //Unlock content

III. How to test it?

In iOS 11 release, Apple introduced a new feature for promoting your in-app purchase directly in App Store.

First add the handler:

#if DEBUG
SwiftyStoreKit.shouldAddStorePaymentHandler = { payment, product in
    return true
}
#endif

Then compose the following URL on your Mac and AirDrop it over to your iOS device and open it in Safari.

itms-services://?action=purchaseIntent&bundleId=com.example.app&productIdentifier=product_name

Then the completion block of SwiftyStoreKit.completeTransactions() in your AppDelegate will be triggered.

This can also be used for testing Promo Code redemption since the URL request creates a pending transaction and adds it to the queue. Make sure you remove this code for your prod release.

Hope this helps!

Kaki answered 5/11, 2018 at 20:53 Comment(1)
What code should be removed from the production release? Will it still work when removed? What about the IF DEBUG flag, should that be removed and leave in the SwiftyStoreKit call?Conjoin
L
5

Other than what Andrew says (about promo codes only working for production environments) it seems you might have an issue regarding your "App Purchases" Handling mechanisms.

You should have your purchases handling object initialized in the AppDelegate, so that it immediately receives the pending purchases and just created purchases, (or in this case when a code is redeemed)

If you check the examples shown here:

https://developer.apple.com/library/content/technotes/tn2387/_index.html#//apple_ref/doc/uid/DTS40014795-CH1-BEST_PRACTICES-ADD_A_TRANSACTION_QUEUE_OBSERVER_AT_APPLICATION_LAUNCH

You are actually doing what is NOT recommended by apple.

Instead add the StoreKit observer INSIDE your AppDelegate:

class AppDelegate: UIResponder, UIApplicationDelegate {
                           ....
    // Attach an observer to the payment queue.
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Attach an observer to the payment queue.
        SKPaymentQueue.default().add(your_observer)
        return true
    }

    // Called when the application is about to terminate.
    func applicationWillTerminate(_ application: UIApplication) {
       // Remove the observer.
       SKPaymentQueue.default().remove(your_observer)
    }

    // OTHER STUFF...

}

You might actually be missing on the timing your app receives the purchases because of this.

By the way, you already have an "In App Purchases" helper object (IAPHealper). All you need to do is, make your AppDelegate store a variable to it, and instantiate it inside the "didFinishLaunchingWithOptions" method.

Something like:

class AppDelegate: UIResponder, UIApplicationDelegate {

    var store : IAPHealper;
                           ....
    // Attach an observer to the payment queue.
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        store = IAPHelper();

        return true
    }

    // OTHER STUFF...

}

EDIT: (To keep all the info here on stack overflow)

According to an Apple's Employee response: https://forums.developer.apple.com/message/204476#204476

Promo codes can only be tested in the production store.

So, in order to test them:

  1. Submit your app for App Review.
  2. Set the release date of the App sometime in the future so that once App Review approves the app, it's not available to the general user.
  3. Use an app promo code to install the app
  4. Use the in app promo code.

And finally, the developer's post also warns us that, after an App has been approved you have to wait 48 hours before the codes start working.


So if after following the steps described above, your app is not behaving as expected. Then the problem you are facing is your app not being "ready" when apple sends you the "purchase successful" notification. Hence why you should follow the guideline described on the first part of this answer. (About initializing your transaction listener as soon as your app is launched)

Lith answered 19/6, 2017 at 2:17 Comment(5)
Doesn't work. Have you ever tried your solution? Basically the thing is the solution works for in-app purchase but it doesn't work for the promo code redemption.Kaki
The thing is that, in-app promo codes are exactly the same as "in-app purchases". However they follow the logic of your app "receiving" the successful purchase notification on app launch. So if you have already followed the steps provided here: forums.developer.apple.com/message/204476#204476 and it still doesn't work it means the "successful" purchase notification is getting lost because your app is "not ready" by the time its received, hence why i wrote the instructions of how to properly configure in app purchases to receive notifications on App Launch.Lith
@zsong That Apple's employee response says the same thing: "If the in_app promo codes aren't activating after the SKProductsRequest works, I'd verify that the transactionObserver is active at launch time."Lith
The 250 points were mistakenly deposited by stackoverflow and the solution actually doesn't work. Thanks for the effort.Kaki
Hi @Pochi. Actually, reading the FAQ provided in the cited link, I see that "If the developer does not approve the release of the production application to the App Store, then any new in-app purchase identifiers will not be activated". From what I understand, you can't get a list of product ids with SKProductRequest if your app is not released, so you can't show the list of product to purchase. In theory, the 2 days refer to the time which has to pass AFTER developer release. Did you managed to receive a list of products with a non released app (e.g, in "pending developer release" state)?Staging
S
1

Promo codes work in production environment only.

If you want to ensure IAPs work without actual application release then use promo codes when application is in "pending developer release" state (after review). One app promo code to install app and another to test IAP. Sometimes it can take additional time (up to 2 days) to distribute data for all Apple servers.

from official devforum answer

Saccule answered 15/6, 2017 at 14:45 Comment(2)
Actually, reading the FAQ provided in the cited link, I see that "If the developer does not approve the release of the production application to the App Store, then any new in-app purchase identifiers will not be activated". Thus, from what I understand, you can't get a list of product ids with SKProductRequest if your app is in the "pending developer release" state using promo code to install it. In theory, 2 days refer to the time which has to pass AFTER developer release. Did you managed to receive a list of products while your app was in "pending developer release" state?Staging
I can confirm that you CAN'T download the product list while your app is in "pending developer release" state. In order to correctly retrieve the product list from the Apple store you have to release your application passing in the "ready for sale" state.Staging
K
1

@Eatton provided very helpful information on the other thread. I just want to summarize the solution to handling redemption of Promo Code for consumable products.

I. You should use SwiftyStoreKit, and put this code in AppDelegate:

SwiftyStoreKit.completeTransactions(atomically: true) { purchases in
    for purchase in purchases {
        switch purchase.transaction.transactionState {
        case .purchased, .restored: 
            if purchase.needsFinishTransaction {
                SwiftyStoreKit.finishTransaction(purchase.transaction)
            }
            // Unlock content
            self.unlockIAPContent(productID: purchase.productId)
        case .failed, .purchasing, .deferred:
            break // do nothing
        }
    }
}

II. If you want to call the logic in the any ViewController, please consider to use NotificationCenter, put the code under //Unlock content

III. How to test it?

In iOS 11 release, Apple introduced a new feature for promoting your in-app purchase directly in App Store.

First add the handler:

#if DEBUG
SwiftyStoreKit.shouldAddStorePaymentHandler = { payment, product in
    return true
}
#endif

Then compose the following URL on your Mac and AirDrop it over to your iOS device and open it in Safari.

itms-services://?action=purchaseIntent&bundleId=com.example.app&productIdentifier=product_name

Then the completion block of SwiftyStoreKit.completeTransactions() in your AppDelegate will be triggered.

This can also be used for testing Promo Code redemption since the URL request creates a pending transaction and adds it to the queue. Make sure you remove this code for your prod release.

Hope this helps!

Kaki answered 5/11, 2018 at 20:53 Comment(1)
What code should be removed from the production release? Will it still work when removed? What about the IF DEBUG flag, should that be removed and leave in the SwiftyStoreKit call?Conjoin

© 2022 - 2024 — McMap. All rights reserved.