はじめに
こんにちは
アプリ内の広告を削除するなどiOSの非消耗型課金IAPを実装する機会があり、悩んだので書いてみたいと思います。Xcode9.2 Swift4.0.3
Non-Consumable(非消耗型)課金について
ユーザがアプリ内で**1度だけ**購入するもの。無効になったり減ったりせず、アプリをアンインストールしても、**リストア処理**を行うことで、購入を復元することができるものです。実装手順
1. iTunes Connectにて課金プロダクトを作成
iTunes Connect->マイApp->機能 へ進み、
+ボタンから、参照名、製品ID、価格、スクリーンショット、審査スクリーンショットを入力する。(これらを入力しないと、提出準備中になりません)
2. マネージャークラスの準備
クラス名や関数名などご自由に変更してください。
import UIKit
import StoreKit
@objc protocol IAPManagerDelegate {
//購入が完了した時
@objc optional func iapManagerDidFinishPurchased()
//ログインや購入確認のアラートが出た時
@objc optional func iapManagerDidFinishItemLoad()
//リストアが完了した時
@objc optional func iapManagerDidFinishRestore(_ productIdentifiers: [String])
//リストアに失敗した時
@objc optional func iapManagerDidFailedRestore()
//1度もアイテム購入したことがなく、リストアを実行した時
@objc optional func iapManagerDidFailedRestoreNeverPurchase()
//購入に失敗した時
@objc optional func iapManagerDidFailedPurchased()
//特殊な購入時の延期の時
@objc optional func iapManagerDidDeferredPurchased()
}
class IAPManager: NSObject {
fileprivate var isBuying = false
fileprivate var isRestoring = false
fileprivate var completionForProductidentifiers : (([SKProduct]) -> Void)?
fileprivate let paymentQueue = SKPaymentQueue.default()
weak var delegate: IAPManagerDelegate?
class var shared : IAPManager {
struct Static {
static let instance : IAPManager = IAPManager()
}
return Static.instance
}
private override init() {}
//ユーザーが課金可能かどうか
class var canMakePayments: Bool {
get { return SKPaymentQueue.canMakePayments() }
}
//Product情報をApp Storeから取得
func validateProductIdentifiers(productIdentifiers:[String], completion:(([SKProduct]) -> Void)?) {
let request = SKProductsRequest(productIdentifiers: Set<String>(productIdentifiers))
self.completionForProductidentifiers = completion
request.delegate = self
request.start()
}
//IDで指定したIAPプロダクトの購入を行います
func buy(productIdentifier: String) {
guard !self.isBuying else { print("購入処理中"); return }
validateProductIdentifiers(productIdentifiers: [productIdentifier]) { [unowned self] products in
let buyProduct: SKProduct? = {
return products.filter { $0.productIdentifier == productIdentifier }.first
}()
//購入処理開始
guard let product = buyProduct else { return }
self.isBuying = true
let payment = SKMutablePayment(product: product)
self.paymentQueue.add(payment)
}
}
//リストアを行います
func restore() {
guard !isRestoring else { print("リストア処理中"); return }
self.isRestoring = true
paymentQueue.restoreCompletedTransactions()
}
}
extension IAPManager: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
print("-----purchased-----")
self.isBuying = false
delegate?.iapManagerDidFinishPurchased?()
queue.finishTransaction(transaction)
case .purchasing:
print("-----purchasing-----")
delegate?.iapManagerDidFinishItemLoad?()
case .restored:
print("-----restored-----")
queue.finishTransaction(transaction)
case .failed:
print("-----purchaseFailed-----")
self.isBuying = false
delegate?.iapManagerDidFailedPurchased?()
queue.finishTransaction(transaction)
case .deferred:
print("-----purchaseDeferred-----")
self.isBuying = false
delegate?.iapManagerDidDeferredPurchased?()
queue.finishTransaction(transaction)
}
}
}
//リストアの問い合わせが完了した時
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
self.isRestoring = false
guard !queue.transactions.isEmpty else {
delegate?.iapManagerDidFailedRestoreNeverPurchase?()
return
}
self.isRestoring = false
let productIdentifiers: [String] = {
var identifiers: [String] = []
queue.transactions.forEach {
identifiers.append($0.payment.productIdentifier)
}
return identifiers
}()
delegate?.iapManagerDidFinishRestore?(productIdentifiers)
}
//リストアの問い合わせが失敗した時
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
print("-----restoreFailed-----")
self.isRestoring = false
delegate?.iapManagerDidFailedRestore?()
}
//AppStoreで必須
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
return true
}
}
extension IAPManager: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
if !response.products.isEmpty {
self.completionForProductidentifiers?(response.products)
}
}
}
3. AppDelegate.swiftを書き換える
AppDelegate.swiftのコードの一部を以下のように書き換えます。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
//以下を追加
SKPaymentQueue.default().add(IAPManager.shared)
return true
}
4. UIを作成する
課金を行うボタンなどのUIを作成します。(例)
(画像のように大げさに配置する必要はありませんが、リストアボタンを分かりやすい位置に置かないとリジェクトされる可能性があります。)
アプリ内で課金を行いたいタイミングで(購入ボタンを押した時など)IAPManagerの以下メソッドを呼びます。
また、このタイミングでIndicatorを表示するなどの実装をすると良いです。
//購入
IAPManager.shared.buy(productIdentifier: "手順1で記入した製品ID")
//リストア
IAPManager.shared.restore()
5. デリゲートメソッドを実装する
override func viewDidLoad() {
super.viewDidLoad()
IAPManager.shared.delegate = self
}
ボタンなどを配置しているUIViewControllerにて、IAPManagerのデリゲートメソッドを記述します。
ここで、各タイミングで行うべき処理を書いてみました。参考にしてみてください
extension TitleViewController: IAPManagerDelegate {
//購入が完了した時
func iapManagerDidFinishPurchased() {
//購入完了をユーザに知らせるアラートを表示
//UserDefaultにBool値を保存する(例:isPurchased = true)
//Indicatorを隠す処理
//広告を消す処理など
}
//購入に失敗した時
func iapManagerDidFailedPurchased() {
//購入失敗をユーザに知らせるアラートなど
//Indicatorを隠す処理
}
//リストアが完了した時
func iapManagerDidFinishRestore(_ productIdentifiers: [String]) {
for identifier in productIdentifiers {
if identifier == App.iapID {
//リストア完了をユーザに知らせるアラートを表示
//UserDefaultにBool値を保存する(例:isPurchased = true)
//広告を消す処理など
}
}
//Indicatorを隠す処理
}
//1度もアイテム購入したことがなく、リストアを実行した時
func iapManagerDidFailedRestoreNeverPurchase() {
//先に購入をお願いするアラートを表示
//Indicatorを隠す処理
}
//リストアに失敗した時
func iapManagerDidFailedRestore() {
//リストア失敗をユーザに知らせるアラートを表示
//Indicatorを隠す処理
}
//特殊な購入時の延期の時
func iapManagerDidDeferredPurchased() {
//購入失敗をユーザに知らせるアラートを表示
//Indicatorを隠す処理
}
}
6. テストを行う
ここで、実際に購入ができるかどうかテストをします。
iTunes Connect -> ユーザと役割 -> Sandboxテスター へ進み、
+ボタンからテストユーザを作成します。
7. iPhone側の準備
サインアウトから、普段利用しているAppleアカウントからログアウトしておきます。(ここでテストアカウントでログインする必要は無いみたいです。)
課金テストをするアプリを開き、課金するところまで操作します。すると、以下のようなアラートが表示されるので、
手順6で作成したユーザのAppleIDとパスワードを入力します。
成功すると以下のアラートが出るはずです。
参考にさせていただいた記事
見て頂いてありがとうございます。