Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancellable requests #538

Merged
merged 9 commits into from
May 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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