licc

心有猛虎 细嗅蔷薇

0%

SKStorefront—判断用户App Store所在地区

最近一年的工作重心都在海外订阅项目上。ROI数据还不错,所以AB测试越做越多。
近期要实现一个根据国家和地区来下发不同的SKU的需求。记录一下。

判断用户的国家和地区可以简化为判断用户Apple ID的地区,也就是App Store地区。

上次和Apple交流,问了这个为问题。告知并没有提供相关API,原因是你App所选的销售地区就是你内购提供的地区,用户可以转区,转区后所订阅的项目也能在新地区提供。

但是我记得WWDC 2019中,介绍了一个iOS13新增的API,可以监听App Store地区的变化。

WWDC详见:

《In-App Purchases and Using Server-to-Server Notifications》

新增的API是SKStorefront

SKStorefront

官方文档见:
https://developer.apple.com/documentation/storekit/skstorefront

获取地区代码

if #available(iOS 13.0, *) {
print(SKPaymentQueue.default().storefront?.countryCode ?? "")
} else {
}

注意有可能为nil

countryCode使用的是ISO 3166-1 Alpha-3代码

SKStorefront还可以通过paymentQueueDidChangeStorefront监听App Store地区的变化

func paymentQueueDidChangeStorefront(_ queue: SKPaymentQueue) {
if let storefront = queue.storefront {
// Refresh the displayed products based on the new storefront.
for product in storeProducts {
if shouldShow(product.productIdentifier, in: storefront) {
// Display this product in your store UI.
}
}
}
}

Apple官方文档说SKStorefront随时可能变化,甚至是在购买过程中,因此新增了一个代理方法paymentQueue:shouldContinueTransaction:inStorefront:

此方法的作用是在购买时候发生SKStorefront变化,可以判断要不要继续执行这个Transaction。

SKPaymentQueue.default().delegate = self  // Set your object as the SKPaymentQueue delegate.

func paymentQueue(_ paymentQueue: SKPaymentQueue,
shouldContinue transaction: SKPaymentTransaction,
in newStorefront: SKStorefront) -> Bool {
return shouldShow(transaction.payment.productIdentifier, in: newStorefront)
}

如果此地区不支持购买paymentQueue:shouldContinueTransaction:inStorefront:返回false。在Transaction回调中就会收到一个SKErrorStoreProductNotAvailable的Error信息

func paymentQueue(_ queue: SKPaymentQueue,
updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
if let transactionError = transaction.error as NSError?,
transactionError.domain == SKErrorDomain
&& transactionError.code == SKError.storeProductNotAvailable.rawValue {
// Show an alert.
}
}
}

但是SKStorefront只支持iOS13+,并不满足我们的需求。因此还要想别的办法。

通用做法

我们的需求是根据国家和地区,下发不同比例的SKU,不存在这个SKU只在这个地区销售的情况。因为订阅类型的SKU,用户可以在订阅管理里面切换套餐

我的做法是从SKU价格处理入手,因为我们是海外项目,在全球销售。所以要要呈现用户所在地区的价格和货币。可以通过NumberFormatter进行转换

let formatter = NumberFormatter.init()
formatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
formatter.numberStyle = NumberFormatter.Style.currency
//SKProduct中有priceLocale
formatter.locale = product!.priceLocale
let price :String = formatter.string(from: self.product!.price) ?? "$4.99"

从上述代码可以看到,SKProduct中有个priceLocale属性,赋值给NumberFormatter后可以对价格进行处理。

priceLocale是Local类型(OC中是NSLocal)

Local中有identifierregionCode等属性。regionCode代表的就是地区,可能为nil,identifier是标识符,是一个字符串。

下图是App Store切换到澳大利亚地区,打印的结果

下图是美国

但是regionCode可能为nil,所以要做下判断,当regionCode为nil时候使用identifier,或者跟着业务逻辑调整。

identifier的格式一般是都是固定的,可以根据@符分割,拿到前面的语言_地区的值。

如何快速切换App Store地区可以看我之前写的这篇文章《iOS开发中一些小工具》 使用Switcher这个工具。安装地址:http://switchr.imagility.io/

这个办法必须先获取一个可用的SKProduct,并不能在请求SKProduct之前拿到地区。所以还是有一定的局限性。

我一般是在productsRequest:didReceiveResponse:回调中,拿到一个SKProduct获取到地区后就存起来。有个这个参数也可以在埋点上报中用到,标明用户的App Store地区。