Last active
October 10, 2016 16:08
-
-
Save syou007/7225af68ae167655a76b0d4c4a653624 to your computer and use it in GitHub Desktop.
Swift3で課金処理を行った。 ref: http://qiita.com/syou007/items/1589f54ea96d5872b4ff
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 表示したい製品IDを事前に渡しておきます。 | |
StoreKitAccessor.instance.cacheProducts(productIdentifiers: ["製品IDa", "製品IDb"]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if let product = StoreKitAccessor.instance.getCacheProduct(productIdentifier: "製品IDa") { | |
// 「$1」などの課金価格を表示する。 | |
label.text = StoreKitAccessor.priceForProduct(product: product) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if !StoreKitAccessor.canMakePayments() { | |
// 購入処理が無効になっている。 | |
} | |
// 画面をロックする。 | |
// キャッシュにも商品データは持ってますが、念のため再度商品情報を取得します。 | |
StoreKitAccessor.instance.getProduct(productIdentifier: "製品ID") { [weak self] (product) in | |
// 製品がない場合は処理を終了する。 | |
guard let product = product else { | |
// 設定ミスかネットワークに接続されてない。 | |
return | |
} | |
// 購入処理 | |
StoreKitAccessor.instance.buy(product: product, callback: { [weak self] (productIdentifier) in | |
if productIdentifier == "製品ID" { | |
// 購入成功 | |
// 購入処理とロック解除 | |
} else { | |
// 処理失敗(キャンセルやシミュレーターの場合) | |
} | |
}) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if !StoreKitAccessor.canMakePayments() { | |
// 購入処理が無効になっている。 | |
} | |
// 画面をロックする。 | |
// リストアします。 | |
StoreKitAccessor.instance.restoreProducts { [weak self] (productIdentifier) in | |
// 製品がない場合は処理を終了する。 | |
if productIdentifier == "製品ID" { | |
// 購入成功 | |
// 復元処理と画面ロック解除 | |
} else { | |
// 処理失敗 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Apple Storeの情報取得、課金周りの処理 | |
class StoreKitAccessor: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver { | |
// 課金アイテムの購入可否 | |
static func canMakePayments() -> Bool { | |
return SKPaymentQueue.canMakePayments() | |
} | |
// SKProduct情報から国に合わせた金額を取得して表示します。 | |
static func priceForProduct(product:SKProduct) -> String { | |
let numberFormatter = NumberFormatter() | |
numberFormatter.formatterBehavior = .behavior10_4 | |
numberFormatter.numberStyle = .currency | |
numberFormatter.locale = product.priceLocale | |
return numberFormatter.string(from: product.price)! | |
} | |
// シングルトンで処理をする。 | |
static let instance = StoreKitAccessor() | |
// 一時的に保持する課金情報 | |
private var cacheProducts:[String: SKProduct] = [String: SKProduct]() | |
// 商品情報取得のコールバック | |
private var getProductionCallback:((SKProduct?)->())? | |
// 購入処理・リストア処理のコールバック | |
private var buyProductionCallback:((String?) -> ())? | |
/*** | |
* 商品情報関連の処理 | |
*/ | |
// 使用する商品(課金)情報を事前に取得する。 | |
// アプリ内で定義していて、事前に金額を表示したい商品IDを全て渡します。 | |
func cacheProducts(productIdentifiers:[String]) { | |
// 指定のプロダクト情報を全て取得します。 | |
let productsRequest = SKProductsRequest(productIdentifiers: Set(productIdentifiers)) | |
productsRequest.delegate = self | |
productsRequest.start() | |
} | |
// キャッシュしてる商品情報を返却する。 | |
func getCacheProduct(productIdentifier:String) -> SKProduct? { | |
return self.cacheProducts[productIdentifier] | |
} | |
// 指定した一つの商品情報を取得します。 | |
// 本メソッドの処理中は画面ロックされていることを想定しており、複数同時に本メソッドが呼び出されることは想定してない。 | |
// 購入処理時に使用してください。(事前キャッシュ情報が購入時に変更される可能性があるため) | |
func getProduct(productIdentifier:String, callback:@escaping (SKProduct?)->()) { | |
// コールバックを保存して商品情報を取得します。 | |
self.getProductionCallback = callback | |
// 指定のプロダクト情報を取得します。 | |
let productsRequest = SKProductsRequest(productIdentifiers: Set([productIdentifier])) | |
productsRequest.delegate = self | |
productsRequest.start() | |
} | |
// 商品情報取得に成功した場合 | |
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { | |
// キャッシュデータを更新する。 | |
for product in response.products { | |
self.cacheProducts[product.productIdentifier] = product | |
// コールバックが定義されている場合はコールバックを返す。(本処理では一件しか呼び出されない。) | |
self.getProductionCallback?(product) | |
} | |
// 使用したコールバックを削除する。 | |
self.getProductionCallback = nil | |
} | |
// 商品情報取得に失敗した場合 | |
func request(_ request: SKRequest, didFailWithError error: Error) { | |
// TODO Crashlyticsなどにエラーログを飛ばす。 | |
// コールバックが記載されていた場合はコールバックを返す。 | |
self.getProductionCallback?(nil) | |
self.getProductionCallback = nil | |
} | |
// 商品情報取得終了処理 | |
func requestDidFinish(_ request: SKRequest) { | |
// 商品情報取得終了時に行いたいことがあればこちらで対応する。 | |
} | |
/*** | |
* 商品購入関連の処理 | |
*/ | |
// 購入処理 | |
// 本メソッドの処理中は画面ロックされていることを想定しており、複数同時に本メソッドが呼び出されることは想定してない。 | |
// ※リストア処理と同じコールバックを使っているので注意! | |
func buy(product:SKProduct, callback:@escaping (String?) -> ()) { | |
self.buyProductionCallback = callback | |
SKPaymentQueue.default().add(self) | |
SKPaymentQueue.default().add(SKPayment(product: product)) | |
// 複数の購入処理を同時に行うこともできるが、使い方が想定できない。 | |
// for product in products { | |
// SKPaymentQueue.default().add(SKPayment(product: product)) | |
// } | |
} | |
// 購入した商品をリストアする。 | |
// 本メソッドの処理中は画面ロックされていることを想定しており、複数同時に本メソッドが呼び出されることは想定してない。 | |
// ※購入処理と同じコールバックを使っているので注意! | |
func restoreProducts(callback:@escaping (String?) -> ()) { | |
self.buyProductionCallback = callback | |
// 自分をQueueに追加して、結果を待ち構えます。 | |
SKPaymentQueue.default().add(self) | |
SKPaymentQueue.default().restoreCompletedTransactions() | |
} | |
// 課金処理成功 | |
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { | |
for transaction in queue.transactions { | |
switch transaction.transactionState { | |
case .purchased: | |
// 成功処理 | |
self.buyProductionCallback?(transaction.payment.productIdentifier) | |
self.buyProductionCallback = nil | |
queue.finishTransaction(transaction) | |
case .restored: | |
// 購入が中断された場合 | |
self.paymentQueueRestoreCompletedTransactionsFinished(queue) | |
queue.finishTransaction(transaction) | |
case .deferred: | |
// ファミリー共有待機処理 | |
// (本処理では購入失敗処理に遷移させる。) | |
queue.finishTransaction(transaction) | |
self.buyProductionCallback?(nil) | |
self.buyProductionCallback = nil | |
case .failed: | |
// 処理失敗 | |
queue.finishTransaction(transaction) | |
self.buyProductionCallback?(nil) | |
self.buyProductionCallback = nil | |
case .purchasing: | |
// 処理中 | |
break | |
} | |
} | |
} | |
// リストア処理成功処理 | |
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { | |
// リストアされた商品をチェックする | |
for transaction in queue.transactions { | |
// 本処理では一つの製品に対する処理しか考慮していないためコールバックを解放する。 | |
self.buyProductionCallback?(transaction.payment.productIdentifier) | |
self.buyProductionCallback = nil | |
} | |
// 終了したことを通知する。 | |
// ※RxSwiftのOnCompleteを使ったほうがスマートに記載できるので、本体の実装が汚くなる場合はRxSwiftの導入をお勧めします。 | |
// 本実装では誰でも使えるようにRxSwiftは導入してません。 | |
self.buyProductionCallback?(nil) | |
self.buyProductionCallback = nil | |
} | |
// リストア処理に失敗 | |
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) { | |
self.buyProductionCallback?(nil) | |
self.buyProductionCallback = nil | |
} | |
// 全てのトランザクションが終わった場合の処理 | |
func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) { | |
SKPaymentQueue.default().remove(self) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment