From ad6a6f82c7e1cdff1387c7b8f37db3219ca92edc Mon Sep 17 00:00:00 2001 From: Precious OSSAI <111347791+precious-ossai-cko@users.noreply.github.com> Date: Tue, 11 Jun 2024 10:59:34 +0100 Subject: [PATCH] Fix: Await risk sdk actions (#275) --- buildSrc/src/main/java/Versions.kt | 2 +- .../com/checkout/CheckoutApiServiceFactory.kt | 10 ++++-- .../repository/TokenRepositoryImpl.kt | 35 ++++++++++++++----- .../usecase/RiskInstanceProvider.kt | 6 ++-- .../tokenization/usecase/RiskSdkUseCase.kt | 23 +++++------- .../tokenization/RiskSdkUseCaseTest.kt | 10 ++++-- .../repository/TokenRepositoryImplTest.kt | 35 +++++++++++++------ 7 files changed, 79 insertions(+), 42 deletions(-) diff --git a/buildSrc/src/main/java/Versions.kt b/buildSrc/src/main/java/Versions.kt index 52b409ad..72b804f7 100644 --- a/buildSrc/src/main/java/Versions.kt +++ b/buildSrc/src/main/java/Versions.kt @@ -36,7 +36,7 @@ object Versions { const val moshi = "1.15.1" // Risk SDK Dependencies - const val riskSdk = "1.0.6" + const val riskSdk = "2.0.0" // Unit Testing Dependencies const val junit5Jupiter = "5.8.0" diff --git a/checkout/src/main/java/com/checkout/CheckoutApiServiceFactory.kt b/checkout/src/main/java/com/checkout/CheckoutApiServiceFactory.kt index 4d9aa148..f2ae1952 100644 --- a/checkout/src/main/java/com/checkout/CheckoutApiServiceFactory.kt +++ b/checkout/src/main/java/com/checkout/CheckoutApiServiceFactory.kt @@ -8,6 +8,7 @@ import com.checkout.logging.EventLoggerProvider import com.checkout.logging.Logger import com.checkout.logging.model.LoggingEvent import com.checkout.network.OkHttpProvider +import com.checkout.risk.FramesOptions import com.checkout.threedsecure.Executor import com.checkout.threedsecure.ThreeDSExecutor import com.checkout.threedsecure.logging.ThreeDSEventLogger @@ -32,6 +33,7 @@ import com.squareup.moshi.Moshi public object CheckoutApiServiceFactory { private lateinit var correlationId: String + private lateinit var riskSDKFramesOptions: FramesOptions @JvmStatic public fun create( @@ -40,9 +42,13 @@ public object CheckoutApiServiceFactory { context: Context, ): CheckoutApiService { val logger = EventLoggerProvider.provide() - logger.setup(context, environment) correlationId = logger.correlationId + riskSDKFramesOptions = FramesOptions( + version = BuildConfig.PRODUCT_VERSION, + productIdentifier = BuildConfig.PRODUCT_IDENTIFIER, + correlationId, + ) return CheckoutApiClient( provideTokenRepository(context, publicKey, environment), @@ -71,7 +77,7 @@ public object CheckoutApiServiceFactory { logger = TokenizationEventLogger(EventLoggerProvider.provide()), publicKey = publicKey, cvvTokenizationNetworkDataMapper = CVVTokenizationNetworkDataMapper(), - riskSdkUseCase = RiskSdkUseCase(environment, context, publicKey, correlationId, RiskInstanceProvider), + riskSdkUseCase = RiskSdkUseCase(environment, context, publicKey, riskSDKFramesOptions, RiskInstanceProvider), ) private fun provideNetworkApiClient( 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 3787d18d..cd1ad660 100644 --- a/checkout/src/main/java/com/checkout/tokenization/repository/TokenRepositoryImpl.kt +++ b/checkout/src/main/java/com/checkout/tokenization/repository/TokenRepositoryImpl.kt @@ -24,13 +24,16 @@ import com.checkout.tokenization.request.GooglePayTokenNetworkRequest import com.checkout.tokenization.request.TokenRequest import com.checkout.tokenization.response.CVVTokenDetailsResponse import com.checkout.tokenization.response.TokenDetailsResponse +import com.checkout.tokenization.usecase.RiskSdkUseCase import com.checkout.tokenization.utils.TokenizationConstants import com.checkout.validation.model.ValidationResult +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.json.JSONException import org.json.JSONObject @@ -45,7 +48,8 @@ internal class TokenRepositoryImpl( private val logger: TokenizationLogger, private val publicKey: String, private val cvvTokenizationNetworkDataMapper: TokenizationNetworkDataMapper, - private val riskSdkUseCase: UseCase, Unit>, + private val riskSdkUseCase: RiskSdkUseCase, + private val dispatcher: CoroutineDispatcher = Dispatchers.IO, ) : TokenRepository { @VisibleForTesting var networkCoroutineScope = @@ -58,6 +62,7 @@ internal class TokenRepositoryImpl( @Suppress("TooGenericExceptionCaught") override fun sendCardTokenRequest(cardTokenRequest: CardTokenRequest) { var response: NetworkApiResponse + val tokenType = TokenizationConstants.CARD networkCoroutineScope.launch { val validationTokenizationDataResult = validateTokenizationDataUseCase.execute(cardTokenRequest.card) @@ -83,7 +88,7 @@ internal class TokenRepositoryImpl( val tokenResult = cardTokenizationNetworkDataMapper.toTokenResult(response) launch(Dispatchers.Main) { - handleResponse(tokenResult, cardTokenRequest.onSuccess, cardTokenRequest.onFailure) + handleResponse(tokenType, tokenResult, cardTokenRequest.onSuccess, cardTokenRequest.onFailure) } } } @@ -126,8 +131,15 @@ internal class TokenRepositoryImpl( launch(Dispatchers.Main) { when (tokenResult) { is TokenResult.Success -> { - resultHandler(CVVTokenizationResultHandler.Success(tokenResult.result)) - riskSdkUseCase.execute(TokenResult.Success(tokenResult.result.token)) + try { + withContext(dispatcher) { + riskSdkUseCase.execute(TokenResult.Success(tokenResult.result.token)) + } + } catch (exception: Exception) { + logger.logErrorOnTokenRequestedEvent(tokenType, publicKey, exception) + } finally { + resultHandler(CVVTokenizationResultHandler.Success(tokenResult.result)) + } } is TokenResult.Failure -> { @@ -144,6 +156,7 @@ internal class TokenRepositoryImpl( @Suppress("TooGenericExceptionCaught") override fun sendGooglePayTokenRequest(googlePayTokenRequest: GooglePayTokenRequest) { var response: NetworkApiResponse + val tokenType = TokenizationConstants.GOOGLE_PAY networkCoroutineScope.launch { try { @@ -176,7 +189,7 @@ internal class TokenRepositoryImpl( ) launch(Dispatchers.Main) { - handleResponse(tokenResult, googlePayTokenRequest.onSuccess, googlePayTokenRequest.onFailure) + handleResponse(tokenType, tokenResult, googlePayTokenRequest.onSuccess, googlePayTokenRequest.onFailure) } } } @@ -192,15 +205,21 @@ internal class TokenRepositoryImpl( ) } - private fun handleResponse( + private suspend fun handleResponse( + tokenType: String, tokenResult: TokenResult, success: (tokenDetails: TokenDetails) -> Unit, failure: (errorMessage: String) -> Unit, ) { when (tokenResult) { - is TokenResult.Success -> { + is TokenResult.Success -> try { + withContext(dispatcher) { + riskSdkUseCase.execute(TokenResult.Success(tokenResult.result.token)) + } + } catch (exception: Exception) { + logger.logErrorOnTokenRequestedEvent(tokenType, publicKey, exception) + } finally { success(tokenResult.result) - riskSdkUseCase.execute(TokenResult.Success(tokenResult.result.token)) } is TokenResult.Failure -> { diff --git a/checkout/src/main/java/com/checkout/tokenization/usecase/RiskInstanceProvider.kt b/checkout/src/main/java/com/checkout/tokenization/usecase/RiskInstanceProvider.kt index a75cd1a0..42aa0d5b 100644 --- a/checkout/src/main/java/com/checkout/tokenization/usecase/RiskInstanceProvider.kt +++ b/checkout/src/main/java/com/checkout/tokenization/usecase/RiskInstanceProvider.kt @@ -2,6 +2,7 @@ package com.checkout.tokenization.usecase import android.content.Context import com.checkout.base.model.Environment +import com.checkout.risk.FramesOptions import com.checkout.risk.Risk import com.checkout.risk.RiskConfig import com.checkout.risk.RiskEnvironment @@ -13,7 +14,7 @@ internal object RiskInstanceProvider { context: Context, publicKey: String, environment: Environment, - correlationId: String, + framesOptions: FramesOptions, ): Risk? { if (riskInstance != null) { return riskInstance @@ -31,8 +32,7 @@ internal object RiskInstanceProvider { RiskConfig( publicKey = publicKey, environment = riskEnvironment, - framesMode = true, - correlationId = correlationId, + framesOptions = framesOptions, ), ) return riskInstance diff --git a/checkout/src/main/java/com/checkout/tokenization/usecase/RiskSdkUseCase.kt b/checkout/src/main/java/com/checkout/tokenization/usecase/RiskSdkUseCase.kt index d8f38d5c..ccf0dac0 100644 --- a/checkout/src/main/java/com/checkout/tokenization/usecase/RiskSdkUseCase.kt +++ b/checkout/src/main/java/com/checkout/tokenization/usecase/RiskSdkUseCase.kt @@ -2,28 +2,21 @@ package com.checkout.tokenization.usecase import android.content.Context import com.checkout.base.model.Environment -import com.checkout.base.usecase.UseCase +import com.checkout.risk.FramesOptions import com.checkout.tokenization.model.TokenResult -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch internal class RiskSdkUseCase( private val environment: Environment, private val context: Context, private val publicKey: String, - private val correlationId: String, + private val framesOptions: FramesOptions, private val riskInstanceProvider: RiskInstanceProvider, -) : UseCase, Unit> { - override fun execute(data: TokenResult) { - CoroutineScope(Dispatchers.IO).launch { - val riskInstance = riskInstanceProvider.provide(context, publicKey, environment, correlationId) - when (data) { - is TokenResult.Success -> { - riskInstance?.publishData(cardToken = data.result) - } - is TokenResult.Failure -> {} - } +) { + suspend fun execute(data: TokenResult) { + val riskInstance = riskInstanceProvider.provide(context, publicKey, environment, framesOptions) + when (data) { + is TokenResult.Success -> riskInstance?.publishData(cardToken = data.result) + is TokenResult.Failure -> {} } } } diff --git a/checkout/src/test/java/com/checkout/tokenization/RiskSdkUseCaseTest.kt b/checkout/src/test/java/com/checkout/tokenization/RiskSdkUseCaseTest.kt index 47faeb79..a3b78550 100644 --- a/checkout/src/test/java/com/checkout/tokenization/RiskSdkUseCaseTest.kt +++ b/checkout/src/test/java/com/checkout/tokenization/RiskSdkUseCaseTest.kt @@ -3,6 +3,7 @@ package com.checkout.tokenization import android.content.Context import com.checkout.base.error.CheckoutError import com.checkout.base.model.Environment +import com.checkout.risk.FramesOptions import com.checkout.risk.Risk import com.checkout.tokenization.model.TokenDetails import com.checkout.tokenization.model.TokenResult @@ -18,6 +19,9 @@ import org.junit.jupiter.api.Test public class RiskSdkUseCaseTest { private val correlationId: String = "testCorrelationId" + private val version: String = "1.0.0" + private val productIdentifier: String = "productIdentifier" + private val framesOptions: FramesOptions = FramesOptions(version, productIdentifier, correlationId) private val environment: Environment = Environment.SANDBOX private val riskInstanceProvider: RiskInstanceProvider = mockk() private val riskInstance: Risk = mockk() @@ -28,7 +32,7 @@ public class RiskSdkUseCaseTest { @BeforeEach public fun setup() { coEvery { tokenDetails.token } returns TOKEN - coEvery { riskInstanceProvider.provide(context, PUBLIC_KEY, environment, correlationId) } returns riskInstance + coEvery { riskInstanceProvider.provide(context, PUBLIC_KEY, environment, framesOptions) } returns riskInstance coEvery { riskInstance.publishData(any()) } returns mockk() useCase = RiskSdkUseCase( @@ -36,7 +40,7 @@ public class RiskSdkUseCaseTest { context = context, publicKey = PUBLIC_KEY, riskInstanceProvider = riskInstanceProvider, - correlationId = correlationId, + framesOptions = framesOptions, ) } @@ -44,7 +48,7 @@ public class RiskSdkUseCaseTest { public fun `Success result should trigger publishData`() { runBlocking { useCase.execute(TokenResult.Success(tokenDetails.token)) - coVerify { riskInstanceProvider.provide(context, PUBLIC_KEY, environment, correlationId) } + coVerify { riskInstanceProvider.provide(context, PUBLIC_KEY, environment, framesOptions) } coVerify { riskInstance.publishData(TOKEN) } } } 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 f8a33670..1a259b46 100644 --- a/checkout/src/test/java/com/checkout/tokenization/repository/TokenRepositoryImplTest.kt +++ b/checkout/src/test/java/com/checkout/tokenization/repository/TokenRepositoryImplTest.kt @@ -18,10 +18,10 @@ import com.checkout.tokenization.model.CVVTokenizationResultHandler import com.checkout.tokenization.model.Card import com.checkout.tokenization.model.CardTokenRequest import com.checkout.tokenization.model.GooglePayTokenRequest -import com.checkout.tokenization.model.TokenResult import com.checkout.tokenization.model.ValidateCVVTokenizationRequest import com.checkout.tokenization.response.CVVTokenDetailsResponse import com.checkout.tokenization.response.TokenDetailsResponse +import com.checkout.tokenization.usecase.RiskSdkUseCase import com.checkout.tokenization.utils.TokenizationConstants import com.checkout.validation.model.ValidationResult import io.mockk.coEvery @@ -30,12 +30,14 @@ import io.mockk.impl.annotations.RelaxedMockK import io.mockk.junit5.MockKExtension import io.mockk.mockk import io.mockk.verify +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import org.amshove.kluent.internal.assertEquals @@ -57,7 +59,7 @@ internal class TokenRepositoryImplTest { private lateinit var mockValidateTokenizationDataUseCase: UseCase> @RelaxedMockK - private lateinit var mockRiskSdkUseCase: UseCase, Unit> + private lateinit var mockRiskSdkUseCase: RiskSdkUseCase @RelaxedMockK private lateinit var mockValidateCVVTokenizationDataUseCase: @@ -197,7 +199,7 @@ internal class TokenRepositoryImplTest { onFailure = { isSuccess = false }, ), ) - + advanceUntilIdle() // Then launch { if (successHandlerInvoked) { @@ -421,6 +423,7 @@ internal class TokenRepositoryImplTest { var isSuccess: Boolean? = null val testDispatcher = UnconfinedTestDispatcher(testScheduler) + tokenRepositoryImpl = createTokenRepository(testDispatcher) Dispatchers.setMain(testDispatcher) tokenRepositoryImpl.networkCoroutineScope = CoroutineScope(StandardTestDispatcher(testScheduler)) @@ -436,11 +439,9 @@ internal class TokenRepositoryImplTest { onFailure = { isSuccess = false }, ), ) - + advanceUntilIdle() // Then - launch { - assertEquals(isSuccess.toString(), successHandlerInvoked.toString()) - } + launch { assertEquals(successHandlerInvoked.toString(), isSuccess.toString()) } } private fun testGooglePayErrorHandlerInvocation(response: NetworkApiResponse) = @@ -535,6 +536,22 @@ internal class TokenRepositoryImplTest { } } + private fun createTokenRepository(testDispatcher: CoroutineDispatcher): TokenRepositoryImpl { + return TokenRepositoryImpl( + networkApiClient = mockTokenNetworkApiClient, + cardToTokenRequestMapper = CardToTokenRequestMapper(), + cvvToTokenNetworkRequestMapper = CVVToTokenNetworkRequestMapper(), + cardTokenizationNetworkDataMapper = CardTokenizationNetworkDataMapper(), + validateTokenizationDataUseCase = mockValidateTokenizationDataUseCase, + validateCVVTokenizationDataUseCase = mockValidateCVVTokenizationDataUseCase, + logger = mockTokenizationLogger, + publicKey = "test_key", + cvvTokenizationNetworkDataMapper = CVVTokenizationNetworkDataMapper(), + riskSdkUseCase = mockRiskSdkUseCase, + dispatcher = testDispatcher, + ) + } + @DisplayName("CVVToken Details invocation") @Nested inner class GetCVVTokenNetworkRequestDetails { @@ -658,9 +675,7 @@ internal class TokenRepositoryImplTest { ) // Then - launch { - assertEquals(isSuccess.toString(), successHandlerInvoked.toString()) - } + launch { assertEquals(isSuccess.toString(), successHandlerInvoked.toString()) } } private fun testCVVTokenizationEventInvocation(isSuccessResponse: Boolean) =