Last active
August 23, 2018 08:12
-
-
Save kimyongin/282f765d96f8649b6c8170ccefdde3a4 to your computer and use it in GitHub Desktop.
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
import StoreKit | |
public typealias ProductIdentifier = String | |
public typealias ProductsRequestCompletionHandler = (_ success: Bool, _ products: [SKProduct]?) -> Void | |
extension Notification.Name { | |
static let IAPHelperPurchaseNotification = Notification.Name("IAPHelperPurchaseNotification") | |
} | |
open class IAPHelper: NSObject { | |
private let productIdentifiers: Set<ProductIdentifier> | |
private var purchasedProductIdentifiers: Set<ProductIdentifier> = [] | |
private var productsRequest: SKProductsRequest? | |
private var productsRequestCompletionHandler: ProductsRequestCompletionHandler? | |
public init(productIds: Set<ProductIdentifier>) { | |
productIdentifiers = productIds | |
for productIdentifier in productIds { | |
let purchased = UserDefaults.standard.bool(forKey: productIdentifier) | |
if purchased { | |
purchasedProductIdentifiers.insert(productIdentifier) | |
print("Previously purchased: \(productIdentifier)") | |
} else { | |
print("Not purchased: \(productIdentifier)") | |
} | |
} | |
super.init() | |
SKPaymentQueue.default().add(self) | |
} | |
} | |
// MARK: - StoreKit API | |
extension IAPHelper { | |
public func requestProducts(_ completionHandler: @escaping ProductsRequestCompletionHandler) { | |
productsRequest?.cancel() | |
productsRequestCompletionHandler = completionHandler | |
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers) | |
productsRequest!.delegate = self | |
productsRequest!.start() | |
} | |
public func buyProduct(_ product: SKProduct) { | |
print("Buying \(product.productIdentifier)...") | |
let payment = SKPayment(product: product) | |
SKPaymentQueue.default().add(payment) | |
} | |
public func isProductPurchased(_ productIdentifier: ProductIdentifier) -> Bool { | |
return purchasedProductIdentifiers.contains(productIdentifier) | |
} | |
public class func canMakePayments() -> Bool { | |
return SKPaymentQueue.canMakePayments() | |
} | |
public func restorePurchases() { | |
SKPaymentQueue.default().restoreCompletedTransactions() | |
} | |
} | |
// MARK: - SKProductsRequestDelegate | |
extension IAPHelper: SKProductsRequestDelegate { | |
public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { | |
print("Loaded list of products...") | |
let products = response.products | |
productsRequestCompletionHandler?(true, products) | |
clearRequestAndHandler() | |
for p in products { | |
print("Found product: \(p.productIdentifier) \(p.localizedTitle) \(p.price.floatValue)") | |
} | |
} | |
public func request(_ request: SKRequest, didFailWithError error: Error) { | |
print("Failed to load list of products.") | |
print("Error: \(error.localizedDescription)") | |
productsRequestCompletionHandler?(false, nil) | |
clearRequestAndHandler() | |
} | |
private func clearRequestAndHandler() { | |
productsRequest = nil | |
productsRequestCompletionHandler = nil | |
} | |
} | |
// MARK: - SKPaymentTransactionObserver | |
extension IAPHelper: SKPaymentTransactionObserver { | |
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { | |
for transaction in transactions { | |
switch (transaction.transactionState) { | |
case .purchased: | |
complete(transaction: transaction) | |
break | |
case .failed: | |
fail(transaction: transaction) | |
break | |
case .restored: | |
restore(transaction: transaction) | |
break | |
case .deferred: | |
break | |
case .purchasing: | |
break | |
} | |
} | |
} | |
private func complete(transaction: SKPaymentTransaction) { | |
print("complete...") | |
deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier) | |
SKPaymentQueue.default().finishTransaction(transaction) | |
} | |
private func restore(transaction: SKPaymentTransaction) { | |
guard let productIdentifier = transaction.original?.payment.productIdentifier else { return } | |
print("restore... \(productIdentifier)") | |
deliverPurchaseNotificationFor(identifier: productIdentifier) | |
SKPaymentQueue.default().finishTransaction(transaction) | |
} | |
private func fail(transaction: SKPaymentTransaction) { | |
print("fail...") | |
if let transactionError = transaction.error as NSError?, | |
let localizedDescription = transaction.error?.localizedDescription, | |
transactionError.code != SKError.paymentCancelled.rawValue { | |
print("Transaction Error: \(localizedDescription)") | |
} | |
SKPaymentQueue.default().finishTransaction(transaction) | |
} | |
private func deliverPurchaseNotificationFor(identifier: String?) { | |
guard let identifier = identifier else { return } | |
purchasedProductIdentifiers.insert(identifier) | |
UserDefaults.standard.set(true, forKey: identifier) | |
NotificationCenter.default.post(name: .IAPHelperPurchaseNotification, object: identifier) | |
} | |
} |
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
import UIKit | |
import StoreKit | |
class ProductCell: UITableViewCell { | |
static let priceFormatter: NumberFormatter = { | |
let formatter = NumberFormatter() | |
formatter.formatterBehavior = .behavior10_4 | |
formatter.numberStyle = .currency | |
return formatter | |
}() | |
var buyButtonHandler: ((_ product: SKProduct) -> Void)? | |
var product: SKProduct? { | |
didSet { | |
guard let product = product else { return } | |
textLabel?.text = product.localizedTitle | |
if RazeFaceProducts.store.isProductPurchased(product.productIdentifier) { | |
accessoryType = .checkmark | |
accessoryView = nil | |
detailTextLabel?.text = "" | |
} else if IAPHelper.canMakePayments() { | |
ProductCell.priceFormatter.locale = product.priceLocale | |
detailTextLabel?.text = ProductCell.priceFormatter.string(from: product.price) | |
accessoryType = .none | |
accessoryView = self.newBuyButton() | |
} else { | |
detailTextLabel?.text = "Not available" | |
} | |
} | |
} | |
override func prepareForReuse() { | |
super.prepareForReuse() | |
textLabel?.text = "" | |
detailTextLabel?.text = "" | |
accessoryView = nil | |
} | |
func newBuyButton() -> UIButton { | |
let button = UIButton(type: .system) | |
button.setTitleColor(tintColor, for: .normal) | |
button.setTitle("Buy", for: .normal) | |
button.addTarget(self, action: #selector(ProductCell.buyButtonTapped(_:)), for: .touchUpInside) | |
button.sizeToFit() | |
return button | |
} | |
@objc func buyButtonTapped(_ sender: AnyObject) { | |
buyButtonHandler?(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
import Foundation | |
public struct RazeFaceProducts { | |
public static let SwiftShopping = "com.razeware.razefaces.swiftshopping" | |
private static let productIdentifiers: Set<ProductIdentifier> = [RazeFaceProducts.SwiftShopping] | |
public static let store = IAPHelper(productIds: RazeFaceProducts.productIdentifiers) | |
} | |
func resourceNameForProductIdentifier(_ productIdentifier: String) -> String? { | |
return productIdentifier.components(separatedBy: ".").last | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment