222 lines
9.1 KiB
Swift
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))")
|
|
}
|
|
}
|
|
}
|