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

Merge release/4.3.7 into main #546

Merged
merged 4 commits into from
Aug 6, 2024
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
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
Loading