Skip to content

Commit

Permalink
Merge pull request #546 from checkout/release/4.3.7
Browse files Browse the repository at this point in the history
Merge release/4.3.7 into main
  • Loading branch information
okhan-okbay-cko authored Aug 6, 2024
2 parents b4fd428 + 81e5dc1 commit 06427ff
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 28 deletions.
17 changes: 17 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.

#### 4.x Releases


## [4.3.7](https://github.com/checkout/frames-ios/releases/tag/4.3.7)

Released on 2024-08-06

Updates:

- Removing the need to await for Risk SDK completion

## [4.3.6](https://github.com/checkout/frames-ios/releases/tag/4.3.6)

Released on 2024-05-30

Updates:

- Fixing a crash within the Risk SDK implementation.

## [4.3.5](https://github.com/checkout/frames-ios/releases/tag/4.3.5)

Released on 2024-05-01
Expand Down
2 changes: 1 addition & 1 deletion Checkout.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'Checkout'
s.version = '4.3.6'
s.version = '4.3.7'
s.summary = 'Checkout SDK for iOS'

s.description = <<-DESC
Expand Down
3 changes: 1 addition & 2 deletions Checkout/Samples/CocoapodsSample/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@ target 'CheckoutCocoapodsSample' do
use_frameworks!

# Pods for CheckoutSDKCocoapodsSample
pod 'Checkout', '4.3.6'

