diff --git a/Checkout/Source/Logging/CheckoutLogEvent+Types.swift b/Checkout/Source/Logging/CheckoutLogEvent+Types.swift index 4020f5ac2..7e61565da 100644 --- a/Checkout/Source/Logging/CheckoutLogEvent+Types.swift +++ b/Checkout/Source/Logging/CheckoutLogEvent+Types.swift @@ -13,6 +13,11 @@ extension CheckoutLogEvent { let publicKey: String } + struct SecurityCodeTokenRequestData: Equatable { + let tokenType: SecurityCodeTokenType? + let publicKey: String + } + struct TokenResponseData: Equatable { let tokenID: String? let scheme: String? diff --git a/Checkout/Source/Logging/CheckoutLogEvent.swift b/Checkout/Source/Logging/CheckoutLogEvent.swift index 623ddbc6d..ccb2313aa 100644 --- a/Checkout/Source/Logging/CheckoutLogEvent.swift +++ b/Checkout/Source/Logging/CheckoutLogEvent.swift @@ -16,6 +16,8 @@ enum CheckoutLogEvent: Equatable { case validateExpiryString case validateExpiryInteger case validateCVV + case cvvRequested(SecurityCodeTokenRequestData) + case cvvResponse(SecurityCodeTokenRequestData, TokenResponseData) func event(date: Date) -> Event { Event( @@ -39,9 +41,9 @@ enum CheckoutLogEvent: Equatable { private var typeIdentifier: String { switch self { - case .tokenRequested: + case .tokenRequested, .cvvRequested: return "token_requested" - case .tokenResponse: + case .tokenResponse, .cvvResponse: return "token_response" case .cardValidator: return "card_validator" @@ -63,9 +65,10 @@ enum CheckoutLogEvent: Equatable { .validateCardNumber, .validateExpiryString, .validateExpiryInteger, - .validateCVV: + .validateCVV, + .cvvRequested: return .info - case .tokenResponse(_, let tokenResponseData): + case .tokenResponse(_, let tokenResponseData), .cvvResponse(_, let tokenResponseData): return level(from: tokenResponseData.httpStatusCode) } } @@ -102,6 +105,23 @@ enum CheckoutLogEvent: Equatable { [.httpStatusCode: tokenResponseData.httpStatusCode], [.serverError: tokenResponseData.serverError] ) + case .cvvRequested(let tokenRequestData): + return [ + .tokenType: tokenRequestData.tokenType?.rawValue.lowercased(), + .publicKey: tokenRequestData.publicKey + ].compactMapValues { $0 } + + case let .cvvResponse(tokenRequestData, tokenResponseData): + return mergeDictionaries( + [ + .tokenType: tokenRequestData.tokenType?.rawValue.lowercased(), + .publicKey: tokenRequestData.publicKey, + .tokenID: tokenResponseData.tokenID, + .scheme: tokenResponseData.scheme + ], + [.httpStatusCode: tokenResponseData.httpStatusCode], + [.serverError: tokenResponseData.serverError] + ) } } diff --git a/Checkout/Source/Network/Models/SecurityCode/SecurityCodeRequest.swift b/Checkout/Source/Network/Models/SecurityCode/SecurityCodeRequest.swift index 8c1582427..a52bece7e 100644 --- a/Checkout/Source/Network/Models/SecurityCode/SecurityCodeRequest.swift +++ b/Checkout/Source/Network/Models/SecurityCode/SecurityCodeRequest.swift @@ -19,3 +19,8 @@ struct TokenData: Encodable, Equatable { case securityCode = "cvv" } } + +// For logging purposes only +enum SecurityCodeTokenType: String, Codable, Equatable { + case cvv +} diff --git a/Checkout/Source/Tokenisation/CheckoutAPIService.swift b/Checkout/Source/Tokenisation/CheckoutAPIService.swift index 019fcd6f7..10ac0fc00 100644 --- a/Checkout/Source/Tokenisation/CheckoutAPIService.swift +++ b/Checkout/Source/Tokenisation/CheckoutAPIService.swift @@ -186,6 +186,10 @@ extension CheckoutAPIService { switch requestParameterResult { case .success(let requestParameters): + logManager.queue(event: .cvvRequested(CheckoutLogEvent.SecurityCodeTokenRequestData( + tokenType: .cvv, + publicKey: publicKey + ))) createSecurityCodeToken(requestParameters: requestParameters, completion: completion) case .failure(let error): switch error { @@ -200,7 +204,9 @@ extension CheckoutAPIService { requestParameters, responseType: SecurityCodeResponse.self, responseErrorType: TokenisationError.ServerError.self - ) { tokenResponseResult, httpURLResponse in + ) { [logManager, logSecurityCodeTokenResponse] tokenResponseResult, httpURLResponse in + logSecurityCodeTokenResponse(tokenResponseResult, httpURLResponse) + switch tokenResponseResult { case .response(let tokenResponse): completion(.success(tokenResponse)) @@ -209,6 +215,36 @@ extension CheckoutAPIService { case .networkError(let networkError): completion(.failure(.networkError(networkError))) } + + logManager.resetCorrelationID() + } + } + + private func logSecurityCodeTokenResponse(tokenResponseResult: NetworkRequestResult, httpURLResponse: HTTPURLResponse?) { + switch tokenResponseResult { + case .response(let tokenResponse): + let tokenRequestData = CheckoutLogEvent.SecurityCodeTokenRequestData(tokenType: .cvv, publicKey: publicKey) + let tokenResponseData = CheckoutLogEvent.TokenResponseData( + tokenID: tokenResponse.token, + scheme: nil, + httpStatusCode: httpURLResponse?.statusCode, + serverError: nil + ) + + logManager.queue(event: .cvvResponse(tokenRequestData, tokenResponseData)) + case .errorResponse(let errorResponse): + let tokenRequestData = CheckoutLogEvent.SecurityCodeTokenRequestData(tokenType: nil, publicKey: publicKey) + let tokenResponseData = CheckoutLogEvent.TokenResponseData( + tokenID: nil, + scheme: nil, + httpStatusCode: httpURLResponse?.statusCode, + serverError: errorResponse + ) + + logManager.queue(event: .cvvResponse(tokenRequestData, tokenResponseData)) + case .networkError: + // we received no response, so nothing to log + break } } } diff --git a/CheckoutTests/Tokenisation/CheckoutAPIServiceTests.swift b/CheckoutTests/Tokenisation/CheckoutAPIServiceTests.swift index 31d4ab521..076c28d47 100644 --- a/CheckoutTests/Tokenisation/CheckoutAPIServiceTests.swift +++ b/CheckoutTests/Tokenisation/CheckoutAPIServiceTests.swift @@ -103,7 +103,6 @@ final class CheckoutAPIServiceTests: XCTestCase { var result: Result? subject.createToken(.card(card)) { result = $0 } - XCTAssertEqual(StubLogManager.queueCalledWith.last, .validateCVV) XCTAssertEqual(result, .failure(.cardValidationError(.cvv(.invalidLength)))) } @@ -117,7 +116,6 @@ final class CheckoutAPIServiceTests: XCTestCase { var result: Result? subject.createToken(.card(card)) { result = $0 } - XCTAssertEqual(StubLogManager.queueCalledWith.last, .validateCVV) XCTAssertEqual(result, .failure(.couldNotBuildURLForRequest)) } @@ -231,6 +229,11 @@ extension CheckoutAPIServiceTests { var result: Result? subject.createSecurityCodeToken(securityCode: "123", completion: { result = $0 }) + XCTAssertEqual(StubLogManager.queueCalledWith.last, .cvvRequested(.init( + tokenType: .cvv, + publicKey: "publicKey" + ))) + stubSecurityCodeRequestExecutor.executeCalledWithCompletion?(.response(tokenResponse), HTTPURLResponse()) XCTAssertEqual(stubRequestFactory.createCalledWith, .securityCodeToken(request: tokenRequest, publicKey: "publicKey")) @@ -239,6 +242,11 @@ extension CheckoutAPIServiceTests { XCTAssertTrue(stubSecurityCodeRequestExecutor.executeCalledWithResponseType == SecurityCodeResponse.self) XCTAssertTrue(stubSecurityCodeRequestExecutor.executeCalledWithResponseErrorType == TokenisationError.ServerError.self) + XCTAssertEqual(StubLogManager.queueCalledWith.last, .cvvResponse( + .init(tokenType: .cvv, publicKey: "publicKey"), + .init(tokenID: "some_token", scheme: nil, httpStatusCode: 200, serverError: nil) + )) + XCTAssertEqual(result, .success(tokenResponse)) } @@ -276,8 +284,23 @@ extension CheckoutAPIServiceTests { var result: Result? subject.createSecurityCodeToken(securityCode: "123", completion: { result = $0 }) + XCTAssertEqual(StubLogManager.queueCalledWith.last, .cvvRequested(.init( + tokenType: .cvv, + publicKey: "publicKey" + ))) + stubSecurityCodeRequestExecutor.executeCalledWithCompletion?(.errorResponse(serverError), HTTPURLResponse()) + XCTAssertEqual(StubLogManager.queueCalledWith.last, .cvvResponse( + .init(tokenType: nil, publicKey: "publicKey"), + .init( + tokenID: nil, + scheme: nil, + httpStatusCode: 200, + serverError: .init(requestID: "requestID", errorType: "errorType", errorCodes: ["test", "value"]) + ) + )) + XCTAssertEqual(stubRequestFactory.createCalledWith, .securityCodeToken(request: tokenRequest, publicKey: "publicKey")) XCTAssertEqual(stubSecurityCodeRequestExecutor.executeCalledWithRequestParameters, requestParameters) @@ -297,6 +320,11 @@ extension CheckoutAPIServiceTests { var result: Result? subject.createSecurityCodeToken(securityCode: "123", completion: { result = $0 }) + XCTAssertEqual(StubLogManager.queueCalledWith.last, .cvvRequested(.init( + tokenType: .cvv, + publicKey: "publicKey" + ))) + stubSecurityCodeRequestExecutor.executeCalledWithCompletion?(.networkError(.connectionFailed), HTTPURLResponse()) XCTAssertEqual(stubRequestFactory.createCalledWith, .securityCodeToken(request: tokenRequest, publicKey: "publicKey")) @@ -307,4 +335,16 @@ extension CheckoutAPIServiceTests { XCTAssertEqual(result, .failure(.networkError(.connectionFailed))) } + + func testCreateSEcurityCodeTokenWithoutAPIKey() { + let service = CheckoutAPIService(publicKey: "", environment: .sandbox) + + service.createSecurityCodeToken(securityCode: "123") { result in + if case .failure(let failure) = result { + XCTAssertEqual(failure, .missingAPIKey) + } else { + XCTFail("Test should return a failure") + } + } + } } diff --git a/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj b/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj index 414f6ebc6..0d342368e 100644 --- a/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj +++ b/iOS Example Frame SPM/iOS Example Frame SPM.xcodeproj/project.pbxproj @@ -1235,7 +1235,7 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/checkout/frames-ios"; requirement = { - branch = "feature/cvv-component-tokenisation"; + branch = "feature/cvv-component-logging"; kind = branch; }; };