iOS
StoreKit
アプリ内課金
Swift

アプリ内課金アイテムの情報(SKProduct)を取得する

More than 1 year has passed since last update.

価格情報はiTunesConnectから取得するのが推奨された方法です。

なので、課金アイテム情報(SKProduct)を取得するProductManagerなるクラスを作りました。

(最近、AppStore価格変更されましたし、ちょっとは役に立つかなと・・)

※実際の課金処理はこちらの記事で紹介してます。


ViewController.swift (使用例)

class ViewController: UIViewController {

super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.

//プロダクト情報取得
ProductManager.request(productIdentifiers: productIdentifiers,
completion: {[weak self] (products: [SKProduct], error: Error?) -> Void in
guard error == nil else { return }

for product in products {
//価格を抽出
let priceString = product.localizedPrice ?? "--"
/*
TODO: UI更新

*/
}

})
}


※Swift2.0でwarning出たので、、修正しました。。


ProductManager.swift

import Foundation

import StoreKit

/// 価格情報取得エラー
public enum ProductManagerError: Error {
case emptyProductIdentifiers
case noValidProducts
case notMatchProductIdentifier
case skError(messaga: String)
case unkown

public var localizedDescription: String {
switch self {
case .emptyProductIdentifiers:
return "プロダクトIDが指定されていません。"
case .noValidProducts:
return "有効なプロダクトを取得できませんでした。"
case .notMatchProductIdentifier:
return "指定したプロダクトIDと取得したプロダクトIDが一致していません。"
case .skError(let message):
return message
default:
return "不明なエラー"
}
}
}

/// 価格情報を取得するためのクラス
final public class ProductManager: NSObject {
///保持用
static private var managers: Set<ProductManager> = Set()

///完了通知
public typealias Completion = ([SKProduct], Error?) -> Void

///完了通知
public typealias CompletionForSingle = (SKProduct?, Error?) -> Void

///完了通知用
private var completion: Completion

///価格問い合わせ用オブジェクト(保持用)
private var productRequest: SKProductsRequest?

///初期化
private init(completion: @escaping Completion) {
self.completion = completion
}

/// 課金アイテム情報を取得(複数)
///
/// - Parameters:
/// - productIdentifiers: プロダクトID配列
/// - completion: 課金アイテム情報取得完了時の処理
class func request(productIdentifiers: [String], completion: @escaping Completion) {
guard !productIdentifiers.isEmpty else {
completion([], ProductManagerError.emptyProductIdentifiers)
return
}

let productManager = ProductManager(completion: completion)
let productRequest = SKProductsRequest(productIdentifiers: Set(productIdentifiers))
productRequest.delegate = productManager
productRequest.start()
productManager.productRequest = productRequest
managers.insert(productManager)
}

/// 課金アイテム情報を取得(1つ)
///
/// - Parameters:
/// - productIdentifier: プロダクトID
/// - completion: 課金アイテム情報取得完了時の処理
class func request(productIdentifier: String, completion: @escaping CompletionForSingle) {
ProductManager.request(productIdentifiers: [productIdentifier]) { (products, error) in
guard error == nil else {
completion(nil, error)
return
}

guard let product = products.first else {
completion(nil, ProductManagerError.noValidProducts)
return
}

guard product.productIdentifier == productIdentifier else {
completion(nil, ProductManagerError.notMatchProductIdentifier)
return
}

completion(product, nil)
}
}
}

// MARK: - SKProducts Request Delegate
extension ProductManager: SKProductsRequestDelegate{
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let error = !response.products.isEmpty ? nil : ProductManagerError.noValidProducts
completion(response.products, error)
}

public func request(_ request: SKRequest, didFailWithError error: Error) {
completion([], ProductManagerError.skError(messaga: error.localizedDescription))
ProductManager.managers.remove(self)
}

public func requestDidFinish(_ request: SKRequest) {
ProductManager.managers.remove(self)
}
}

// MARK: - Utility
public extension SKProduct {
/// 価格
var localizedPrice: String? {
let numberFormatter = NumberFormatter()
numberFormatter.formatterBehavior = .behavior10_4
numberFormatter.numberStyle = .currency
numberFormatter.locale = priceLocale
return numberFormatter.string(from: price)
}
}


ちなみに、Appleのサンプルコード(StoreManger)では、

・シングルトン

・取得完了通知はNSNotificationCenter

・SKProductを保持

という実装になってます。(どれもかぶってない・・・)

価格情報を静かに更新するような場合は、NSNotificationCenterでいいかなぁとも思います。が、

価格情報今から読み込みます!読み込み完了しました!的なことをしたい場合は、ブロックスがいいかなぁとも思います。

用途と好みに応じて使い分ければいいかなと^^;

※Objective-Cで書いてたのをSwiftで書き直したのですが、、

間違ってたら、、、優しくご指導いただければ幸いです。。