Skip to content

Commit

Permalink
Merge pull request #538 from RomanPodymov/develop
Browse files Browse the repository at this point in the history
Cancellable requests
  • Loading branch information
Sam-Spencer authored May 29, 2020
2 parents 8559919 + 2d0f23d commit cca046f
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 24 deletions.
4 changes: 3 additions & 1 deletion SwiftyStoreKit/InAppProductQueryRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion SwiftyStoreKit/InAppReceiptRefreshRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import StoreKit
import Foundation

class InAppReceiptRefreshRequest: NSObject, SKRequestDelegate {
class InAppReceiptRefreshRequest: NSObject, SKRequestDelegate, InAppRequest {

enum ResultType {
case success
Expand Down Expand Up @@ -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 ?? [:]
Expand Down
10 changes: 7 additions & 3 deletions SwiftyStoreKit/InAppReceiptVerificator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -95,6 +98,7 @@ class InAppReceiptVerificator: NSObject {
completion(.error(error: .networkError(error: e)))
}
}
return receiptRefreshRequest
}
}

Expand Down
5 changes: 4 additions & 1 deletion SwiftyStoreKit/ProductsInfoController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>: InAppProductQuery] = [:]

func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) {
@discardableResult
func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) -> InAppProductRequest {

if inflightRequests[productIds] == nil {
let request = inAppProductRequestBuilder.request(productIds: productIds) { results in
Expand All @@ -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
}
}
}
24 changes: 14 additions & 10 deletions SwiftyStoreKit/SwiftyStoreKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,13 @@ public class SwiftyStoreKit {
}

// MARK: private methods
fileprivate func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) {
fileprivate func retrieveProductsInfo(_ productIds: Set<String>, 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 {
Expand Down Expand Up @@ -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<String>, completion: @escaping (RetrieveResults) -> Void) {
@discardableResult
public class func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) -> InAppRequest {

return sharedInstance.retrieveProductsInfo(productIds, completion: completion)
}
Expand All @@ -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)
}

/**
Expand Down Expand Up @@ -255,19 +257,21 @@ 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)
}

/**
* Fetch application receipt
* - 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)
}

/**
Expand Down
24 changes: 16 additions & 8 deletions SwiftyStoreKitTests/InAppReceiptVerificatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,15 @@ 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)

}, completion: { _ in

})
XCTAssertNotNil(request)
XCTAssertTrue(refreshCalled)
}

Expand All @@ -95,14 +96,15 @@ 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)

}, completion: { _ in

})
XCTAssertNotNil(request)
XCTAssertTrue(refreshCalled)
}

Expand All @@ -115,14 +117,15 @@ 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)

}, completion: { _ in

})
XCTAssertNotNil(request)
XCTAssertTrue(refreshCalled)
}

Expand All @@ -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)
Expand All @@ -141,14 +144,15 @@ class InAppReceiptVerificatorTests: XCTestCase {

XCTAssertEqual(result, VerifyReceiptResult.error(error: ReceiptError.networkError(error: refreshError)))
})
XCTAssertNotNil(request)
}

func testVerifyReceipt_when_appStoreReceiptURLIsNil_refreshCallbackSuccess_receiptDataNotWritten_then_errorNoReceiptData_validateNotCalled() {

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)
Expand All @@ -157,6 +161,7 @@ class InAppReceiptVerificatorTests: XCTestCase {

XCTAssertEqual(result, VerifyReceiptResult.error(error: ReceiptError.noReceiptData))
})
XCTAssertNotNil(request)
XCTAssertFalse(validator.validateCalled)
}

Expand All @@ -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)
Expand All @@ -177,6 +182,7 @@ class InAppReceiptVerificatorTests: XCTestCase {

XCTAssertEqual(result, VerifyReceiptResult.error(error: ReceiptError.noReceiptData))
})
XCTAssertNotNil(request)
XCTAssertFalse(validator.validateCalled)
removeReceiptData(at: testReceiptURL)
}
Expand All @@ -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)
Expand All @@ -197,6 +203,7 @@ class InAppReceiptVerificatorTests: XCTestCase {
}, completion: { _ in

})
XCTAssertNil(request)
XCTAssertTrue(validator.validateCalled)
removeReceiptData(at: testReceiptURL)
}
Expand All @@ -210,14 +217,15 @@ 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)

}, completion: { _ in

})
XCTAssertNil(request)
XCTAssertTrue(validator.validateCalled)
removeReceiptData(at: testReceiptURL)
}
Expand Down

0 comments on commit cca046f

Please sign in to comment.