Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 45 additions & 39 deletions SwiftyStoreKit/InAppProductPurchaseRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,47 +27,56 @@ import Foundation

class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver {

enum TransactionType {
case purchase(product: SKProduct)
case restore
}

enum TransactionResult {
case purchased(product: Product)
case restored(product: Product)
case failed(error: Error)
}

typealias RequestCallback = ([TransactionResult]) -> ()
private let callback: RequestCallback
private var purchases : [SKPaymentTransactionState: [String]] = [:]

var paymentQueue: SKPaymentQueue {
return SKPaymentQueue.default()
}
let product : SKProduct?

let transactionType: TransactionType
let atomically: Bool

deinit {
paymentQueue.remove(self)
}
// Initialiser for product purchase
private init(product: SKProduct?, atomically: Bool, callback: @escaping RequestCallback) {

// Initialiser for product purchase
private init(transactionType: TransactionType, atomically: Bool, callback: @escaping RequestCallback) {
self.transactionType = transactionType
self.atomically = atomically
self.product = product
self.callback = callback
super.init()
paymentQueue.add(self)
}

// MARK: Public methods
class func startPayment(product: SKProduct, atomically: Bool, applicationUsername: String = "", callback: @escaping RequestCallback) -> InAppProductPurchaseRequest {
let request = InAppProductPurchaseRequest(product: product, atomically: atomically, callback: callback)
class func startPayment(_ product: SKProduct, atomically: Bool, applicationUsername: String = "", callback: @escaping RequestCallback) -> InAppProductPurchaseRequest {
let transactionType = TransactionType.purchase(product: product)
let request = InAppProductPurchaseRequest(transactionType: transactionType, atomically: atomically, callback: callback)
request.startPayment(product, applicationUsername: applicationUsername)
return request
}

class func restorePurchases(atomically: Bool, callback: @escaping RequestCallback) -> InAppProductPurchaseRequest {
let request = InAppProductPurchaseRequest(product: nil, atomically: atomically, callback: callback)
let transactionType = TransactionType.restore
let request = InAppProductPurchaseRequest(transactionType: transactionType, atomically: atomically, callback: callback)
request.startRestorePurchases()
return request
}

class func finishTransaction(_ transaction: PaymentTransaction) {
guard let skTransaction = transaction as? SKPaymentTransaction else {
print("Object is not a SKPaymentTransaction: \(transaction)")
Expand All @@ -85,45 +94,44 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver {
}
let payment = SKMutablePayment(product: product)
payment.applicationUsername = applicationUsername

DispatchQueue.global(qos: .default).async {
self.paymentQueue.add(payment)
}
}
private func startRestorePurchases() {

DispatchQueue.global(qos: .default).async {
self.paymentQueue.restoreCompletedTransactions()
}
}

// MARK: SKPaymentTransactionObserver
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

var transactionResults: [TransactionResult] = []

for transaction in transactions {

let transactionProductIdentifier = transaction.payment.productIdentifier

var isPurchaseRequest = false
if let productIdentifier = product?._productIdentifier {
if transactionProductIdentifier != productIdentifier {
switch transactionType {
case .purchase(let product):
guard transactionProductIdentifier == product._productIdentifier else {
continue
}
isPurchaseRequest = true

default:
break
}

let transactionState = transaction.transactionState

switch transactionState {
case .purchased:
if isPurchaseRequest {
let product = Product(productId: transactionProductIdentifier, transaction: transaction, needsFinishTransaction: !atomically)
transactionResults.append(.purchased(product: product))
if atomically {
paymentQueue.finishTransaction(transaction)
}
let product = Product(productId: transactionProductIdentifier, transaction: transaction, needsFinishTransaction: !atomically)
transactionResults.append(.purchased(product: product))
if atomically {
paymentQueue.finishTransaction(transaction)
}
case .failed:
// TODO: How to discriminate between purchase and restore?
Expand All @@ -134,12 +142,10 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver {
transactionResults.append(.failed(error: transaction.error ?? altError))
paymentQueue.finishTransaction(transaction)
case .restored:
if !isPurchaseRequest {
let product = Product(productId: transactionProductIdentifier, transaction: transaction, needsFinishTransaction: !atomically)
transactionResults.append(.restored(product: product))
if atomically {
paymentQueue.finishTransaction(transaction)
}
let product = Product(productId: transactionProductIdentifier, transaction: transaction, needsFinishTransaction: !atomically)
transactionResults.append(.restored(product: product))
if atomically {
paymentQueue.finishTransaction(transaction)
}
case .purchasing:
// In progress: do nothing
Expand All @@ -161,13 +167,13 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver {
}
}
}

func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {

}

func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {

DispatchQueue.main.async {
self.callback([.failed(error: error)])
}
Expand All @@ -176,12 +182,12 @@ class InAppProductPurchaseRequest: NSObject, SKPaymentTransactionObserver {
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
// This method will be called after all purchases have been restored (includes the case of no purchases)
guard let restored = purchases[.restored], restored.count > 0 else {

self.callback([])
return
}
}

func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {

}
Expand Down
5 changes: 3 additions & 2 deletions SwiftyStoreKit/SwiftyStoreKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ public class SwiftyStoreKit {
return
}

inflightPurchases[productIdentifier] = InAppProductPurchaseRequest.startPayment(product: product, atomically: atomically, applicationUsername: applicationUsername) { results in
inflightPurchases[productIdentifier] = InAppProductPurchaseRequest.startPayment(product, atomically: atomically, applicationUsername: applicationUsername) { results in

self.inflightPurchases[productIdentifier] = nil

Expand All @@ -240,7 +240,8 @@ public class SwiftyStoreKit {
case .failed(let error):
return .error(error: .failed(error: error))
case .restored(let product):
return .error(error: .failed(error: storeInternalError(code: InternalErrorCode.restoredPurchaseWhenPurchasing.rawValue, description: "Cannot restore product \(product.productId) from purchase path")))
return .success(product: product)

}
}

Expand Down