How to get Local Currency for SKProduct | Display IAP Price in Swift
Asked Answered
M

7

31

I am trying to display the price of an in app purchase using the local currency, so the correct dollar is displayed for both US & CA as well as Euro, GBP etc.

I know each SKProduct has a price which appears during the transaction as an alert view, this appears when confirming the purchase.

However I want to display the price before confirmation.

I was thinking to do something like this:

//Products Array
var productsArray: Array<SKProduct!> = []

//Request Products
    func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
        if response.products.count != 0 {
            for product in response.products {
                print("\(product.localizedTitle)")
                productsArray.append(product)
            }
        }
        else {
            print("There are no products.")
        }

        if response.invalidProductIdentifiers.count != 0 {
            print("\(response.invalidProductIdentifiers.description)")
        }            
     }


let item = SKProduct
  for i in productsArray {
    if i.localizedTitle == "com.Company.App.item1" 
      item = i
    }
  }

But this doesn't work as i Doesn't seem to have a price property.

Does anybody know how I can set a label text to the price of an iAP using the correct local currency?

For example £1.49 GBP is $1.99 US dollars using Apples Pricing Matrix and outputting the value should match the values of the product price when confirming the transaction.

Milla answered 22/4, 2016 at 12:56 Comment(0)
M
43

Swift 5 and 2021 version:

Create an extension to SKProduct so you can access product.localizedPrice conveniently:

extension SKProduct {

    private static let formatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        return formatter
    }()

    var isFree: Bool {
        price == 0.00
    }

    var localizedPrice: String? {
        guard !isFree else {
            return nil
        }
        
        let formatter = SKProduct.formatter
        formatter.locale = priceLocale

        return formatter.string(from: price)
    }

}

Original Anser:

Swift 4.2 version of Olivier's answer

func priceStringForProduct(item: SKProduct) -> String? {
    let price = item.price
    if price == NSDecimalNumber(decimal: 0.00) {
        return "GET" //or whatever you like really... maybe 'Free'
    } else {
        let numberFormatter = NumberFormatter()
        let locale = item.priceLocale
        numberFormatter.numberStyle = .currency
        numberFormatter.locale = locale
        return numberFormatter.string(from: price)
    }
}
Misti answered 27/8, 2018 at 11:55 Comment(1)
Beware of "localizedPrice" returning unlocalized English strings like "Get".Aeolus
L
22

you should use NSNumberFormatter with the product values for price and priceLocale to output a string that is formatted correctly regardless of the user's location. product.price returns the price in the local currency as an NSDecimalNumber, and product.productLocale returns the NSLocale for the price value.

eg

var item = SKProduct()
for i in productsArray {
    if i.productIdentifier == "com.Company.App.item1" {
      item = i
      if let formattedPrice = priceStringForProduct(item) {
        //update ui with price
      }
    } 
 }

where priceStringForProduct is function defined elsewhere:-

func priceStringForProduct(item: SKProduct) -> String? {
    let numberFormatter = NSNumberFormatter()
    let price = item.price
    let locale = item.priceLocale
    numberFormatter.numberStyle = .CurrencyStyle
    numberFormatter.locale = locale
    return numberFormatter.stringFromNumber(price)
}

You might also want to handle the special case where the price is 0.0 (free tier). In this case amend the priceStringForProduct function to:

func priceStringForProduct(item: SKProduct) -> String? {
    let price = item.price
    if price == NSDecimalNumber(float: 0.0) {
        return "GET" //or whatever you like really... maybe 'Free'
    } else {
        let numberFormatter = NSNumberFormatter()
        let locale = item.priceLocale
        numberFormatter.numberStyle = .CurrencyStyle
        numberFormatter.locale = locale
        return numberFormatter.stringFromNumber(price)
    }
}

Edit: Couple other things, when you specify your productArray a more 'Swifty' way of doing it is:

var productsArray = [SKProduct]()

and then in your didRecieveResponse, instead of looping through the products you can just set productsArray as response.products

var productsArray = [SKProduct]()
    if response.products.count != 0 {

        print("\(response.products.map {p -> String in return p.localizedTitle})")
        productsArray = response.products

    }

