From 1236bb17ff58f68c8e958d2db727cc8fa2d6aa4b Mon Sep 17 00:00:00 2001 From: Chintan Soni <114917119+chintan-soni-cko@users.noreply.github.com> Date: Wed, 11 Oct 2023 12:15:50 +0100 Subject: [PATCH] PIMOB:2168 - Tokenization Logging was implemented for CVV (#244) --- .../logging/TokenizationEventLogger.kt | 34 +++-- .../logging/TokenizationLogger.kt | 2 + .../repository/TokenRepositoryImpl.kt | 53 ++++++-- .../logging/TokenizationEventLoggerTest.kt | 11 +- .../repository/TokenRepositoryImplTest.kt | 116 ++++++++++++++++-- .../styling/CustomCVVInputFieldStyle.kt | 2 +- ...ngFormAddressToBillingAddressMapperTest.kt | 1 + 7 files changed, 186 insertions(+), 33 deletions(-) diff --git a/checkout/src/main/java/com/checkout/tokenization/logging/TokenizationEventLogger.kt b/checkout/src/main/java/com/checkout/tokenization/logging/TokenizationEventLogger.kt index f2cff94b..41941945 100644 --- a/checkout/src/main/java/com/checkout/tokenization/logging/TokenizationEventLogger.kt +++ b/checkout/src/main/java/com/checkout/tokenization/logging/TokenizationEventLogger.kt @@ -14,6 +14,7 @@ import com.checkout.logging.utils.TOKEN_ID import com.checkout.logging.utils.TOKEN_TYPE import com.checkout.logging.utils.putErrorAttributes import com.checkout.network.response.ErrorResponse +import com.checkout.tokenization.response.CVVTokenDetailsResponse import com.checkout.tokenization.response.TokenDetailsResponse internal class TokenizationEventLogger(private val logger: Logger) : TokenizationLogger { @@ -29,9 +30,19 @@ internal class TokenizationEventLogger(private val logger: Logger) tokenType: String, publicKey: String, tokenDetails: TokenDetailsResponse?, + cvvTokenDetailsResponse: CVVTokenDetailsResponse?, code: Int?, errorResponse: ErrorResponse?, - ) = logEvent(TokenizationEventType.TOKEN_RESPONSE, tokenType, publicKey, null, tokenDetails, code, errorResponse) + ) = logEvent( + tokenizationEventType = TokenizationEventType.TOKEN_RESPONSE, + tokenType = tokenType, + publicKey = publicKey, + error = null, + tokenDetails = tokenDetails, + cvvTokenDetailsResponse = cvvTokenDetailsResponse, + code = code, + errorResponse = errorResponse, + ) override fun resetSession() = logger.resetSession() @@ -41,17 +52,19 @@ internal class TokenizationEventLogger(private val logger: Logger) publicKey: String, error: Throwable? = null, tokenDetails: TokenDetailsResponse? = null, + cvvTokenDetailsResponse: CVVTokenDetailsResponse? = null, code: Int? = null, errorResponse: ErrorResponse? = null, ) = logger.log( provideLoggingEvent( - tokenizationEventType, - tokenType, - publicKey, - error, - tokenDetails, - code, - errorResponse, + tokenizationEventType = tokenizationEventType, + tokenType = tokenType, + publicKey = publicKey, + error = error, + tokenDetails = tokenDetails, + cvvTokenDetails = cvvTokenDetailsResponse, + code = code, + errorResponse = errorResponse, ), ) @@ -61,6 +74,7 @@ internal class TokenizationEventLogger(private val logger: Logger) publicKey: String, error: Throwable?, tokenDetails: TokenDetailsResponse?, + cvvTokenDetails: CVVTokenDetailsResponse?, code: Int?, errorResponse: ErrorResponse?, ): LoggingEvent { @@ -75,6 +89,10 @@ internal class TokenizationEventLogger(private val logger: Logger) properties[SCHEME] = it.scheme ?: "" } + cvvTokenDetails?.let { + properties[TOKEN_ID] = it.token + } + code?.let { properties[HTTP_STATUS_CODE] = it } diff --git a/checkout/src/main/java/com/checkout/tokenization/logging/TokenizationLogger.kt b/checkout/src/main/java/com/checkout/tokenization/logging/TokenizationLogger.kt index 3b06e0d6..93335237 100644 --- a/checkout/src/main/java/com/checkout/tokenization/logging/TokenizationLogger.kt +++ b/checkout/src/main/java/com/checkout/tokenization/logging/TokenizationLogger.kt @@ -1,6 +1,7 @@ package com.checkout.tokenization.logging import com.checkout.network.response.ErrorResponse +import com.checkout.tokenization.response.CVVTokenDetailsResponse import com.checkout.tokenization.response.TokenDetailsResponse /** @@ -25,6 +26,7 @@ internal interface TokenizationLogger { tokenType: String, publicKey: String, tokenDetails: TokenDetailsResponse? = null, + cvvTokenDetailsResponse: CVVTokenDetailsResponse? = null, code: Int? = null, errorResponse: ErrorResponse? = null, ) diff --git a/checkout/src/main/java/com/checkout/tokenization/repository/TokenRepositoryImpl.kt b/checkout/src/main/java/com/checkout/tokenization/repository/TokenRepositoryImpl.kt index e99035f5..40a9ab78 100644 --- a/checkout/src/main/java/com/checkout/tokenization/repository/TokenRepositoryImpl.kt +++ b/checkout/src/main/java/com/checkout/tokenization/repository/TokenRepositoryImpl.kt @@ -88,8 +88,11 @@ internal class TokenRepositoryImpl( @Suppress("TooGenericExceptionCaught") override fun sendCVVTokenizationRequest(cvvTokenizationRequest: CVVTokenizationRequest) { + var response: NetworkApiResponse + with(cvvTokenizationRequest) { networkCoroutineScope.launch { + val tokenType = TokenizationConstants.CVV val validateCVVRequest = ValidateCVVTokenizationRequest( cvv = cvv, cardScheme = cardScheme, @@ -97,15 +100,20 @@ internal class TokenRepositoryImpl( val validationTokenizationDataResult = validateCVVTokenizationDataUseCase.execute(validateCVVRequest) - val response: NetworkApiResponse = when (validationTokenizationDataResult) { + when (validationTokenizationDataResult) { is ValidationResult.Failure -> { - NetworkApiResponse.InternalError(validationTokenizationDataResult.error) + response = NetworkApiResponse.InternalError(validationTokenizationDataResult.error) } is ValidationResult.Success -> { - networkApiClient.sendCVVTokenRequest( + logger.logTokenRequestEvent(tokenType, publicKey) + + response = networkApiClient.sendCVVTokenRequest( cvvToTokenNetworkRequestMapper.map(from = cvvTokenizationRequest), ) + + logCVVTokenizationResponse(response) + logger.resetSession() } } @@ -195,14 +203,41 @@ internal class TokenRepositoryImpl( private fun logResponse(response: NetworkApiResponse, tokenType: String) { when (response) { is NetworkApiResponse.ServerError -> logger.logTokenResponseEvent( - tokenType, - publicKey, - null, - response.code, - response.body, + tokenType = tokenType, + publicKey = publicKey, + tokenDetails = null, + cvvTokenDetailsResponse = null, + code = response.code, + errorResponse = response.body, + ) + + is NetworkApiResponse.Success -> logger.logTokenResponseEvent( + tokenType = tokenType, + publicKey = publicKey, + tokenDetails = response.body, + ) + + else -> {} + } + } + + private fun logCVVTokenizationResponse(response: NetworkApiResponse) { + when (response) { + is NetworkApiResponse.ServerError -> logger.logTokenResponseEvent( + tokenType = TokenizationConstants.CVV, + publicKey = publicKey, + tokenDetails = null, + cvvTokenDetailsResponse = null, + code = response.code, + errorResponse = response.body, ) - is NetworkApiResponse.Success -> logger.logTokenResponseEvent(tokenType, publicKey, response.body) + is NetworkApiResponse.Success -> logger.logTokenResponseEvent( + tokenType = TokenizationConstants.CVV, + publicKey = publicKey, + tokenDetails = null, + cvvTokenDetailsResponse = response.body, + ) else -> {} } diff --git a/checkout/src/test/java/com/checkout/tokenization/logging/TokenizationEventLoggerTest.kt b/checkout/src/test/java/com/checkout/tokenization/logging/TokenizationEventLoggerTest.kt index ed75ca48..e2bf3da1 100644 --- a/checkout/src/test/java/com/checkout/tokenization/logging/TokenizationEventLoggerTest.kt +++ b/checkout/src/test/java/com/checkout/tokenization/logging/TokenizationEventLoggerTest.kt @@ -179,11 +179,12 @@ internal class TokenizationEventLoggerTest { // When tokenizationEventLogger.logTokenResponseEvent( - tokenType, - "test_key", - null, - 501, - TokenizationRequestTestData.errorResponse(), + tokenType = tokenType, + publicKey = "test_key", + tokenDetails = null, + cvvTokenDetailsResponse = null, + code = 501, + errorResponse = TokenizationRequestTestData.errorResponse(), ) // Then diff --git a/checkout/src/test/java/com/checkout/tokenization/repository/TokenRepositoryImplTest.kt b/checkout/src/test/java/com/checkout/tokenization/repository/TokenRepositoryImplTest.kt index 7cbf4e98..a97eb192 100644 --- a/checkout/src/test/java/com/checkout/tokenization/repository/TokenRepositoryImplTest.kt +++ b/checkout/src/test/java/com/checkout/tokenization/repository/TokenRepositoryImplTest.kt @@ -3,6 +3,7 @@ package com.checkout.tokenization.repository import com.checkout.base.model.CardScheme import com.checkout.base.usecase.UseCase import com.checkout.mock.TokenizationRequestTestData +import com.checkout.mock.TokenizationRequestTestData.cvvTokenizationRequest import com.checkout.network.response.ErrorResponse import com.checkout.network.response.NetworkApiResponse import com.checkout.tokenization.TokenNetworkApiClient @@ -241,11 +242,12 @@ internal class TokenRepositoryImplTest { } else { verify(exactly = 1) { mockTokenizationLogger.logTokenResponseEvent( - eq(TokenizationConstants.CARD), - eq("test_key"), - null, - 501, - serverErrorBody, + tokenType = eq(TokenizationConstants.CARD), + publicKey = eq("test_key"), + tokenDetails = null, + cvvTokenDetailsResponse = null, + code = 501, + errorResponse = serverErrorBody, ) } } @@ -507,11 +509,12 @@ internal class TokenRepositoryImplTest { } else { verify(exactly = 1) { mockTokenizationLogger.logTokenResponseEvent( - eq(TokenizationConstants.GOOGLE_PAY), - eq("test_key"), - null, - 501, - serverErrorBody, + tokenType = eq(TokenizationConstants.GOOGLE_PAY), + publicKey = eq("test_key"), + tokenDetails = null, + cvvTokenDetailsResponse = null, + code = 501, + errorResponse = serverErrorBody, ) } } @@ -567,6 +570,45 @@ internal class TokenRepositoryImplTest { ) } + @Test + fun `when sendCVVTokenizationRequest invoked with success then log tokenResponseEvent along with resetSession`() { + testCVVTokenizationEventInvocation(true) + } + + @Test + fun `when sendCVVTokenizationRequest invoked with servererror then log tokenResponseEvent along with resetSession`() { + testCVVTokenizationEventInvocation(false) + } + + @Test + fun `when sendCVVTokenizationRequest invoked then send cvv token request with correct data is invoked`() = runTest { + // Given + val response = mockk>() + + val testDispatcher = UnconfinedTestDispatcher(testScheduler) + Dispatchers.setMain(testDispatcher) + + tokenRepositoryImpl.networkCoroutineScope = CoroutineScope(StandardTestDispatcher(testScheduler)) + + every { mockValidateCVVTokenizationDataUseCase.execute(any()) } returns ValidationResult.Success(Unit) + coEvery { mockTokenNetworkApiClient.sendCVVTokenRequest(any()) } returns response + + // When + tokenRepositoryImpl.sendCVVTokenizationRequest( + cvvTokenizationRequest, + ) + + // Then + launch { + verify(exactly = 1) { + mockTokenizationLogger.logTokenRequestEvent( + TokenizationConstants.CVV, + "test_key", + ) + } + } + } + private fun testCVVTokenResultInvocation( successHandlerInvoked: Boolean, response: NetworkApiResponse, @@ -603,5 +645,59 @@ internal class TokenRepositoryImplTest { assertEquals(isSuccess.toString(), successHandlerInvoked.toString()) } } + + private fun testCVVTokenizationEventInvocation(isSuccessResponse: Boolean) = + runTest { + // Given + val successBody = mockk() + val serverErrorBody = mockk() + + val response = if (isSuccessResponse) { + NetworkApiResponse.Success(successBody) + } else { + NetworkApiResponse.ServerError(serverErrorBody, 501) + } + + val testDispatcher = UnconfinedTestDispatcher(testScheduler) + Dispatchers.setMain(testDispatcher) + + tokenRepositoryImpl.networkCoroutineScope = CoroutineScope(StandardTestDispatcher(testScheduler)) + + every { mockValidateCVVTokenizationDataUseCase.execute(any()) } returns ValidationResult.Success(Unit) + coEvery { mockTokenNetworkApiClient.sendCVVTokenRequest(any()) } returns response + + // When + tokenRepositoryImpl.sendCVVTokenizationRequest( + cvvTokenizationRequest, + ) + + // Then + launch { + if (isSuccessResponse) { + verify(exactly = 1) { + mockTokenizationLogger.logTokenResponseEvent( + tokenType = eq(TokenizationConstants.CVV), + publicKey = eq("test_key"), + tokenDetails = null, + cvvTokenDetailsResponse = eq(successBody), + + ) + } + } else { + verify(exactly = 1) { + mockTokenizationLogger.logTokenResponseEvent( + tokenType = eq(TokenizationConstants.CVV), + publicKey = eq("test_key"), + tokenDetails = null, + cvvTokenDetailsResponse = null, + code = 501, + errorResponse = serverErrorBody, + ) + } + } + + verify(exactly = 1) { mockTokenizationLogger.resetSession() } + } + } } } diff --git a/example_app_frames/src/main/java/com/checkout/example/frames/styling/CustomCVVInputFieldStyle.kt b/example_app_frames/src/main/java/com/checkout/example/frames/styling/CustomCVVInputFieldStyle.kt index 2745c82d..6631a7da 100644 --- a/example_app_frames/src/main/java/com/checkout/example/frames/styling/CustomCVVInputFieldStyle.kt +++ b/example_app_frames/src/main/java/com/checkout/example/frames/styling/CustomCVVInputFieldStyle.kt @@ -20,7 +20,7 @@ object CustomCVVInputFieldStyle { fun create() = InputFieldStyle( placeholderTextId = R.string.enter_cvv_here, textStyle = TextStyle(16, color = textColor, font = Font.Cursive), - placeholderStyle = TextStyle(16, color = placeHolderTextColor, font = Font.Cursive), + placeholderStyle = TextStyle(16, color = placeHolderTextColor, font = Font.Monospace), containerStyle = ContainerStyle( width = 250, color = backgroundColor, diff --git a/frames/src/test/java/com/checkout/frames/mapper/BillingFormAddressToBillingAddressMapperTest.kt b/frames/src/test/java/com/checkout/frames/mapper/BillingFormAddressToBillingAddressMapperTest.kt index c0315569..6dd11c1f 100644 --- a/frames/src/test/java/com/checkout/frames/mapper/BillingFormAddressToBillingAddressMapperTest.kt +++ b/frames/src/test/java/com/checkout/frames/mapper/BillingFormAddressToBillingAddressMapperTest.kt @@ -53,6 +53,7 @@ internal class BillingFormAddressToBillingAddressMapperTest { } companion object { + @Suppress("LongMethod") @JvmStatic fun testBillingAddressSummaryArguments(): Stream = Stream.of( Arguments.of(