Skip to content

Commit 06427ff

Browse files
Merge pull request #546 from checkout/release/4.3.7
Merge release/4.3.7 into main
2 parents b4fd428 + 81e5dc1 commit 06427ff

File tree

12 files changed

+158
-28
lines changed

12 files changed

+158
-28
lines changed

.github/CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
55

66
#### 4.x Releases
77

8+
9+
## [4.3.7](https://github.com/checkout/frames-ios/releases/tag/4.3.7)
10+
11+
Released on 2024-08-06
12+
13+
Updates:
14+
15+
- Removing the need to await for Risk SDK completion
16+
17+
## [4.3.6](https://github.com/checkout/frames-ios/releases/tag/4.3.6)
18+
19+
Released on 2024-05-30
20+
21+
Updates:
22+
23+
- Fixing a crash within the Risk SDK implementation.
24+
825
## [4.3.5](https://github.com/checkout/frames-ios/releases/tag/4.3.5)
926

1027
Released on 2024-05-01

Checkout.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'Checkout'
3-
s.version = '4.3.6'
3+
s.version = '4.3.7'
44
s.summary = 'Checkout SDK for iOS'
55

66
s.description = <<-DESC

Checkout/Samples/CocoapodsSample/Podfile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,5 @@ target 'CheckoutCocoapodsSample' do
55
use_frameworks!
66

77
# Pods for CheckoutSDKCocoapodsSample
8-
pod 'Checkout', '4.3.6'
9-
8+
pod 'Checkout', '4.3.7'
109
end

