Search code examples
iosappstore-approvalstorekit

Different IAP pricing per country in iOS


I noticed that buying % of IAP for one of my apps in China is very low compared to other countries. I conclude its because of the price being to high. I want to be able to implement different price tiers for my IAPs per country (and then specifically for China in the first place). I know there are these special price tiers (tier A, B, alternate tier 4...) which already offer some cheaper prices for “emerging countries” but they will not do.

All my IAP are non-consumables.

After research here is my idea:

  1. Define for each IAP a normal and cheap one in the itunes portal.
  2. When requesting the info via the StoreKit API in the app I would request both “variants” of each IAP
  3. The returned SKProduct.priceLocal.regionCode could tell me if the user is in China in which case I would select to take the cheap variant of the IAP (logic implemented in the app).

Would this be a good approach? Does apple allow this strategy?


Solution

  • The above approach is working, my app is pushed to the AppStore; let's see if it gets approved by Apple.

    My implementation details, using an example for buying a car with discount for people connecting to the China AppStore :

    class IAPManager : NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
    
        //holds the literal string identifiers as also entered in the itunes connect portal for the in app purchases
        //the format of this string can be anything you want. I use a revers domain notation to make it "unique".
        //notice that we are requesting the price for a car, there are 2 prices the "normal" price and the "discount" price
        //the idea is that we will offer the "discount" price for people connected to the Chinese AppStore
        struct ProductIdentifiers {
            static let carNormalPricing   = "net.fantastic.car._normalpricing"
            static let carDiscountPricing = "net.fantastic.car._discountpricing"
        }
    
    
        //stores the product request send to the AppStore for retrieving the pricing info
        //as stated in the Apple documentation this should be kept in memory until the request completes
        private var productRequest: SKProductsRequest?
    
        //once the pricing is retrieved from the AppStore its stored here
        private var carPrices: (normal: SKProduct?, discount: SKProduct?)
    
        //this is where the "magic" happens, pleople connecting to the Chinese app store get the discount pricing
        //all others get then normal pricing. By changing the "zh" you can select different stores...
        //I have tested this and this does work (for China anayway).
        var carPriceToUse: SKProduct? {
            //if we don't have normal pricing no pricing available at all!
            guard let normal = carPrices.normal else {
                return nil
            }
            //if we don't have any discount pricing always use the normal of course
            guard let discount = carPrices.discount else {
                return normal
            }
            //we got both prices so select between them
            //for chinese languages we use discount pricing
            if discount.priceLocale.languageCode == "zh" {
                return discount
            }
            //if not go for normal pricing
            return normal
        }
    
        func askAppStoreForCarPrices() {
            //make list of the product ids that we will be retrieving from the AppStore
            var productIds = Set<String>()
            //we want to get the norma and discount pricing for our car
            productIds.insert(ProductIdentifiers.carNormalPricing)
            productIds.insert(ProductIdentifiers.carDiscountPricing)
            //make and sendout the request (on main queue)
            productRequest = SKProductsRequest(productIdentifiers: productIds)
            productRequest?.delegate = self
            DispatchQueue.main.async(){
                [weak self] in
                self?.productRequest?.start()
            }
        }
    
        func buyCar() {
            //only if I can buy...
            guard let storeProduct = carPriceToUse else {
                fatalError("Asked to buy but no prices loaded yet")
            }
            //ask to buy
            DispatchQueue.main.async(){
                let payment = SKPayment(product: storeProduct)
                SKPaymentQueue.default().add(payment)
            }
        }
    
        //MARK: - SKProductsRequestDelegate, SKPaymentTransactionObserver
    
    
        func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
            //parseout any priceinfo
            for product in response.products {
                if product.productIdentifier == ProductIdentifiers.carNormalPricing {
                    carPrices.normal = product
                }
                if product.productIdentifier == ProductIdentifiers.carDiscountPricing {
                    carPrices.discount = product
                }
            }
        }
    
        func request(_ request: SKRequest, didFailWithError error: Error) {
            //...handle error when retrieving prices here...
        }
    
        func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
            //..handle actual buying process here
        }
    
    }