Files

222 lines
9.1 KiB
Swift

/// Copyright (c) 2018 Razeware LLC
///
/// 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.
///
/// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
/// distribute, sublicense, create a derivative work, and/or sell copies of the
/// Software in any work that is designed, intended, or marketed for pedagogical or
/// instructional purposes related to programming, coding, application development,
/// or information technology. Permission for such use, copying, modification,
/// merger, publication, distribution, sublicensing, creation of derivative works,
/// or sale is expressly withheld.
///
/// 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.
import StoreKit
public typealias ProductIdentifier = String
public typealias ProductsRequestCompletionHandler = (_ success: Bool, _ products: [SKProduct]?) -> Void
open class IAPHelper: NSObject {
private let productIdentifiers: Set<ProductIdentifier>
private var purchasedProductIdentifiers: Set<ProductIdentifier> = []
private var productsRequest: SKProductsRequest?
private var productsRequestCompletionHandler: ProductsRequestCompletionHandler?
let receiptURL = Bundle.main.appStoreReceiptURL
// MARK: - Init
public init(productIds: Set<ProductIdentifier>) {
productIdentifiers = productIds
super.init()
loadPurchase()
SKPaymentQueue.default().add(self)
}
// MARK: - Load purchased
func loadPurchase() {
purchasedProductIdentifiers.removeAll()
productIdentifiers.forEach { (productIdentifier) in
let purchased = UserDefaults.standard.bool(forKey: productIdentifier)
if purchased {
purchasedProductIdentifiers.insert(productIdentifier)
}
}
}
}
// 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("[IAPHelper] Buying product \(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("[IAPHelper] Products loaded (count: \(response.products.count))")
// this protocol method is not guaranteed to be dispatched on main thread
DispatchQueue.main.async {
self.productsRequestCompletionHandler?(true, response.products)
self.clearRequestAndHandler()
}
}
public func request(_ request: SKRequest, didFailWithError error: Error) {
print("[IAPHelper] Failed to load list of products (error: \(error.localizedDescription))")
// this protocol method is not guaranteed to be dispatched on main thread
DispatchQueue.main.async {
self.productsRequestCompletionHandler?(false, nil)
self.clearRequestAndHandler()
}
}
private func clearRequestAndHandler() {
productsRequest = nil
productsRequestCompletionHandler = nil
}
}
// MARK: - SKPaymentTransactionObserver
extension IAPHelper: SKPaymentTransactionObserver {
public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print("[IAPHelper] Restore transaction completed")
if queue.transactions.count == 0 {
NotificationCenter.default.post(name: .EQNInAppPurchaseNoTransactions, object: nil)
}
}
public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
print("[IAPHelper] Restore transaction failed (error : \(error.localizedDescription))")
NotificationCenter.default.post(name: .EQNInAppPurchaseDidFail, object: nil)
}
public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("[IAPHelper] Updated transactions (count: \(transactions.count))")
for transaction in transactions {
switch (transaction.transactionState) {
case .purchased:
handlePurchased(transaction: transaction)
case .failed:
handleFailed(transaction: transaction)
case .restored:
handleRestored(transaction: transaction)
case .deferred:
break
case .purchasing:
break
@unknown default:
break
}
}
}
private func handlePurchased(transaction: SKPaymentTransaction) {
print("[IAPHelper] Handle purchased transaction (product: \(transaction.payment.productIdentifier))")
deliverPurchaseNotificationFor(identifier: transaction.payment.productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
// register subscription
serverPurchaseRegistration(for: transaction.payment.productIdentifier, transactionId:transaction.transactionIdentifier)
}
private func handleRestored(transaction: SKPaymentTransaction) {
guard let productIdentifier = transaction.original?.payment.productIdentifier else {
print("[IAPHelper] Handle restored transaction falied, cannot retrieve productIdentifier")
return
}
print("[IAPHelper] Handle restored transaction (product: \(productIdentifier))")
deliverPurchaseNotificationFor(identifier: productIdentifier)
SKPaymentQueue.default().finishTransaction(transaction)
// register subscription
serverPurchaseRegistration(for: productIdentifier, transactionId: transaction.transactionIdentifier)
}
private func handleFailed(transaction: SKPaymentTransaction) {
print("[IAPHelper] Handle failed transaction (product: \(transaction.payment.productIdentifier))")
if let transactionError = transaction.error as NSError?,
let localizedDescription = transaction.error?.localizedDescription,
transactionError.code != SKError.paymentCancelled.rawValue {
print("[IAPHelper] Handle failed transaction error: \(localizedDescription)")
}
NotificationCenter.default.post(name: .EQNInAppPurchaseDidFail, object: nil)
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: .EQNInAppPurchaseDidComplete, object: identifier)
}
private func serverPurchaseRegistration(for productId: String, transactionId: String?) {
let idTransaction = transactionId ?? "NOT_AVAILABLE"
let url = EQNGeneratoreURLServer.urlRegisterSubscription(forProductId: productId, transactionId: idTransaction)
ServerRequest.default().inviaInformazioniAlServer(with: url, richiesta: .registerSubscription) { (response) in
// nope
print("[IAPHelper] Subscription registered with server")
} failure: { (error) in
// nope
print("[IAPHelper] Error when registering subscription (error: \(String(describing: error?.localizedDescription))")
}
}
}