Edit: To test for a number of different locales I usually make an array of NSLocales and then loop through printing the result. There is a repo with all the localeIdentifiers's here

so:

let testPrice = NSDecimalNumber(float: 1.99)
let localeArray = [NSLocale(localeIdentifier: "uz_Latn"),
    NSLocale(localeIdentifier: "en_BZ"),
    NSLocale(localeIdentifier: "nyn_UG"),
    NSLocale(localeIdentifier: "ebu_KE"),
    NSLocale(localeIdentifier: "en_JM"),
    NSLocale(localeIdentifier: "en_US")]
    /*I got these at random from the link above, pick the countries
    you expect to operate in*/

    for locale in localeArray {
        let numberFormatter = NSNumberFormatter()
        numberFormatter.numberStyle = .CurrencyStyle
        numberFormatter.locale = locale
        print(numberFormatter.stringFromNumber(testPrice))
    }
Limited answered 22/4, 2016 at 13:20 Comment(7)
Ah, yeah you are specifying that you want the SKProduct at index 1 of productsArray, I didn't notice that. basically you've taken the product out of the array and then tried to loop through the product rather than the array. I'll change the code in my answer, just a sec.Limited
I've changed the loop in my answer, it should work now (hopefully! haha)Limited
I actually did change it myself to for i in productsArray but I think Xcode was being slow. BTW I needed to use i.productIdentifier not i.localizedTitle as the title is just the name of the iAP. When I changed that it seemed to work. However how can I test this for different locations ?Milla
I usually make an array of NSLocale's and loop through with a test price. I'll add that to my answer, thank you for marking it correct btw!Limited
Thanks,this is actually really useful however it just changes the currency type but keeps the value 1.99. I changed testPrice with i.price and as suspected the item value is the same but the currency changes. This is most likely due to when the product is fetched it already gets the local price value as a number. So it seems there's no way of what the value is going to be in other countriesMilla
Ah yeah you can't change the local price sadly, only see what the format looks like. If I find a way to simulate retrieving a product from a different locale I'll let you know!Limited
This answer starts well I believe, but goes downhill in the last code snipped. It may induce in error that you can actually do that. Please see the answer from gnasher729Mesmerism
T
13

Simply you just need to do the following

product.priceLocale.currencyCode
Tyburn answered 16/2, 2020 at 15:46 Comment(1)
good and short answerLexielexigraphy
F
10

The StoreKit Programming Guide has this code snippet for showing the price using the App Store currency:

NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setLocale:product.priceLocale];
NSString *formattedPrice = [numberFormatter stringFromNumber:product.price];

https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/ShowUI.html Listing 2-3

Fendley answered 17/5, 2018 at 15:42 Comment(0)
E
6

You MUST NOT display the price in the local currency. You must display it in the currency provided by the store. If say a French user purchases from the French app store then the price comes up in Euro, and that's what the user pays. If that user goes to New Zealand and changes his or her locale to New Zealand but stays with the French app store, they are still billed in Euros. So that's what you should display.

You say "£1.49 is $1.99 according to Apple's pricing matrix". But £1.49 IS NOT $1.99. If I, as a British user, go to the USA and buy from the UK app store, I pay £1.49 even if I'm in the USA. And "£1.49" is what the store will tell you.

Embroidery answered 19/10, 2016 at 7:45 Comment(0)
A
5

Apple provides regularPrice sample code here:

https://developer.apple.com/documentation/storekit/in-app_purchase/offering_completing_and_restoring_in-app_purchases

extension SKProduct {

    /// - returns: The cost of the product formatted in the local currency.
    var regularPrice: String? {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.locale = self.priceLocale
        return formatter.string(from: self.price)
    }

}
Aeolus answered 6/11, 2019 at 2:0 Comment(0)
B
2

Swift 3 version of Olivier's priceStringForProduct:item

func priceStringForProduct(item: SKProduct) -> String? {
        let price = item.price
        if price == 0 {
            return "Free!" //or whatever you like
        } else {
            let numberFormatter = NumberFormatter()
            let locale = item.priceLocale
            numberFormatter.numberStyle = .currency
            numberFormatter.locale = locale
            return numberFormatter.string(from: price)
        }
    }
Bulldoze answered 15/6, 2017 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.