|
@@ -26,7 +26,7 @@ import Foundation
|
|
import StoreKit
|
|
import StoreKit
|
|
|
|
|
|
protocol TransactionController {
|
|
protocol TransactionController {
|
|
-
|
|
|
|
|
|
+
|
|
/// Process the supplied transactions on a given queue.
|
|
/// Process the supplied transactions on a given queue.
|
|
/// - parameter transactions: transactions to process
|
|
/// - parameter transactions: transactions to process
|
|
/// - parameter paymentQueue: payment queue for finishing transactions
|
|
/// - parameter paymentQueue: payment queue for finishing transactions
|
|
@@ -41,30 +41,32 @@ public enum TransactionResult {
|
|
}
|
|
}
|
|
|
|
|
|
public protocol PaymentQueue: class {
|
|
public protocol PaymentQueue: class {
|
|
-
|
|
|
|
|
|
+
|
|
func add(_ observer: SKPaymentTransactionObserver)
|
|
func add(_ observer: SKPaymentTransactionObserver)
|
|
func remove(_ observer: SKPaymentTransactionObserver)
|
|
func remove(_ observer: SKPaymentTransactionObserver)
|
|
-
|
|
|
|
|
|
+
|
|
func add(_ payment: SKPayment)
|
|
func add(_ payment: SKPayment)
|
|
|
|
|
|
func start(_ downloads: [SKDownload])
|
|
func start(_ downloads: [SKDownload])
|
|
func pause(_ downloads: [SKDownload])
|
|
func pause(_ downloads: [SKDownload])
|
|
- #if os(watchOS)
|
|
|
|
- func resumeDownloads(_ downloads: [SKDownload])
|
|
|
|
- #else
|
|
|
|
func resume(_ downloads: [SKDownload])
|
|
func resume(_ downloads: [SKDownload])
|
|
- #endif
|
|
|
|
func cancel(_ downloads: [SKDownload])
|
|
func cancel(_ downloads: [SKDownload])
|
|
|
|
|
|
func restoreCompletedTransactions(withApplicationUsername username: String?)
|
|
func restoreCompletedTransactions(withApplicationUsername username: String?)
|
|
-
|
|
|
|
|
|
+
|
|
func finishTransaction(_ transaction: SKPaymentTransaction)
|
|
func finishTransaction(_ transaction: SKPaymentTransaction)
|
|
}
|
|
}
|
|
|
|
|
|
-extension SKPaymentQueue: PaymentQueue { }
|
|
|
|
|
|
+extension SKPaymentQueue: PaymentQueue {
|
|
|
|
+ #if os(watchOS) && swift(<5.3)
|
|
|
|
+ public func resume(_ downloads: [SKDownload]) {
|
|
|
|
+ resumeDownloads(downloads)
|
|
|
|
+ }
|
|
|
|
+ #endif
|
|
|
|
+}
|
|
|
|
|
|
extension SKPaymentTransaction {
|
|
extension SKPaymentTransaction {
|
|
-
|
|
|
|
|
|
+
|
|
open override var debugDescription: String {
|
|
open override var debugDescription: String {
|
|
let transactionId = transactionIdentifier ?? "null"
|
|
let transactionId = transactionIdentifier ?? "null"
|
|
return "productId: \(payment.productIdentifier), transactionId: \(transactionId), state: \(transactionState), date: \(String(describing: transactionDate))"
|
|
return "productId: \(payment.productIdentifier), transactionId: \(transactionId), state: \(transactionState), date: \(String(describing: transactionDate))"
|
|
@@ -72,9 +74,9 @@ extension SKPaymentTransaction {
|
|
}
|
|
}
|
|
|
|
|
|
extension SKPaymentTransactionState: CustomDebugStringConvertible {
|
|
extension SKPaymentTransactionState: CustomDebugStringConvertible {
|
|
-
|
|
|
|
|
|
+
|
|
public var debugDescription: String {
|
|
public var debugDescription: String {
|
|
-
|
|
|
|
|
|
+
|
|
switch self {
|
|
switch self {
|
|
case .purchasing: return "purchasing"
|
|
case .purchasing: return "purchasing"
|
|
case .purchased: return "purchased"
|
|
case .purchased: return "purchased"
|
|
@@ -87,24 +89,24 @@ extension SKPaymentTransactionState: CustomDebugStringConvertible {
|
|
}
|
|
}
|
|
|
|
|
|
class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
|
|
class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
|
|
-
|
|
|
|
|
|
+
|
|
private let paymentsController: PaymentsController
|
|
private let paymentsController: PaymentsController
|
|
-
|
|
|
|
|
|
+
|
|
private let restorePurchasesController: RestorePurchasesController
|
|
private let restorePurchasesController: RestorePurchasesController
|
|
-
|
|
|
|
|
|
+
|
|
private let completeTransactionsController: CompleteTransactionsController
|
|
private let completeTransactionsController: CompleteTransactionsController
|
|
-
|
|
|
|
|
|
+
|
|
unowned let paymentQueue: PaymentQueue
|
|
unowned let paymentQueue: PaymentQueue
|
|
-
|
|
|
|
|
|
+
|
|
deinit {
|
|
deinit {
|
|
paymentQueue.remove(self)
|
|
paymentQueue.remove(self)
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
init(paymentQueue: PaymentQueue = SKPaymentQueue.default(),
|
|
init(paymentQueue: PaymentQueue = SKPaymentQueue.default(),
|
|
paymentsController: PaymentsController = PaymentsController(),
|
|
paymentsController: PaymentsController = PaymentsController(),
|
|
restorePurchasesController: RestorePurchasesController = RestorePurchasesController(),
|
|
restorePurchasesController: RestorePurchasesController = RestorePurchasesController(),
|
|
completeTransactionsController: CompleteTransactionsController = CompleteTransactionsController()) {
|
|
completeTransactionsController: CompleteTransactionsController = CompleteTransactionsController()) {
|
|
-
|
|
|
|
|
|
+
|
|
self.paymentQueue = paymentQueue
|
|
self.paymentQueue = paymentQueue
|
|
self.paymentsController = paymentsController
|
|
self.paymentsController = paymentsController
|
|
self.restorePurchasesController = restorePurchasesController
|
|
self.restorePurchasesController = restorePurchasesController
|
|
@@ -118,7 +120,7 @@ class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
|
|
let message = "SwiftyStoreKit.completeTransactions() must be called when the app launches."
|
|
let message = "SwiftyStoreKit.completeTransactions() must be called when the app launches."
|
|
assert(completeTransactionsController.completeTransactions != nil, message)
|
|
assert(completeTransactionsController.completeTransactions != nil, message)
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
func startPayment(_ payment: Payment) {
|
|
func startPayment(_ payment: Payment) {
|
|
assertCompleteTransactionsWasCalled()
|
|
assertCompleteTransactionsWasCalled()
|
|
|
|
|
|
@@ -126,45 +128,45 @@ class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
|
|
skPayment.applicationUsername = payment.applicationUsername
|
|
skPayment.applicationUsername = payment.applicationUsername
|
|
skPayment.quantity = payment.quantity
|
|
skPayment.quantity = payment.quantity
|
|
|
|
|
|
- if #available(iOS 12.2, tvOS 12.2, OSX 10.14.4, *) {
|
|
|
|
|
|
+ if #available(iOS 12.2, tvOS 12.2, OSX 10.14.4, watchOS 6.2, *) {
|
|
if let discount = payment.paymentDiscount?.discount as? SKPaymentDiscount {
|
|
if let discount = payment.paymentDiscount?.discount as? SKPaymentDiscount {
|
|
skPayment.paymentDiscount = discount
|
|
skPayment.paymentDiscount = discount
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-#if os(iOS) || os(tvOS)
|
|
|
|
- if #available(iOS 8.3, *) {
|
|
|
|
|
|
+ #if os(iOS) || os(tvOS) || os(watchOS)
|
|
|
|
+ if #available(iOS 8.3, watchOS 6.2, *) {
|
|
skPayment.simulatesAskToBuyInSandbox = payment.simulatesAskToBuyInSandbox
|
|
skPayment.simulatesAskToBuyInSandbox = payment.simulatesAskToBuyInSandbox
|
|
}
|
|
}
|
|
-#endif
|
|
|
|
-
|
|
|
|
|
|
+ #endif
|
|
|
|
+
|
|
paymentQueue.add(skPayment)
|
|
paymentQueue.add(skPayment)
|
|
-
|
|
|
|
|
|
+
|
|
paymentsController.append(payment)
|
|
paymentsController.append(payment)
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
func restorePurchases(_ restorePurchases: RestorePurchases) {
|
|
func restorePurchases(_ restorePurchases: RestorePurchases) {
|
|
assertCompleteTransactionsWasCalled()
|
|
assertCompleteTransactionsWasCalled()
|
|
-
|
|
|
|
|
|
+
|
|
if restorePurchasesController.restorePurchases != nil {
|
|
if restorePurchasesController.restorePurchases != nil {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
paymentQueue.restoreCompletedTransactions(withApplicationUsername: restorePurchases.applicationUsername)
|
|
paymentQueue.restoreCompletedTransactions(withApplicationUsername: restorePurchases.applicationUsername)
|
|
-
|
|
|
|
|
|
+
|
|
restorePurchasesController.restorePurchases = restorePurchases
|
|
restorePurchasesController.restorePurchases = restorePurchases
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
func completeTransactions(_ completeTransactions: CompleteTransactions) {
|
|
func completeTransactions(_ completeTransactions: CompleteTransactions) {
|
|
-
|
|
|
|
|
|
+
|
|
guard completeTransactionsController.completeTransactions == nil else {
|
|
guard completeTransactionsController.completeTransactions == nil else {
|
|
print("SwiftyStoreKit.completeTransactions() should only be called once when the app launches. Ignoring this call")
|
|
print("SwiftyStoreKit.completeTransactions() should only be called once when the app launches. Ignoring this call")
|
|
return
|
|
return
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
completeTransactionsController.completeTransactions = completeTransactions
|
|
completeTransactionsController.completeTransactions = completeTransactions
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
func finishTransaction(_ transaction: PaymentTransaction) {
|
|
func finishTransaction(_ transaction: PaymentTransaction) {
|
|
guard let skTransaction = transaction as? SKPaymentTransaction else {
|
|
guard let skTransaction = transaction as? SKPaymentTransaction else {
|
|
print("Object is not a SKPaymentTransaction: \(transaction)")
|
|
print("Object is not a SKPaymentTransaction: \(transaction)")
|
|
@@ -181,22 +183,18 @@ class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
|
|
}
|
|
}
|
|
|
|
|
|
func resume(_ downloads: [SKDownload]) {
|
|
func resume(_ downloads: [SKDownload]) {
|
|
- #if os(watchOS)
|
|
|
|
- paymentQueue.resumeDownloads(downloads)
|
|
|
|
- #else
|
|
|
|
paymentQueue.resume(downloads)
|
|
paymentQueue.resume(downloads)
|
|
- #endif
|
|
|
|
}
|
|
}
|
|
func cancel(_ downloads: [SKDownload]) {
|
|
func cancel(_ downloads: [SKDownload]) {
|
|
paymentQueue.cancel(downloads)
|
|
paymentQueue.cancel(downloads)
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
var shouldAddStorePaymentHandler: ShouldAddStorePaymentHandler?
|
|
var shouldAddStorePaymentHandler: ShouldAddStorePaymentHandler?
|
|
var updatedDownloadsHandler: UpdatedDownloadsHandler?
|
|
var updatedDownloadsHandler: UpdatedDownloadsHandler?
|
|
-
|
|
|
|
|
|
+
|
|
// MARK: SKPaymentTransactionObserver
|
|
// MARK: SKPaymentTransactionObserver
|
|
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
|
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
|
|
-
|
|
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* Some notes about how requests are processed by SKPaymentQueue:
|
|
* Some notes about how requests are processed by SKPaymentQueue:
|
|
*
|
|
*
|
|
@@ -221,39 +219,39 @@ class PaymentQueueController: NSObject, SKPaymentTransactionObserver {
|
|
var unhandledTransactions = transactions.filter { $0.transactionState != .purchasing }
|
|
var unhandledTransactions = transactions.filter { $0.transactionState != .purchasing }
|
|
|
|
|
|
if unhandledTransactions.count > 0 {
|
|
if unhandledTransactions.count > 0 {
|
|
-
|
|
|
|
|
|
+
|
|
unhandledTransactions = paymentsController.processTransactions(transactions, on: paymentQueue)
|
|
unhandledTransactions = paymentsController.processTransactions(transactions, on: paymentQueue)
|
|
-
|
|
|
|
|
|
+
|
|
unhandledTransactions = restorePurchasesController.processTransactions(unhandledTransactions, on: paymentQueue)
|
|
unhandledTransactions = restorePurchasesController.processTransactions(unhandledTransactions, on: paymentQueue)
|
|
-
|
|
|
|
|
|
+
|
|
unhandledTransactions = completeTransactionsController.processTransactions(unhandledTransactions, on: paymentQueue)
|
|
unhandledTransactions = completeTransactionsController.processTransactions(unhandledTransactions, on: paymentQueue)
|
|
-
|
|
|
|
|
|
+
|
|
if unhandledTransactions.count > 0 {
|
|
if unhandledTransactions.count > 0 {
|
|
let strings = unhandledTransactions.map { $0.debugDescription }.joined(separator: "\n")
|
|
let strings = unhandledTransactions.map { $0.debugDescription }.joined(separator: "\n")
|
|
print("unhandledTransactions:\n\(strings)")
|
|
print("unhandledTransactions:\n\(strings)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
|
|
func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
|
|
-
|
|
|
|
|
|
+
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
|
|
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
|
|
-
|
|
|
|
|
|
+
|
|
restorePurchasesController.restoreCompletedTransactionsFailed(withError: error)
|
|
restorePurchasesController.restoreCompletedTransactionsFailed(withError: error)
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
|
|
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
|
|
-
|
|
|
|
|
|
+
|
|
restorePurchasesController.restoreCompletedTransactionsFinished()
|
|
restorePurchasesController.restoreCompletedTransactionsFinished()
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {
|
|
func paymentQueue(_ queue: SKPaymentQueue, updatedDownloads downloads: [SKDownload]) {
|
|
-
|
|
|
|
|
|
+
|
|
updatedDownloadsHandler?(downloads)
|
|
updatedDownloadsHandler?(downloads)
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
#if os(iOS) && !targetEnvironment(macCatalyst)
|
|
#if os(iOS) && !targetEnvironment(macCatalyst)
|
|
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
|
|
func paymentQueue(_ queue: SKPaymentQueue, shouldAddStorePayment payment: SKPayment, for product: SKProduct) -> Bool {
|
|
|
|
|