Skip to content

Instantly share code, notes, and snippets.

@syou007
Last active October 10, 2016 16:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save syou007/7225af68ae167655a76b0d4c4a653624 to your computer and use it in GitHub Desktop.
Save syou007/7225af68ae167655a76b0d4c4a653624 to your computer and use it in GitHub Desktop.
Swift3で課金処理を行った。 ref: http://qiita.com/syou007/items/1589f54ea96d5872b4ff
// 表示したい製品IDを事前に渡しておきます。
StoreKitAccessor.instance.cacheProducts(productIdentifiers: ["製品IDa", "製品IDb"])
if let product = StoreKitAccessor.instance.getCacheProduct(productIdentifier: "製品IDa") {
// 「$1」などの課金価格を表示する。
label.text = StoreKitAccessor.priceForProduct(product: product)
}
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 {
// 処理失敗(キャンセルやシミュレーターの場合)
}
})
}
if !StoreKitAccessor.canMakePayments() {
// 購入処理が無効になっている。
}
// 画面をロックする。
// リストアします。
StoreKitAccessor.instance.restoreProducts { [weak self] (productIdentifier) in
// 製品がない場合は処理を終了する。
if productIdentifier == "製品ID" {
// 購入成功
// 復元処理と画面ロック解除
} else {
// 処理失敗
}
}
// 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