Skip to content

Instantly share code, notes, and snippets.

@jeffreybergier
Created December 13, 2015 21:22
Show Gist options
  • Save jeffreybergier/fc36340a33ee7bd280c6 to your computer and use it in GitHub Desktop.
Save jeffreybergier/fc36340a33ee7bd280c6 to your computer and use it in GitHub Desktop.
A simple Completion Handler based Purchase Manager class for StoreKit written in Swift
//
// PurchaseManager.swift
// GratuitousSwift
//
// Created by Jeffrey Bergier on 9/30/15.
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
class JSBPurchaseManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
private let paymentQueue = SKPaymentQueue.defaultQueue()
// MARK: Set Observer
func beginObserving() {
self.paymentQueue.addTransactionObserver(self)
}
func endObserving() {
self.paymentQueue.removeTransactionObserver(self)
for (key, _) in self.productsRequestsInProgress {
key.cancel()
key.delegate = .None
}
self.productsRequestsInProgress = [ : ]
self.latestPurchasesRestoreCompletionHandler = .None
self.purchasesInProgress = [ : ]
}
// MARK: Receipt verification
var bundleID: String {
return "OBFUSCATE YOUR BUNDLE ID HERE"
}
func verifyAppReceiptAgainstAppleCertificate() -> Bool {
let verifier = RMStoreAppReceiptVerifier()
verifier.bundleIdentifier = self.bundleID
let verified = verifier.verifyAppReceipt()
return verified
}
// MARK: Product Request
private var productsRequestsInProgress = [SKRequest : ProductsRequestCompletionHandler]()
func initiateRequest(request: SKProductsRequest, completionHandler: (request: SKProductsRequest, response: SKProductsResponse?, error: NSError?) -> ()) {
self.productsRequestsInProgress[request] = completionHandler
request.delegate = self
request.start()
}
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
if let completionHandler = self.productsRequestsInProgress[request] {
completionHandler(request: request, response: response, error: .None)
}
self.productsRequestsInProgress.removeValueForKey(request)
}
func request(request: SKRequest, didFailWithError error: NSError) {
if let completionHandler = self.productsRequestsInProgress[request], let productsRequest = request as? SKProductsRequest {
completionHandler(request: productsRequest, response: .None, error: error)
}
self.productsRequestsInProgress.removeValueForKey(request)
}
private typealias ProductsRequestCompletionHandler = (request: SKProductsRequest, response: SKProductsResponse?, error: NSError?) -> ()
// MARK: Restore Purchases
private var latestPurchasesRestoreCompletionHandler: PurchasesRestoreCompletionHandler?
func restorePurchasesWithCompletionHandler(completionHandler: (queue: SKPaymentQueue?, error: NSError?) -> ()) {
if let _ = self.latestPurchasesRestoreCompletionHandler {
completionHandler(queue: .None, error: NSError(purchaseError: .RestorePurchasesAlreadyInProgress))
} else {
self.latestPurchasesRestoreCompletionHandler = completionHandler
self.paymentQueue.restoreCompletedTransactions()
}
}
func paymentQueue(queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: NSError) {
self.latestPurchasesRestoreCompletionHandler?(queue: queue, error: error)
self.latestPurchasesRestoreCompletionHandler = .None
}
func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
self.latestPurchasesRestoreCompletionHandler?(queue: queue, error: .None)
self.latestPurchasesRestoreCompletionHandler = .None
}
private typealias PurchasesRestoreCompletionHandler = (queue: SKPaymentQueue?, error: NSError?) -> ()
// MARK: Purchasing Items
private var purchasesInProgress = [SKPayment : PurchasePaymentCompletionHandler]()
func initiatePurchaseWithPayment(payment: SKPayment, completionHandler: (transaction: SKPaymentTransaction) -> ()) {
if let existingCompletionHandler = self.purchasesInProgress[payment] {
let bogusTransaction = BogusTransaction()
bogusTransaction.error = NSError(purchaseError: .PurchaseAlreadyInProgress)
existingCompletionHandler(transaction: bogusTransaction)
} else {
self.purchasesInProgress[payment] = completionHandler
self.paymentQueue.addPayment(payment)
}
}
// Sent when the transaction array has changed (additions or state changes). Client should check state of transactions and finish as appropriate.
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .Purchasing:
break // do nothing and wait
case .Purchased, .Restored, .Failed, .Deferred:
if let completionHandler = self.purchasesInProgress[transaction.payment] {
completionHandler(transaction: transaction)
self.purchasesInProgress.removeValueForKey(transaction.payment)
} else {
// we know finish can never get called on these transactions because there is no completion handler
// doing it now in a last ditch effort to prevent errors
queue.finishTransaction(transaction)
}
}
}
}
func paymentQueue(queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
}
func finishTransaction(transaction: SKPaymentTransaction) {
self.paymentQueue.finishTransaction(transaction)
}
private typealias PurchasePaymentCompletionHandler = (transaction: SKPaymentTransaction) -> ()
private class BogusTransaction: SKPaymentTransaction {
override var error: NSError? {
get {
return _error
}
set {
_error = newValue
}
}
private var _error: NSError?
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment