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 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 ?? [:] diff --git a/SwiftyStoreKit/InAppReceiptVerificator.swift b/SwiftyStoreKit/InAppReceiptVerificator.swift index d49ba5dd..c86ac1c8 100644 --- a/SwiftyStoreKit/InAppReceiptVerificator.swift +++ b/SwiftyStoreKit/InAppReceiptVerificator.swift @@ -49,12 +49,13 @@ 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 + return fetchReceipt(forceRefresh: forceRefresh, refresh: refresh) { result in switch result { case .success(let receiptData): self.verify(receiptData: receiptData, using: validator, completion: completion) @@ -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 } } diff --git a/SwiftyStoreKit/ProductsInfoController.swift b/SwiftyStoreKit/ProductsInfoController.swift index cb5bff48..591e26a1 100644 --- a/SwiftyStoreKit/ProductsInfoController.swift +++ b/SwiftyStoreKit/ProductsInfoController.swift @@ -51,7 +51,8 @@ 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) { + @discardableResult + func retrieveProductsInfo(_ productIds: Set, completion: @escaping (RetrieveResults) -> Void) -> InAppProductRequest { if inflightRequests[productIds] == nil { let request = inAppProductRequestBuilder.request(productIds: productIds) { results in @@ -68,8 +69,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 } } } diff --git a/SwiftyStoreKit/SwiftyStoreKit.swift b/SwiftyStoreKit/SwiftyStoreKit.swift index 37ed6933..30721886 100644 --- a/SwiftyStoreKit/SwiftyStoreKit.swift +++ b/SwiftyStoreKit/SwiftyStoreKit.swift @@ -42,13 +42,13 @@ 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 + 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 { @@ -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,9 +157,10 @@ 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) + return sharedInstance.purchaseProduct(productId, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, simulatesAskToBuyInSandbox: simulatesAskToBuyInSandbox, completion: completion) } /** @@ -255,9 +257,10 @@ 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) + return sharedInstance.receiptVerificator.verifyReceipt(using: validator, forceRefresh: forceRefresh, completion: completion) } /** @@ -265,9 +268,10 @@ 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) + return sharedInstance.receiptVerificator.fetchReceipt(forceRefresh: forceRefresh, completion: completion) } /** 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) }