From 7d942b5be891f1ae2ab3cf8f63f18eeaf78f1c58 Mon Sep 17 00:00:00 2001 From: George Kiriy Date: Sat, 15 Apr 2017 15:18:47 +0300 Subject: [PATCH] Added SKPayment and SKProductsRequest wrappers --- PMKStoreKit.xcodeproj/project.pbxproj | 16 +++++-- Sources/SKPayment+Promise.swift | 45 +++++++++++++++++++ ....swift => SKProductsRequest+Promise.swift} | 4 +- Sources/SKReceiptRefreshRequest+Promise.swift | 35 +++++++++++++++ 4 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 Sources/SKPayment+Promise.swift rename Sources/{SKRequest+Promise.swift => SKProductsRequest+Promise.swift} (91%) create mode 100644 Sources/SKReceiptRefreshRequest+Promise.swift diff --git a/PMKStoreKit.xcodeproj/project.pbxproj b/PMKStoreKit.xcodeproj/project.pbxproj index f56bf96..b0179cc 100644 --- a/PMKStoreKit.xcodeproj/project.pbxproj +++ b/PMKStoreKit.xcodeproj/project.pbxproj @@ -9,11 +9,13 @@ /* Begin PBXBuildFile section */ 637B4A6C1D5D5F9B00E1BC6C /* SKRequest+AnyPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = 637B4A691D5D5F9B00E1BC6C /* SKRequest+AnyPromise.h */; settings = {ATTRIBUTES = (Public, ); }; }; 637B4A6D1D5D5F9B00E1BC6C /* SKRequest+AnyPromise.m in Sources */ = {isa = PBXBuildFile; fileRef = 637B4A6A1D5D5F9B00E1BC6C /* SKRequest+AnyPromise.m */; }; - 637B4A6E1D5D5F9B00E1BC6C /* SKRequest+Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 637B4A6B1D5D5F9B00E1BC6C /* SKRequest+Promise.swift */; }; + 637B4A6E1D5D5F9B00E1BC6C /* SKProductsRequest+Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = 637B4A6B1D5D5F9B00E1BC6C /* SKProductsRequest+Promise.swift */; }; 637B4A711D5D5FA400E1BC6C /* TestStoreKit.m in Sources */ = {isa = PBXBuildFile; fileRef = 637B4A6F1D5D5FA400E1BC6C /* TestStoreKit.m */; }; 637B4A721D5D5FA400E1BC6C /* TestStoreKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 637B4A701D5D5FA400E1BC6C /* TestStoreKit.swift */; }; 637B4A741D5D5FC600E1BC6C /* PMKStoreKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 637B4A731D5D5FC600E1BC6C /* PMKStoreKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 63C7FFF71D5C020D003BAE60 /* PMKStoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63C7FFA71D5BEE09003BAE60 /* PMKStoreKit.framework */; }; + D3F6DBF31EA245750013E242 /* SKPayment+Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F6DBF21EA245750013E242 /* SKPayment+Promise.swift */; }; + D3F6DBF51EA246340013E242 /* SKReceiptRefreshRequest+Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3F6DBF41EA246340013E242 /* SKReceiptRefreshRequest+Promise.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -29,7 +31,7 @@ /* Begin PBXFileReference section */ 637B4A691D5D5F9B00E1BC6C /* SKRequest+AnyPromise.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SKRequest+AnyPromise.h"; path = "Sources/SKRequest+AnyPromise.h"; sourceTree = SOURCE_ROOT; }; 637B4A6A1D5D5F9B00E1BC6C /* SKRequest+AnyPromise.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "SKRequest+AnyPromise.m"; path = "Sources/SKRequest+AnyPromise.m"; sourceTree = SOURCE_ROOT; }; - 637B4A6B1D5D5F9B00E1BC6C /* SKRequest+Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "SKRequest+Promise.swift"; path = "Sources/SKRequest+Promise.swift"; sourceTree = SOURCE_ROOT; }; + 637B4A6B1D5D5F9B00E1BC6C /* SKProductsRequest+Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "SKProductsRequest+Promise.swift"; path = "Sources/SKProductsRequest+Promise.swift"; sourceTree = SOURCE_ROOT; }; 637B4A6F1D5D5FA400E1BC6C /* TestStoreKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TestStoreKit.m; path = Tests/TestStoreKit.m; sourceTree = SOURCE_ROOT; }; 637B4A701D5D5FA400E1BC6C /* TestStoreKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TestStoreKit.swift; path = Tests/TestStoreKit.swift; sourceTree = SOURCE_ROOT; }; 637B4A731D5D5FC600E1BC6C /* PMKStoreKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PMKStoreKit.h; path = Sources/PMKStoreKit.h; sourceTree = SOURCE_ROOT; }; @@ -38,6 +40,8 @@ 63C7FFF21D5C020D003BAE60 /* PMKSKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PMKSKTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 63CCF8121D5C0C4E00503216 /* Cartfile */ = {isa = PBXFileReference; lastKnownFileType = text; path = Cartfile; sourceTree = ""; }; 63CCF8171D5C11B500503216 /* Carthage.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Carthage.xcconfig; sourceTree = ""; }; + D3F6DBF21EA245750013E242 /* SKPayment+Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "SKPayment+Promise.swift"; path = "Sources/SKPayment+Promise.swift"; sourceTree = SOURCE_ROOT; }; + D3F6DBF41EA246340013E242 /* SKReceiptRefreshRequest+Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "SKReceiptRefreshRequest+Promise.swift"; path = "Sources/SKReceiptRefreshRequest+Promise.swift"; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -86,7 +90,9 @@ 637B4A731D5D5FC600E1BC6C /* PMKStoreKit.h */, 637B4A691D5D5F9B00E1BC6C /* SKRequest+AnyPromise.h */, 637B4A6A1D5D5F9B00E1BC6C /* SKRequest+AnyPromise.m */, - 637B4A6B1D5D5F9B00E1BC6C /* SKRequest+Promise.swift */, + 637B4A6B1D5D5F9B00E1BC6C /* SKProductsRequest+Promise.swift */, + D3F6DBF21EA245750013E242 /* SKPayment+Promise.swift */, + D3F6DBF41EA246340013E242 /* SKReceiptRefreshRequest+Promise.swift */, ); name = Sources; path = "PMK+UIKit"; @@ -215,8 +221,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D3F6DBF51EA246340013E242 /* SKReceiptRefreshRequest+Promise.swift in Sources */, 637B4A6D1D5D5F9B00E1BC6C /* SKRequest+AnyPromise.m in Sources */, - 637B4A6E1D5D5F9B00E1BC6C /* SKRequest+Promise.swift in Sources */, + 637B4A6E1D5D5F9B00E1BC6C /* SKProductsRequest+Promise.swift in Sources */, + D3F6DBF31EA245750013E242 /* SKPayment+Promise.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Sources/SKPayment+Promise.swift b/Sources/SKPayment+Promise.swift new file mode 100644 index 0000000..cec99d1 --- /dev/null +++ b/Sources/SKPayment+Promise.swift @@ -0,0 +1,45 @@ +import StoreKit +#if !COCOAPODS +import PromiseKit +#endif + +extension SKPayment { + public func promise() -> Promise { + return PaymentObserver(payment: self).promise + } +} + +fileprivate class PaymentObserver: NSObject, SKPaymentTransactionObserver { + let (promise, fulfill, reject) = Promise.pending() + let payment: SKPayment + var retainCycle: PaymentObserver? + + init(payment: SKPayment) { + self.payment = payment + super.init() + SKPaymentQueue.default().add(self) + SKPaymentQueue.default().add(payment) + retainCycle = self + } + + func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { + guard let transaction = transactions.first(where: { $0.payment == payment }) else { + return + } + switch transaction.transactionState { + case .purchased: + queue.finishTransaction(transaction) + fulfill(transaction) + queue.remove(self) + retainCycle = nil + case .failed: + let error = transaction.error ?? NSError.cancelledError() + queue.finishTransaction(transaction) + reject(error) + queue.remove(self) + retainCycle = nil + default: + break + } + } +} diff --git a/Sources/SKRequest+Promise.swift b/Sources/SKProductsRequest+Promise.swift similarity index 91% rename from Sources/SKRequest+Promise.swift rename to Sources/SKProductsRequest+Promise.swift index 6f587a8..ea09e8e 100644 --- a/Sources/SKRequest+Promise.swift +++ b/Sources/SKProductsRequest+Promise.swift @@ -13,7 +13,7 @@ import PromiseKit import PromiseKit */ -extension SKRequest { +extension SKProductsRequest { /** Sends the request to the Apple App Store. @@ -29,7 +29,7 @@ extension SKRequest { } -private class SKDelegate: NSObject, SKProductsRequestDelegate { +fileprivate class SKDelegate: NSObject, SKProductsRequestDelegate { let (promise, fulfill, reject) = Promise.pending() var retainCycle: SKDelegate? diff --git a/Sources/SKReceiptRefreshRequest+Promise.swift b/Sources/SKReceiptRefreshRequest+Promise.swift new file mode 100644 index 0000000..b1b942a --- /dev/null +++ b/Sources/SKReceiptRefreshRequest+Promise.swift @@ -0,0 +1,35 @@ +import StoreKit +#if !COCOAPODS +import PromiseKit +#endif + +extension SKReceiptRefreshRequest { + public func promise() -> Promise { + return ReceiptRefreshObserver(request: self).promise + } +} + +fileprivate class ReceiptRefreshObserver: NSObject, SKRequestDelegate { + let (promise, fulfill, reject) = Promise.pending() + let request: SKReceiptRefreshRequest + var retainCycle: ReceiptRefreshObserver? + + init(request: SKReceiptRefreshRequest) { + self.request = request + super.init() + request.delegate = self + request.start() + retainCycle = self + } + + + func requestDidFinish(_ request: SKRequest) { + fulfill(self.request) + retainCycle = nil + } + + func request(_ request: SKRequest, didFailWithError error: Error) { + reject(error) + retainCycle = nil + } +}