Skip to content

Instantly share code, notes, and snippets.

@kimyongin
Last active August 23, 2018 08:12
Show Gist options
  • Save kimyongin/282f765d96f8649b6c8170ccefdde3a4 to your computer and use it in GitHub Desktop.
Save kimyongin/282f765d96f8649b6c8170ccefdde3a4 to your computer and use it in GitHub Desktop.
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)
}
}
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!)
}
}
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