From e2b9dba68199ace0c22e17860ffc1ba87b6ea132 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Fri, 10 Apr 2020 12:11:50 +0200 Subject: [PATCH 1/9] Added InAppRequest --- SwiftyStoreKit/InAppProductQueryRequest.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SwiftyStoreKit/InAppProductQueryRequest.swift b/SwiftyStoreKit/InAppProductQueryRequest.swift index 880a2262..97c9f889 100644 --- a/SwiftyStoreKit/InAppProductQueryRequest.swift +++ b/SwiftyStoreKit/InAppProductQueryRequest.swift @@ -26,11 +26,13 @@ import StoreKit typealias InAppProductRequestCallback = (RetrieveResults) -> Void -protocol InAppProductRequest: class { +public protocol InAppRequest: class { func start() func cancel() } +protocol InAppProductRequest: InAppRequest { } + class InAppProductQueryRequest: NSObject, InAppProductRequest, SKProductsRequestDelegate { private let callback: InAppProductRequestCallback From 135464bd53ee05c7bd64f499e6015f40f8be7e3f Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Fri, 10 Apr 2020 12:13:33 +0200 Subject: [PATCH 2/9] Added InAppReceiptRefreshRequest.cancel --- SwiftyStoreKit/InAppReceiptRefreshRequest.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SwiftyStoreKit/InAppReceiptRefreshRequest.swift b/SwiftyStoreKit/InAppReceiptRefreshRequest.swift index 2aa9f5c4..d4402c4d 100644 --- a/SwiftyStoreKit/InAppReceiptRefreshRequest.swift +++ b/SwiftyStoreKit/InAppReceiptRefreshRequest.swift @@ -26,7 +26,7 @@ import StoreKit import Foundation -class InAppReceiptRefreshRequest: NSObject, SKRequestDelegate { +class InAppReceiptRefreshRequest: NSObject, SKRequestDelegate, InAppRequest { enum ResultType { case success @@ -60,6 +60,10 @@ class InAppReceiptRefreshRequest: NSObject, SKRequestDelegate { self.refreshReceiptRequest.start() } + func cancel() { + self.refreshReceiptRequest.cancel() + } + func requestDidFinish(_ request: SKRequest) { /*if let resoreRequest = request as? SKReceiptRefreshRequest { let receiptProperties = resoreRequest.receiptProperties ?? [:] From 4db2a6d46b05022604b7e6051f01ec180e377f61 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Fri, 10 Apr 2020 12:16:06 +0200 Subject: [PATCH 3/9] Return requests from InAppReceiptVerificator --- SwiftyStoreKit/InAppReceiptVerificator.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SwiftyStoreKit/InAppReceiptVerificator.swift b/SwiftyStoreKit/InAppReceiptVerificator.swift index d49ba5dd..b9d0c808 100644 --- a/SwiftyStoreKit/InAppReceiptVerificator.swift +++ b/SwiftyStoreKit/InAppReceiptVerificator.swift @@ -49,10 +49,11 @@ class InAppReceiptVerificator: NSObject { * - Parameter refresh: closure to perform receipt refresh (this is made explicit for testability) * - Parameter completion: handler for result */ + @discardableResult public func verifyReceipt(using validator: ReceiptValidator, forceRefresh: Bool, refresh: InAppReceiptRefreshRequest.ReceiptRefresh = InAppReceiptRefreshRequest.refresh, - completion: @escaping (VerifyReceiptResult) -> Void) { + completion: @escaping (VerifyReceiptResult) -> Void) -> InAppRequest? { fetchReceipt(forceRefresh: forceRefresh, refresh: refresh) { result in switch result { @@ -72,12 +73,14 @@ class InAppReceiptVerificator: NSObject { * - Parameter refresh: closure to perform receipt refresh (this is made explicit for testability) * - Parameter completion: handler for result */ + @discardableResult public func fetchReceipt(forceRefresh: Bool, refresh: InAppReceiptRefreshRequest.ReceiptRefresh = InAppReceiptRefreshRequest.refresh, - completion: @escaping (FetchReceiptResult) -> Void) { + completion: @escaping (FetchReceiptResult) -> Void) -> InAppRequest? { if let receiptData = appStoreReceiptData, forceRefresh == false { completion(.success(receiptData: receiptData)) + return nil } else { receiptRefreshRequest = refresh(nil) { result in @@ -95,6 +98,7 @@ class InAppReceiptVerificator: NSObject { completion(.error(error: .networkError(error: e))) } } + return receiptRefreshRequest } } From 913e8c6486fe3d2db00d90c5deaf5add7eb910e8 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Fri, 10 Apr 2020 12:35:08 +0200 Subject: [PATCH 4/9] Return request in ProductsInfoController --- SwiftyStoreKit/ProductsInfoController.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SwiftyStoreKit/ProductsInfoController.swift b/SwiftyStoreKit/ProductsInfoController.swift index cb5bff48..fed38db4 100644 --- a/SwiftyStoreKit/ProductsInfoController.swift +++ b/SwiftyStoreKit/ProductsInfoController.swift @@ -51,7 +51,7 @@ class ProductsInfoController: NSObject { // As we can have multiple inflight requests, we store them in a dictionary by product ids private var inflightRequests: [Set: InAppProductQuery] = [:] - func retrieveProductsInfo(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) { + func retrieveProductsInfo(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) -> InAppProductRequest { if inflightRequests[productIds] == nil { let request = inAppProductRequestBuilder.request(productIds: productIds) { results in @@ -68,8 +68,10 @@ class ProductsInfoController: NSObject { } inflightRequests[productIds] = InAppProductQuery(request: request, completionHandlers: [completion]) request.start() + return request } else { inflightRequests[productIds]!.completionHandlers.append(completion) + return inflightRequests[productIds]!.request } } } From 72759e44bf1993112a56c6e9e48860d9500c941e Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Fri, 10 Apr 2020 12:37:51 +0200 Subject: [PATCH 5/9] Fixed build errors --- SwiftyStoreKit/SwiftyStoreKit.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/SwiftyStoreKit/SwiftyStoreKit.swift b/SwiftyStoreKit/SwiftyStoreKit.swift index 37ed6933..b5e67137 100644 --- a/SwiftyStoreKit/SwiftyStoreKit.swift +++ b/SwiftyStoreKit/SwiftyStoreKit.swift @@ -42,11 +42,11 @@ public class SwiftyStoreKit { } // MARK: private methods - fileprivate func retrieveProductsInfo(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) { + fileprivate func retrieveProductsInfo(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) -> InAppProductRequest { return productsInfoController.retrieveProductsInfo(productIds, completion: completion) } - fileprivate func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", simulatesAskToBuyInSandbox: Bool = false, completion: @escaping ( PurchaseResult) -> Void) { + fileprivate func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", simulatesAskToBuyInSandbox: Bool = false, completion: @escaping ( PurchaseResult) -> Void) -> InAppProductRequest { retrieveProductsInfo(Set([productId])) { result -> Void in if let product = result.retrievedProducts.first { @@ -143,7 +143,8 @@ extension SwiftyStoreKit { * - Parameter productIds: The set of product identifiers to retrieve corresponding products for * - Parameter completion: handler for result */ - public class func retrieveProductsInfo(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) { + @discardableResult + public class func retrieveProductsInfo(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) -> InAppRequest { return sharedInstance.retrieveProductsInfo(productIds, completion: completion) } @@ -156,7 +157,8 @@ extension SwiftyStoreKit { * - Parameter applicationUsername: an opaque identifier for the user’s account on your system * - Parameter completion: handler for result */ - public class func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", simulatesAskToBuyInSandbox: Bool = false, completion: @escaping (PurchaseResult) -> Void) { + @discardableResult + public class func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", simulatesAskToBuyInSandbox: Bool = false, completion: @escaping (PurchaseResult) -> Void) -> InAppRequest { sharedInstance.purchaseProduct(productId, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, simulatesAskToBuyInSandbox: simulatesAskToBuyInSandbox, completion: completion) } @@ -255,7 +257,8 @@ extension SwiftyStoreKit { * - Parameter forceRefresh: If true, refreshes the receipt even if one already exists. * - Parameter completion: handler for result */ - public class func verifyReceipt(using validator: ReceiptValidator, forceRefresh: Bool = false, completion: @escaping (VerifyReceiptResult) -> Void) { + @discardableResult + public class func verifyReceipt(using validator: ReceiptValidator, forceRefresh: Bool = false, completion: @escaping (VerifyReceiptResult) -> Void) -> InAppRequest? { sharedInstance.receiptVerificator.verifyReceipt(using: validator, forceRefresh: forceRefresh, completion: completion) } @@ -265,7 +268,8 @@ extension SwiftyStoreKit { * - Parameter forceRefresh: If true, refreshes the receipt even if one already exists. * - Parameter completion: handler for result */ - public class func fetchReceipt(forceRefresh: Bool, completion: @escaping (FetchReceiptResult) -> Void) { + @discardableResult + public class func fetchReceipt(forceRefresh: Bool, completion: @escaping (FetchReceiptResult) -> Void) -> InAppRequest? { sharedInstance.receiptVerificator.fetchReceipt(forceRefresh: forceRefresh, completion: completion) } From 5e0fa8a5feea1c8ae2f47cf5d29d6055ac7d7cf0 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Fri, 10 Apr 2020 12:47:23 +0200 Subject: [PATCH 6/9] Fixed build errors in Xcode 10.2 --- SwiftyStoreKit/SwiftyStoreKit.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SwiftyStoreKit/SwiftyStoreKit.swift b/SwiftyStoreKit/SwiftyStoreKit.swift index b5e67137..30721886 100644 --- a/SwiftyStoreKit/SwiftyStoreKit.swift +++ b/SwiftyStoreKit/SwiftyStoreKit.swift @@ -48,7 +48,7 @@ public class SwiftyStoreKit { fileprivate func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", simulatesAskToBuyInSandbox: Bool = false, completion: @escaping ( PurchaseResult) -> Void) -> InAppProductRequest { - retrieveProductsInfo(Set([productId])) { result -> Void in + return retrieveProductsInfo(Set([productId])) { result -> Void in if let product = result.retrievedProducts.first { self.purchase(product: product, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, simulatesAskToBuyInSandbox: simulatesAskToBuyInSandbox, completion: completion) } else if let error = result.error { @@ -160,7 +160,7 @@ extension SwiftyStoreKit { @discardableResult public class func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", simulatesAskToBuyInSandbox: Bool = false, completion: @escaping (PurchaseResult) -> Void) -> InAppRequest { - sharedInstance.purchaseProduct(productId, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, simulatesAskToBuyInSandbox: simulatesAskToBuyInSandbox, completion: completion) + return sharedInstance.purchaseProduct(productId, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, simulatesAskToBuyInSandbox: simulatesAskToBuyInSandbox, completion: completion) } /** @@ -260,7 +260,7 @@ extension SwiftyStoreKit { @discardableResult public class func verifyReceipt(using validator: ReceiptValidator, forceRefresh: Bool = false, completion: @escaping (VerifyReceiptResult) -> Void) -> InAppRequest? { - sharedInstance.receiptVerificator.verifyReceipt(using: validator, forceRefresh: forceRefresh, completion: completion) + return sharedInstance.receiptVerificator.verifyReceipt(using: validator, forceRefresh: forceRefresh, completion: completion) } /** @@ -271,7 +271,7 @@ extension SwiftyStoreKit { @discardableResult public class func fetchReceipt(forceRefresh: Bool, completion: @escaping (FetchReceiptResult) -> Void) -> InAppRequest? { - sharedInstance.receiptVerificator.fetchReceipt(forceRefresh: forceRefresh, completion: completion) + return sharedInstance.receiptVerificator.fetchReceipt(forceRefresh: forceRefresh, completion: completion) } /** From 29d8714f146a2b885c33ed5a0c4ac404edadc678 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Fri, 10 Apr 2020 12:49:19 +0200 Subject: [PATCH 7/9] Fixes for Xcode 10.2 --- SwiftyStoreKit/InAppReceiptVerificator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SwiftyStoreKit/InAppReceiptVerificator.swift b/SwiftyStoreKit/InAppReceiptVerificator.swift index b9d0c808..c86ac1c8 100644 --- a/SwiftyStoreKit/InAppReceiptVerificator.swift +++ b/SwiftyStoreKit/InAppReceiptVerificator.swift @@ -55,7 +55,7 @@ class InAppReceiptVerificator: NSObject { refresh: InAppReceiptRefreshRequest.ReceiptRefresh = InAppReceiptRefreshRequest.refresh, completion: @escaping (VerifyReceiptResult) -> Void) -> InAppRequest? { - fetchReceipt(forceRefresh: forceRefresh, refresh: refresh) { result in + return fetchReceipt(forceRefresh: forceRefresh, refresh: refresh) { result in switch result { case .success(let receiptData): self.verify(receiptData: receiptData, using: validator, completion: completion) From 0d51327d01a9e4e67a7c5fcf7c6f7e8a2217a4c9 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Fri, 10 Apr 2020 15:37:04 +0200 Subject: [PATCH 8/9] Added missing @discardableResult --- SwiftyStoreKit/ProductsInfoController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/SwiftyStoreKit/ProductsInfoController.swift b/SwiftyStoreKit/ProductsInfoController.swift index fed38db4..591e26a1 100644 --- a/SwiftyStoreKit/ProductsInfoController.swift +++ b/SwiftyStoreKit/ProductsInfoController.swift @@ -51,6 +51,7 @@ class ProductsInfoController: NSObject { // As we can have multiple inflight requests, we store them in a dictionary by product ids private var inflightRequests: [Set: InAppProductQuery] = [:] + @discardableResult func retrieveProductsInfo(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) -> InAppProductRequest { if inflightRequests[productIds] == nil { From 2d0f23d31d08bd57e647c0490cf83f40d31cb11b Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Fri, 10 Apr 2020 20:19:15 +0200 Subject: [PATCH 9/9] More InAppReceiptVerificatorTests tests --- .../InAppReceiptVerificatorTests.swift | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/SwiftyStoreKitTests/InAppReceiptVerificatorTests.swift b/SwiftyStoreKitTests/InAppReceiptVerificatorTests.swift index 967fd5ed..82f2fb1d 100644 --- a/SwiftyStoreKitTests/InAppReceiptVerificatorTests.swift +++ b/SwiftyStoreKitTests/InAppReceiptVerificatorTests.swift @@ -76,7 +76,7 @@ class InAppReceiptVerificatorTests: XCTestCase { let verificator = InAppReceiptVerificator(appStoreReceiptURL: nil) var refreshCalled = false - verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in + let request = verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in refreshCalled = true return TestInAppReceiptRefreshRequest(receiptProperties: properties, callback: callback) @@ -84,6 +84,7 @@ class InAppReceiptVerificatorTests: XCTestCase { }, completion: { _ in }) + XCTAssertNotNil(request) XCTAssertTrue(refreshCalled) } @@ -95,7 +96,7 @@ class InAppReceiptVerificatorTests: XCTestCase { let verificator = InAppReceiptVerificator(appStoreReceiptURL: testReceiptURL) var refreshCalled = false - verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in + let request = verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in refreshCalled = true return TestInAppReceiptRefreshRequest(receiptProperties: properties, callback: callback) @@ -103,6 +104,7 @@ class InAppReceiptVerificatorTests: XCTestCase { }, completion: { _ in }) + XCTAssertNotNil(request) XCTAssertTrue(refreshCalled) } @@ -115,7 +117,7 @@ class InAppReceiptVerificatorTests: XCTestCase { let verificator = InAppReceiptVerificator(appStoreReceiptURL: testReceiptURL) var refreshCalled = false - verificator.verifyReceipt(using: validator, forceRefresh: true, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in + let request = verificator.verifyReceipt(using: validator, forceRefresh: true, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in refreshCalled = true return TestInAppReceiptRefreshRequest(receiptProperties: properties, callback: callback) @@ -123,6 +125,7 @@ class InAppReceiptVerificatorTests: XCTestCase { }, completion: { _ in }) + XCTAssertNotNil(request) XCTAssertTrue(refreshCalled) } @@ -132,7 +135,7 @@ class InAppReceiptVerificatorTests: XCTestCase { let verificator = InAppReceiptVerificator(appStoreReceiptURL: nil) let refreshError = NSError(domain: "", code: 0, userInfo: nil) - verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in + let request = verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in callback(.error(e: refreshError)) return TestInAppReceiptRefreshRequest(receiptProperties: properties, callback: callback) @@ -141,6 +144,7 @@ class InAppReceiptVerificatorTests: XCTestCase { XCTAssertEqual(result, VerifyReceiptResult.error(error: ReceiptError.networkError(error: refreshError))) }) + XCTAssertNotNil(request) } func testVerifyReceipt_when_appStoreReceiptURLIsNil_refreshCallbackSuccess_receiptDataNotWritten_then_errorNoReceiptData_validateNotCalled() { @@ -148,7 +152,7 @@ class InAppReceiptVerificatorTests: XCTestCase { let validator = TestReceiptValidator() let verificator = InAppReceiptVerificator(appStoreReceiptURL: nil) - verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in + let request = verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in callback(.success) return TestInAppReceiptRefreshRequest(receiptProperties: properties, callback: callback) @@ -157,6 +161,7 @@ class InAppReceiptVerificatorTests: XCTestCase { XCTAssertEqual(result, VerifyReceiptResult.error(error: ReceiptError.noReceiptData)) }) + XCTAssertNotNil(request) XCTAssertFalse(validator.validateCalled) } @@ -167,7 +172,7 @@ class InAppReceiptVerificatorTests: XCTestCase { let validator = TestReceiptValidator() let verificator = InAppReceiptVerificator(appStoreReceiptURL: nil) - verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in + let request = verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in writeReceiptData(to: testReceiptURL) callback(.success) @@ -177,6 +182,7 @@ class InAppReceiptVerificatorTests: XCTestCase { XCTAssertEqual(result, VerifyReceiptResult.error(error: ReceiptError.noReceiptData)) }) + XCTAssertNotNil(request) XCTAssertFalse(validator.validateCalled) removeReceiptData(at: testReceiptURL) } @@ -188,7 +194,7 @@ class InAppReceiptVerificatorTests: XCTestCase { let validator = TestReceiptValidator() let verificator = InAppReceiptVerificator(appStoreReceiptURL: testReceiptURL) - verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in + let request = verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in writeReceiptData(to: testReceiptURL) callback(.success) @@ -197,6 +203,7 @@ class InAppReceiptVerificatorTests: XCTestCase { }, completion: { _ in }) + XCTAssertNil(request) XCTAssertTrue(validator.validateCalled) removeReceiptData(at: testReceiptURL) } @@ -210,7 +217,7 @@ class InAppReceiptVerificatorTests: XCTestCase { let validator = TestReceiptValidator() let verificator = InAppReceiptVerificator(appStoreReceiptURL: testReceiptURL) - verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in + let request = verificator.verifyReceipt(using: validator, forceRefresh: false, refresh: { (properties, callback) -> InAppReceiptRefreshRequest in XCTFail("refresh should not be called if we already have a receipt") return TestInAppReceiptRefreshRequest(receiptProperties: properties, callback: callback) @@ -218,6 +225,7 @@ class InAppReceiptVerificatorTests: XCTestCase { }, completion: { _ in }) + XCTAssertNil(request) XCTAssertTrue(validator.validateCalled) removeReceiptData(at: testReceiptURL) }