Checkout/Source/Logging/CheckoutLogEvent.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ enum CheckoutLogEvent: Equatable {
1919
case cvvRequested(SecurityCodeTokenRequestData)
2020
case cvvResponse(SecurityCodeTokenRequestData, TokenResponseData)
2121
case riskSDKCompletion
22+
case riskSDKTimeOut
2223

2324
func event(date: Date) -> Event {
2425
Event(
@@ -58,6 +59,8 @@ enum CheckoutLogEvent: Equatable {
5859
return "card_validator_cvv"
5960
case .riskSDKCompletion:
6061
return "risk_sdk_completion"
62+
case .riskSDKTimeOut:
63+
return "risk_sdk_time_out"
6164
}
6265
}
6366

@@ -70,7 +73,8 @@ enum CheckoutLogEvent: Equatable {
7073
.validateExpiryInteger,
7174
.validateCVV,
7275
.cvvRequested,
73-
.riskSDKCompletion:
76+
.riskSDKCompletion,
77+
.riskSDKTimeOut:
7478
return .info
7579
case .tokenResponse(_, let tokenResponseData),
7680
.cvvResponse(_, let tokenResponseData):
@@ -93,7 +97,8 @@ enum CheckoutLogEvent: Equatable {
9397
.validateExpiryString,
9498
.validateExpiryInteger,
9599
.validateCVV,
96-
.riskSDKCompletion:
100+
.riskSDKCompletion,
101+
.riskSDKTimeOut:
97102
return [:]
98103
case let .tokenRequested(tokenRequestData):
99104
return [

Checkout/Source/Tokenisation/CheckoutAPIService.swift

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ final public class CheckoutAPIService: CheckoutAPIProtocol {
145145
}
146146
}
147147

148+
let timeoutInterval: TimeInterval = 5.0
149+
private let taskCompletionQueue = DispatchQueue(label: "taskCompletionQueue", qos: .userInitiated)
150+
private var isTaskCompleted = false
151+
148152
private func createToken(requestParameters: NetworkManager.RequestParameters,
149153
paymentType: TokenRequest.TokenType,
150154
completion: @escaping (Result<TokenDetails, TokenisationError.TokenRequest>) -> Void) {
@@ -164,19 +168,10 @@ final public class CheckoutAPIService: CheckoutAPIProtocol {
164168
return
165169
}
166170

167-
self.riskSDK.configure { configurationResult in
168-
switch configurationResult {
169-
case .failure:
170-
completion(.success(tokenDetails))
171-
logManager.resetCorrelationID()
172-
case .success():
173-
self.riskSDK.publishData(cardToken: tokenDetails.token) { _ in
174-
logManager.queue(event: .riskSDKCompletion)
175-
completion(.success(tokenDetails))
176-
logManager.resetCorrelationID()
177-
}
178-
}
179-
}
171+
self.callRiskSDK(tokenDetails: tokenDetails) {
172+
completion(.success(tokenDetails))
173+
}
174+
180175
case .errorResponse(let errorResponse):
181176
completion(.failure(.serverError(errorResponse)))
182177
logManager.resetCorrelationID()
@@ -187,6 +182,48 @@ final public class CheckoutAPIService: CheckoutAPIProtocol {
187182
}
188183
}
189184

185+
private func callRiskSDK(tokenDetails: TokenDetails,
186+
completion: @escaping () -> Void) {
187+
188+
/* Risk SDK calls can be finalised in 3 different ways
189+
1. When Risk SDK's configure(...) function completed successfully and publishData(...) completed successfully or not
190+
2. When Risk SDK's configure(...) function completed with failure
191+
3. When Risk SDK's configure(...) or publishData(...) functions hang and don't call their completion blocks.
192+
In this case, we wait for `self.timeoutInterval` amount of time and call the completion block anyway.
193+
194+
All these operations are done synchronously to avoid the completion closure getting called multiple times.
195+
*/
196+
197+
let finaliseRiskSDKCalls = {
198+
self.taskCompletionQueue.sync {
199+
if !self.isTaskCompleted {
200+
self.isTaskCompleted = true
201+
completion()
202+
}
203+
}
204+
}
205+
206+
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + timeoutInterval) {
207+
finaliseRiskSDKCalls()
208+
self.logManager.queue(event: .riskSDKTimeOut)
209+
}
210+
211+
self.riskSDK.configure { [weak self] configurationResult in
212+
guard let self else { return }
213+
switch configurationResult {
214+
case .failure:
215+
finaliseRiskSDKCalls()
216+
self.logManager.resetCorrelationID()
217+
case .success():
218+
self.riskSDK.publishData(cardToken: tokenDetails.token) { _ in
219+
self.logManager.queue(event: .riskSDKCompletion)
220+
finaliseRiskSDKCalls()
221+
self.logManager.resetCorrelationID()
222+
}
223+
}
224+
}
225+
}
226+
190227
private func logTokenResponse(tokenResponseResult: NetworkRequestResult<TokenResponse, TokenisationError.ServerError>,
191228
paymentType: TokenRequest.TokenType,
192229
httpURLResponse: HTTPURLResponse?) {

Checkout/Source/Validation/Constants.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public enum Constants {
2525
}
2626

2727
enum Product {
28-
static let version = "4.3.6"
28+
static let version = "4.3.7"
2929
static let name = "checkout-ios-sdk"
3030
static let userAgent = "checkout-sdk-ios/\(version)"
3131
}

CheckoutTests/Stubs/StubRisk.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,24 @@ class StubRisk: RiskProtocol {
1414

1515
var configureCalledCount = 0
1616
var publishDataCalledCount = 0
17-
17+
18+
// If set to false, Risk SDK will hang and not call the completion block for that specific function.
19+
// 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.
20+
var shouldConfigureFunctionCallCompletion: Bool = true
21+
var shouldPublishFunctionCallCompletion: Bool = true
22+
1823
func configure(completion: @escaping (Result<Void, RiskError.Configuration>) -> Void) {
1924
configureCalledCount += 1
20-
completion(.success(()))
25+
if shouldConfigureFunctionCallCompletion {
26+
completion(.success(()))
27+
}
2128
}
2229

2330
func publishData (cardToken: String? = nil, completion: @escaping (Result<PublishRiskData, RiskError.Publish>) -> Void) {
2431
publishDataCalledCount += 1
25-
completion(.success(PublishRiskData(deviceSessionId: "dsid_testDeviceSessionId")))
32+
if shouldPublishFunctionCallCompletion {
33+
completion(.success(PublishRiskData(deviceSessionId: "dsid_testDeviceSessionId")))
34+
}
2635
}
2736
}
2837

CheckoutTests/Tokenisation/CheckoutAPIServiceTests.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,3 +354,67 @@ extension CheckoutAPIServiceTests {
354354
}
355355
}
356356
}
357+
358+
// Risk SDK Timeout Recovery Tests
359+
extension CheckoutAPIServiceTests {
360+
func testWhenRiskSDKCallsCompletionThenFramesReturnsSuccess() {
361+
let card = StubProvider.createCard()
362+
let tokenRequest = StubProvider.createTokenRequest()
363+
let requestParameters = StubProvider.createRequestParameters()
364+
let tokenResponse = StubProvider.createTokenResponse()
365+
let tokenDetails = StubProvider.createTokenDetails()
366+
367+
stubTokenRequestFactory.createToReturn = .success(tokenRequest)
368+
stubRequestFactory.createToReturn = .success(requestParameters)
369+
stubTokenDetailsFactory.createToReturn = tokenDetails
370+
371+
var result: Result<TokenDetails, TokenisationError.TokenRequest>?
372+
subject.createToken(.card(card)) { result = $0 }
373+
stubRequestExecutor.executeCalledWithCompletion?(.response(tokenResponse), HTTPURLResponse())
374+
375+
XCTAssertEqual(stubRisk.configureCalledCount, 1)
376+
XCTAssertEqual(stubRisk.publishDataCalledCount, 1)
377+
XCTAssertEqual(result, .success(tokenDetails))
378+
}
379+
380+
func testWhenRiskSDKConfigureHangsThenFramesSDKCancelsWaitingRiskSDKAndCallsCompletionBlockAnywayAfterTimeout() {
381+
stubRisk.shouldConfigureFunctionCallCompletion = false // Configure function will hang forever before it calls its completion closure
382+
verifyRiskSDKTimeoutRecovery(timeoutAddition: 1, expectedConfigureCallCount: 1, expectedPublishDataCallCount: 0)
383+
}
384+
385+
func testWhenRiskSDKPublishHangsThenFramesSDKCancelsWaitingRiskSDKAndCallsCompletionBlockAnywayAfterTimeout() {
386+
stubRisk.shouldPublishFunctionCallCompletion = false // Publish data function will hang forever before it calls its completion closure
387+
verifyRiskSDKTimeoutRecovery(timeoutAddition: 1, expectedConfigureCallCount: 1, expectedPublishDataCallCount: 1)
388+
}
389+
390+
func verifyRiskSDKTimeoutRecovery(timeoutAddition: Double,
391+
expectedConfigureCallCount: Int,
392+
expectedPublishDataCallCount: Int,
393+
file: StaticString = #file,
394+
line: UInt = #line) {
395+
let card = StubProvider.createCard()
396+
let tokenRequest = StubProvider.createTokenRequest()
397+
let tokenResponse = StubProvider.createTokenResponse()
398+
let requestParameters = StubProvider.createRequestParameters()
399+
let tokenDetails = StubProvider.createTokenDetails()
400+
401+
stubTokenRequestFactory.createToReturn = .success(tokenRequest)
402+
stubRequestFactory.createToReturn = .success(requestParameters)
403+
stubTokenDetailsFactory.createToReturn = tokenDetails
404+
405+
let expectation = self.expectation(description: "Frames will time out awaiting Risk SDK result")
406+
407+
var _: Result<TokenDetails, TokenisationError.TokenRequest>?
408+
subject.createToken(.card(card)) {
409+
410+
XCTAssertEqual(self.stubRisk.configureCalledCount, expectedConfigureCallCount, file: file, line: line)
411+
XCTAssertEqual(self.stubRisk.publishDataCalledCount, expectedPublishDataCallCount, file: file, line: line)
412+
XCTAssertEqual($0, .success(tokenDetails), file: file, line: line)
413+
414+
expectation.fulfill()
415+
}
416+
stubRequestExecutor.executeCalledWithCompletion?(.response(tokenResponse), HTTPURLResponse())
417+
418+
waitForExpectations(timeout: subject.timeoutInterval + timeoutAddition)
419+
}
420+
}

Frames.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "Frames"
3-
s.version = "4.3.6"
3+
s.version = "4.3.7"
44
s.summary = "Checkout API Client, Payment Form UI and Utilities in Swift"
55
s.description = <<-DESC
66
Checkout API Client and Payment Form Utilities in Swift.
@@ -21,6 +21,6 @@ Pod::Spec.new do |s|
2121

2222
s.dependency 'PhoneNumberKit'
2323
s.dependency 'CheckoutEventLoggerKit', '~> 1.2.4'
24-
s.dependency 'Checkout', '4.3.6'
24+
s.dependency 'Checkout', '4.3.7'
2525

2626
end

Source/Core/Constants/Constants.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
enum Constants {
99

1010
static let productName = "frames-ios-sdk"
11-
static let version = "4.3.6"
11+
static let version = "4.3.7"
1212
static let userAgent = "checkout-sdk-frames-ios/\(version)"
1313

1414
enum Logging {

iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1239,7 +1239,7 @@
12391239
repositoryURL = "https://github.com/checkout/frames-ios";
12401240
requirement = {
12411241
kind = exactVersion;
1242-
version = 4.3.6;
1242+
version = 4.3.7;
12431243
};
12441244
};
12451245
16C3F83E2A7927ED00690639 /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = {

iOS Example Frame/Podfile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ target 'iOS Example Frame' do
66
use_frameworks!
77

88
# Pods for iOS Example Custom
9-
pod 'Frames', '4.3.6'
10-
9+
pod 'Frames', '4.3.7'
1110
end
1211

1312
post_install do |installer|

0 commit comments

Comments
 (0)