pod 'Checkout', '4.3.7'
end
9 changes: 7 additions & 2 deletions Checkout/Source/Logging/CheckoutLogEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum CheckoutLogEvent: Equatable {
case cvvRequested(SecurityCodeTokenRequestData)
case cvvResponse(SecurityCodeTokenRequestData, TokenResponseData)
case riskSDKCompletion
case riskSDKTimeOut

func event(date: Date) -> Event {
Event(
Expand Down Expand Up @@ -58,6 +59,8 @@ enum CheckoutLogEvent: Equatable {
return "card_validator_cvv"
case .riskSDKCompletion:
return "risk_sdk_completion"
case .riskSDKTimeOut:
return "risk_sdk_time_out"
}
}

Expand All @@ -70,7 +73,8 @@ enum CheckoutLogEvent: Equatable {
.validateExpiryInteger,
.validateCVV,
.cvvRequested,
.riskSDKCompletion:
.riskSDKCompletion,
.riskSDKTimeOut:
return .info
case .tokenResponse(_, let tokenResponseData),
.cvvResponse(_, let tokenResponseData):
Expand All @@ -93,7 +97,8 @@ enum CheckoutLogEvent: Equatable {
.validateExpiryString,
.validateExpiryInteger,
.validateCVV,
.riskSDKCompletion:
.riskSDKCompletion,
.riskSDKTimeOut:
return [:]
case let .tokenRequested(tokenRequestData):
return [
Expand Down
63 changes: 50 additions & 13 deletions Checkout/Source/Tokenisation/CheckoutAPIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ final public class CheckoutAPIService: CheckoutAPIProtocol {
}
}

let timeoutInterval: TimeInterval = 5.0
private let taskCompletionQueue = DispatchQueue(label: "taskCompletionQueue", qos: .userInitiated)
private var isTaskCompleted = false

private func createToken(requestParameters: NetworkManager.RequestParameters,
paymentType: TokenRequest.TokenType,
completion: @escaping (Result<TokenDetails, TokenisationError.TokenRequest>) -> Void) {
Expand All @@ -164,19 +168,10 @@ final public class CheckoutAPIService: CheckoutAPIProtocol {
return
}

self.riskSDK.configure { configurationResult in
switch configurationResult {
case .failure:
completion(.success(tokenDetails))
logManager.resetCorrelationID()
case .success():
self.riskSDK.publishData(cardToken: tokenDetails.token) { _ in
logManager.queue(event: .riskSDKCompletion)
completion(.success(tokenDetails))
logManager.resetCorrelationID()
}
}
}
self.callRiskSDK(tokenDetails: tokenDetails) {
completion(.success(tokenDetails))
}

case .errorResponse(let errorResponse):
completion(.failure(.serverError(errorResponse)))
logManager.resetCorrelationID()
Expand All @@ -187,6 +182,48 @@ final public class CheckoutAPIService: CheckoutAPIProtocol {
}
}

private func callRiskSDK(tokenDetails: TokenDetails,
completion: @escaping () -> Void) {

/* Risk SDK calls can be finalised in 3 different ways
1. When Risk SDK's configure(...) function completed successfully and publishData(...) completed successfully or not
2. When Risk SDK's configure(...) function completed with failure
3. When Risk SDK's configure(...) or publishData(...) functions hang and don't call their completion blocks.
In this case, we wait for `self.timeoutInterval` amount of time and call the completion block anyway.

All these operations are done synchronously to avoid the completion closure getting called multiple times.
*/

let finaliseRiskSDKCalls = {
self.taskCompletionQueue.sync {
if !self.isTaskCompleted {
self.isTaskCompleted = true
completion()
}
}
}

DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + timeoutInterval) {
finaliseRiskSDKCalls()
self.logManager.queue(event: .riskSDKTimeOut)
}

self.riskSDK.configure { [weak self] configurationResult in
guard let self else { return }
switch configurationResult {
case .failure:
finaliseRiskSDKCalls()
self.logManager.resetCorrelationID()
case .success():
self.riskSDK.publishData(cardToken: tokenDetails.token) { _ in
self.logManager.queue(event: .riskSDKCompletion)
finaliseRiskSDKCalls()
self.logManager.resetCorrelationID()
}
}
}
}

private func logTokenResponse(tokenResponseResult: NetworkRequestResult<TokenResponse, TokenisationError.ServerError>,
paymentType: TokenRequest.TokenType,
httpURLResponse: HTTPURLResponse?) {
Expand Down
2 changes: 1 addition & 1 deletion Checkout/Source/Validation/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public enum Constants {
}

enum Product {
static let version = "4.3.6"
static let version = "4.3.7"
static let name = "checkout-ios-sdk"
static let userAgent = "checkout-sdk-ios/\(version)"
}
Expand Down
15 changes: 12 additions & 3 deletions CheckoutTests/Stubs/StubRisk.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,24 @@ class StubRisk: RiskProtocol {

var configureCalledCount = 0
var publishDataCalledCount = 0


// If set to false, Risk SDK will hang and not call the completion block for that specific function.
// It will mimic the behaviour of a bug we have. We need to call Frames's completion block after the defined timeout period in that case.
var shouldConfigureFunctionCallCompletion: Bool = true
var shouldPublishFunctionCallCompletion: Bool = true

func configure(completion: @escaping (Result<Void, RiskError.Configuration>) -> Void) {
configureCalledCount += 1
completion(.success(()))
if shouldConfigureFunctionCallCompletion {
completion(.success(()))
}
}

func publishData (cardToken: String? = nil, completion: @escaping (Result<PublishRiskData, RiskError.Publish>) -> Void) {
publishDataCalledCount += 1
completion(.success(PublishRiskData(deviceSessionId: "dsid_testDeviceSessionId")))
if shouldPublishFunctionCallCompletion {
completion(.success(PublishRiskData(deviceSessionId: "dsid_testDeviceSessionId")))
}
}
}

64 changes: 64 additions & 0 deletions CheckoutTests/Tokenisation/CheckoutAPIServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -354,3 +354,67 @@ extension CheckoutAPIServiceTests {
}
}
}

// Risk SDK Timeout Recovery Tests
extension CheckoutAPIServiceTests {
func testWhenRiskSDKCallsCompletionThenFramesReturnsSuccess() {
let card = StubProvider.createCard()
let tokenRequest = StubProvider.createTokenRequest()
let requestParameters = StubProvider.createRequestParameters()
let tokenResponse = StubProvider.createTokenResponse()
let tokenDetails = StubProvider.createTokenDetails()

stubTokenRequestFactory.createToReturn = .success(tokenRequest)
stubRequestFactory.createToReturn = .success(requestParameters)
stubTokenDetailsFactory.createToReturn = tokenDetails

var result: Result<TokenDetails, TokenisationError.TokenRequest>?
subject.createToken(.card(card)) { result = $0 }
stubRequestExecutor.executeCalledWithCompletion?(.response(tokenResponse), HTTPURLResponse())

XCTAssertEqual(stubRisk.configureCalledCount, 1)
XCTAssertEqual(stubRisk.publishDataCalledCount, 1)
XCTAssertEqual(result, .success(tokenDetails))
}

func testWhenRiskSDKConfigureHangsThenFramesSDKCancelsWaitingRiskSDKAndCallsCompletionBlockAnywayAfterTimeout() {
stubRisk.shouldConfigureFunctionCallCompletion = false // Configure function will hang forever before it calls its completion closure
verifyRiskSDKTimeoutRecovery(timeoutAddition: 1, expectedConfigureCallCount: 1, expectedPublishDataCallCount: 0)
}

func testWhenRiskSDKPublishHangsThenFramesSDKCancelsWaitingRiskSDKAndCallsCompletionBlockAnywayAfterTimeout() {
stubRisk.shouldPublishFunctionCallCompletion = false // Publish data function will hang forever before it calls its completion closure
verifyRiskSDKTimeoutRecovery(timeoutAddition: 1, expectedConfigureCallCount: 1, expectedPublishDataCallCount: 1)
}

func verifyRiskSDKTimeoutRecovery(timeoutAddition: Double,
expectedConfigureCallCount: Int,
expectedPublishDataCallCount: Int,
file: StaticString = #file,
line: UInt = #line) {
let card = StubProvider.createCard()
let tokenRequest = StubProvider.createTokenRequest()
let tokenResponse = StubProvider.createTokenResponse()
let requestParameters = StubProvider.createRequestParameters()
let tokenDetails = StubProvider.createTokenDetails()

stubTokenRequestFactory.createToReturn = .success(tokenRequest)
stubRequestFactory.createToReturn = .success(requestParameters)
stubTokenDetailsFactory.createToReturn = tokenDetails

let expectation = self.expectation(description: "Frames will time out awaiting Risk SDK result")

var _: Result<TokenDetails, TokenisationError.TokenRequest>?
subject.createToken(.card(card)) {

XCTAssertEqual(self.stubRisk.configureCalledCount, expectedConfigureCallCount, file: file, line: line)
XCTAssertEqual(self.stubRisk.publishDataCalledCount, expectedPublishDataCallCount, file: file, line: line)
XCTAssertEqual($0, .success(tokenDetails), file: file, line: line)

expectation.fulfill()
}
stubRequestExecutor.executeCalledWithCompletion?(.response(tokenResponse), HTTPURLResponse())

waitForExpectations(timeout: subject.timeoutInterval + timeoutAddition)
}
}
4 changes: 2 additions & 2 deletions Frames.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Frames"
s.version = "4.3.6"
s.version = "4.3.7"
s.summary = "Checkout API Client, Payment Form UI and Utilities in Swift"
s.description = <<-DESC
Checkout API Client and Payment Form Utilities in Swift.
Expand All @@ -21,6 +21,6 @@ Pod::Spec.new do |s|

s.dependency 'PhoneNumberKit'
s.dependency 'CheckoutEventLoggerKit', '~> 1.2.4'
s.dependency 'Checkout', '4.3.6'
s.dependency 'Checkout', '4.3.7'

end
2 changes: 1 addition & 1 deletion Source/Core/Constants/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
enum Constants {

static let productName = "frames-ios-sdk"
static let version = "4.3.6"
static let version = "4.3.7"
static let userAgent = "checkout-sdk-frames-ios/\(version)"

enum Logging {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1239,7 +1239,7 @@
repositoryURL = "https://github.com/checkout/frames-ios";
requirement = {
kind = exactVersion;
version = 4.3.6;
version = 4.3.7;
};
};
16C3F83E2A7927ED00690639 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = {
Expand Down
3 changes: 1 addition & 2 deletions iOS Example Frame/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ target 'iOS Example Frame' do
use_frameworks!

# Pods for iOS Example Custom
pod 'Frames', '4.3.6'

pod 'Frames', '4.3.7'
end

post_install do |installer|
Expand Down

0 comments on commit 06427ff

Please sign in to comment.