Skip to content

Commit

Permalink
Fix: Await risk sdk actions (#275)
Browse files Browse the repository at this point in the history
  • Loading branch information
precious-ossai-cko authored Jun 11, 2024
1 parent cbeb2f7 commit ad6a6f8
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 42 deletions.
2 changes: 1 addition & 1 deletion buildSrc/src/main/java/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
10 changes: 8 additions & 2 deletions checkout/src/main/java/com/checkout/CheckoutApiServiceFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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),
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -45,7 +48,8 @@ internal class TokenRepositoryImpl(
private val logger: TokenizationLogger,
private val publicKey: String,
private val cvvTokenizationNetworkDataMapper: TokenizationNetworkDataMapper<CVVTokenDetails>,
private val riskSdkUseCase: UseCase<TokenResult<String>, Unit>,
private val riskSdkUseCase: RiskSdkUseCase,
private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
) : TokenRepository {
@VisibleForTesting
var networkCoroutineScope =
Expand All @@ -58,6 +62,7 @@ internal class TokenRepositoryImpl(
@Suppress("TooGenericExceptionCaught")
override fun sendCardTokenRequest(cardTokenRequest: CardTokenRequest) {
var response: NetworkApiResponse<TokenDetailsResponse>
val tokenType = TokenizationConstants.CARD

networkCoroutineScope.launch {
val validationTokenizationDataResult = validateTokenizationDataUseCase.execute(cardTokenRequest.card)
Expand All @@ -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)
}
}
}
Expand Down Expand Up @@ -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 -> {
Expand All @@ -144,6 +156,7 @@ internal class TokenRepositoryImpl(
@Suppress("TooGenericExceptionCaught")
override fun sendGooglePayTokenRequest(googlePayTokenRequest: GooglePayTokenRequest) {
var response: NetworkApiResponse<TokenDetailsResponse>
val tokenType = TokenizationConstants.GOOGLE_PAY

networkCoroutineScope.launch {
try {
Expand Down Expand Up @@ -176,7 +189,7 @@ internal class TokenRepositoryImpl(
)

launch(Dispatchers.Main) {
handleResponse(tokenResult, googlePayTokenRequest.onSuccess, googlePayTokenRequest.onFailure)
handleResponse(tokenType, tokenResult, googlePayTokenRequest.onSuccess, googlePayTokenRequest.onFailure)
}
}
}
Expand All @@ -192,15 +205,21 @@ internal class TokenRepositoryImpl(
)
}

private fun handleResponse(
private suspend fun handleResponse(
tokenType: String,
tokenResult: TokenResult<TokenDetails>,
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 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -13,7 +14,7 @@ internal object RiskInstanceProvider {
context: Context,
publicKey: String,
environment: Environment,
correlationId: String,
framesOptions: FramesOptions,
): Risk? {
if (riskInstance != null) {
return riskInstance
Expand All @@ -31,8 +32,7 @@ internal object RiskInstanceProvider {
RiskConfig(
publicKey = publicKey,
environment = riskEnvironment,
framesMode = true,
correlationId = correlationId,
framesOptions = framesOptions,
),
)
return riskInstance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TokenResult<String>, Unit> {
override fun execute(data: TokenResult<String>) {
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<String>) {
val riskInstance = riskInstanceProvider.provide(context, publicKey, environment, framesOptions)
when (data) {
is TokenResult.Success -> riskInstance?.publishData(cardToken = data.result)
is TokenResult.Failure -> {}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -28,23 +32,23 @@ 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(
environment = environment,
context = context,
publicKey = PUBLIC_KEY,
riskInstanceProvider = riskInstanceProvider,
correlationId = correlationId,
framesOptions = framesOptions,
)
}

@Test
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) }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -57,7 +59,7 @@ internal class TokenRepositoryImplTest {
private lateinit var mockValidateTokenizationDataUseCase: UseCase<Card, ValidationResult<Unit>>

@RelaxedMockK
private lateinit var mockRiskSdkUseCase: UseCase<TokenResult<String>, Unit>
private lateinit var mockRiskSdkUseCase: RiskSdkUseCase

@RelaxedMockK
private lateinit var mockValidateCVVTokenizationDataUseCase:
Expand Down Expand Up @@ -197,7 +199,7 @@ internal class TokenRepositoryImplTest {
onFailure = { isSuccess = false },
),
)

advanceUntilIdle()
// Then
launch {
if (successHandlerInvoked) {
Expand Down Expand Up @@ -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))
Expand All @@ -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<TokenDetailsResponse>) =
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) =
Expand Down

0 comments on commit ad6a6f8

Please sign in to comment.