From c83081cc1127f6a35a5e7744a62a8855168f8f0a Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 11 Feb 2025 15:18:31 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tap/domain/visa/VisaCardScanHandler.kt | 17 ++- .../data/visa/MockVisaActivationRepository.kt | 2 +- .../visa/model/VisaActivationOrderInfo.kt | 11 ++ .../visa/model/VisaActivationRemoteState.kt | 25 ++-- .../visa/model/VisaCardActivationStatus.kt | 4 + .../model/VisaCardWalletDataToSignRequest.kt | 12 +- .../visa/model/VisaDataToSignByCardWallet.kt | 3 + .../impl/DefaultOnboardingVisaComponent.kt | 36 ++++-- .../OnboardingVisaAccessCodeComponent.kt | 13 +- .../model/OnboardingVisaAccessCodeModel.kt | 54 +------- .../approve/OnboardingVisaApproveComponent.kt | 5 +- .../model/OnboardingVisaApproveModel.kt | 15 ++- .../model/OnboardingVisaInProgressModel.kt | 2 +- .../OnboardingVisaOtherWalletComponent.kt | 4 +- .../model/OnboardingVisaOtherWalletModel.kt | 2 +- .../pincode/OnboardingVisaPinCodeComponent.kt | 3 +- .../model/OnboardingVisaPinCodeModel.kt | 2 +- .../welcome/OnboardingVisaWelcomeComponent.kt | 52 ++++++-- .../model/OnboardingVisaWelcomeModel.kt | 116 +++++++++++++++++ .../child/welcome/ui/OnboardingVisaWelcome.kt | 3 +- .../ui/state/OnboardingVisaWelcomeUM.kt | 1 + .../visa/impl/common/ActivationReadyEvent.kt | 10 ++ .../impl/common/PreparationDataForApprove.kt | 10 ++ .../v2/visa/impl/di/ComponentModule.kt | 6 + .../v2/visa/impl/model/OnboardingVisaModel.kt | 120 +++++++++++++----- .../v2/visa/impl/route/OnboardingVisaRoute.kt | 28 +++- .../onboarding/v2/visa/impl/route/Step.kt | 4 +- .../onboarding/v2/visa/impl/route/Titles.kt | 4 +- 28 files changed, 411 insertions(+), 153 deletions(-) create mode 100644 domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationOrderInfo.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/model/OnboardingVisaWelcomeModel.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/common/ActivationReadyEvent.kt create mode 100644 features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/common/PreparationDataForApprove.kt diff --git a/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt b/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt index 1ef93cd25d..ad7336846e 100644 --- a/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt +++ b/app/src/main/java/com/tangem/tap/domain/visa/VisaCardScanHandler.kt @@ -33,7 +33,6 @@ internal class VisaCardScanHandler @Inject constructor( private class SessionContext( val visaActivationRepository: VisaActivationRepository, - val cardId: String, val session: CardSession, ) @@ -54,7 +53,6 @@ internal class VisaCardScanHandler @Inject constructor( val context = SessionContext( visaActivationRepository = visaActivationRepository, - cardId = card.cardId, session = session, ) @@ -140,7 +138,9 @@ internal class VisaCardScanHandler @Inject constructor( Timber.i( "Failed to get Access token for Wallet public key authoziation. Authorizing using Card Pub key", ) - return handleCardAuthorization() + return handleCardAuthorization( + cardWalletAddress = walletAddress.value, + ) } val signChallengeResult = signChallengeWithWallet( @@ -153,6 +153,7 @@ internal class VisaCardScanHandler @Inject constructor( is CompletionResult.Success -> { Timber.i("Challenge signed with Wallet public key") handleWalletAuthorizationTokens( + cardWalletAddress = walletAddress.value, signedChallenge = challengeResponse .toSignedChallenge(signChallengeResult.data.signature.toHexString()), ) @@ -165,6 +166,7 @@ internal class VisaCardScanHandler @Inject constructor( } private suspend fun SessionContext.handleWalletAuthorizationTokens( + cardWalletAddress: String, signedChallenge: VisaAuthSignedChallenge, ): CompletionResult { val authorizationTokensResponse = runCatching { @@ -173,7 +175,9 @@ internal class VisaCardScanHandler @Inject constructor( Timber.i( "Failed to get Access token for Wallet public key authoziation. Authorizing using Card Pub key", ) - return handleCardAuthorization() + return handleCardAuthorization( + cardWalletAddress = cardWalletAddress, + ) } Timber.i("Authorized using Wallet public key successfully") @@ -181,7 +185,9 @@ internal class VisaCardScanHandler @Inject constructor( return CompletionResult.Success(VisaCardActivationStatus.Activated(authorizationTokensResponse)) } - private suspend fun SessionContext.handleCardAuthorization(): CompletionResult { + private suspend fun SessionContext.handleCardAuthorization( + cardWalletAddress: String, + ): CompletionResult { val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) Timber.i("Requesting authorization challenge to sign") @@ -258,6 +264,7 @@ internal class VisaCardScanHandler @Inject constructor( activationInput = activationInput, authTokens = authorizationTokensResponse, remoteState = activationRemoteState, + cardWalletAddress = cardWalletAddress, ), ) } diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt index 4862fb729c..8ebb4ae1b4 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt @@ -18,7 +18,7 @@ class MockVisaActivationRepository @AssistedInject constructor( } override suspend fun getActivationRemoteStateLongPoll(): VisaActivationRemoteState { - return VisaActivationRemoteState.WaitingPinCode + return VisaActivationRemoteState.PaymentAccountDeploying } override suspend fun getCardWalletAcceptanceData( diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationOrderInfo.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationOrderInfo.kt new file mode 100644 index 0000000000..b8320e7678 --- /dev/null +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationOrderInfo.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.visa.model + +import com.squareup.moshi.Json +import kotlinx.serialization.Serializable + +@Serializable +data class VisaActivationOrderInfo( + @Json(name = "orderId") val orderId: String, + @Json(name = "customer_id") val customerId: String, + @Json(name = "customer_wallet_address") val customerWalletAddress: String, +) \ No newline at end of file diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationRemoteState.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationRemoteState.kt index 5b2c197c56..88e4389287 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationRemoteState.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaActivationRemoteState.kt @@ -13,14 +13,16 @@ sealed class VisaActivationRemoteState { @Serializable data class CustomerWalletSignatureRequired( - val request: VisaCardWalletDataToSignRequest, + val activationOrderInfo: VisaActivationOrderInfo, ) : VisaActivationRemoteState() @Serializable data object PaymentAccountDeploying : VisaActivationRemoteState() @Serializable - data object WaitingPinCode : VisaActivationRemoteState() + data class WaitingPinCode( + val activationOrderInfo: VisaActivationOrderInfo, + ) : VisaActivationRemoteState() @Serializable data object WaitingForActivationFinishing : VisaActivationRemoteState() @@ -39,7 +41,8 @@ sealed class VisaActivationRemoteState { @Suppress("ClassNaming") class VisaActivationRemoteState_Json( @Json(name = "type") val type: VisaActivationRemoteState_Type, - @Json(name = "request") val request: VisaCardWalletDataToSignRequest? = null, + @Json(name = "requestCardWallet") val requestCardWallet: VisaCardWalletDataToSignRequest? = null, + @Json(name = "activationOrderInfo") val activationOrderInfo: VisaActivationOrderInfo? = null, ) @Suppress("ClassNaming") @@ -62,11 +65,12 @@ class VisaActivationRemoteState_JsonAdapter { fun fromJson(value: VisaActivationRemoteState_Json): VisaActivationRemoteState { return when (value.type) { VisaActivationRemoteState_Type.CardWalletSignatureRequired -> - VisaActivationRemoteState.CardWalletSignatureRequired(value.request!!) + VisaActivationRemoteState.CardWalletSignatureRequired(value.requestCardWallet!!) VisaActivationRemoteState_Type.CustomerWalletSignatureRequired -> - VisaActivationRemoteState.CustomerWalletSignatureRequired(value.request!!) + VisaActivationRemoteState.CustomerWalletSignatureRequired(value.activationOrderInfo!!) VisaActivationRemoteState_Type.PaymentAccountDeploying -> VisaActivationRemoteState.PaymentAccountDeploying - VisaActivationRemoteState_Type.WaitingPinCode -> VisaActivationRemoteState.WaitingPinCode + VisaActivationRemoteState_Type.WaitingPinCode -> + VisaActivationRemoteState.WaitingPinCode(value.activationOrderInfo!!) VisaActivationRemoteState_Type.WaitingForActivationFinishing -> VisaActivationRemoteState.WaitingForActivationFinishing VisaActivationRemoteState_Type.Activated -> VisaActivationRemoteState.Activated @@ -80,17 +84,20 @@ class VisaActivationRemoteState_JsonAdapter { is VisaActivationRemoteState.CardWalletSignatureRequired -> VisaActivationRemoteState_Json( type = VisaActivationRemoteState_Type.CardWalletSignatureRequired, - request = value.request, + requestCardWallet = value.request, ) is VisaActivationRemoteState.CustomerWalletSignatureRequired -> VisaActivationRemoteState_Json( type = VisaActivationRemoteState_Type.CustomerWalletSignatureRequired, - request = value.request, + activationOrderInfo = value.activationOrderInfo, ) is VisaActivationRemoteState.PaymentAccountDeploying -> VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.PaymentAccountDeploying) is VisaActivationRemoteState.WaitingPinCode -> - VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.WaitingPinCode) + VisaActivationRemoteState_Json( + VisaActivationRemoteState_Type.WaitingPinCode, + activationOrderInfo = value.activationOrderInfo, + ) is VisaActivationRemoteState.WaitingForActivationFinishing -> VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.WaitingForActivationFinishing) is VisaActivationRemoteState.Activated -> VisaActivationRemoteState_Json( diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCardActivationStatus.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCardActivationStatus.kt index 833bbd8ccd..ffaa9c6520 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCardActivationStatus.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCardActivationStatus.kt @@ -19,6 +19,7 @@ sealed class VisaCardActivationStatus { val activationInput: VisaActivationInput, val authTokens: VisaAuthTokens, val remoteState: VisaActivationRemoteState, + val cardWalletAddress: String, ) : VisaCardActivationStatus() @Serializable @@ -43,6 +44,7 @@ class VisaCardActivationStatus_Json( @Json(name = "activationInput") val activationInput: VisaActivationInput? = null, @Json(name = "authTokens") val authTokens: VisaAuthTokens? = null, @Json(name = "remoteState") val remoteState: VisaActivationRemoteState? = null, + @Json(name = "cardWalletAddress") val cardWalletAddress: String? = null, ) // do not rename instances @@ -67,6 +69,7 @@ class VisaCardActivationStatus_JsonAdapter { value.activationInput!!, value.authTokens!!, value.remoteState!!, + value.cardWalletAddress!!, ) VisaCardActivationStatus_Type.NotStartedActivation -> VisaCardActivationStatus.NotStartedActivation( value.activationInput!!, @@ -88,6 +91,7 @@ class VisaCardActivationStatus_JsonAdapter { value.activationInput, value.authTokens, value.remoteState, + value.cardWalletAddress, ) is VisaCardActivationStatus.NotStartedActivation -> VisaCardActivationStatus_Json( VisaCardActivationStatus_Type.NotStartedActivation, diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCardWalletDataToSignRequest.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCardWalletDataToSignRequest.kt index a704090318..831a4b1d6c 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCardWalletDataToSignRequest.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCardWalletDataToSignRequest.kt @@ -1,13 +1,3 @@ package com.tangem.domain.visa.model -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import kotlinx.serialization.Serializable - -@Serializable -@JsonClass(generateAdapter = true) -data class VisaCardWalletDataToSignRequest( - @Json(name = "orderId") val orderId: String, - @Json(name = "customer_id") val customerId: String, - @Json(name = "customer_wallet_address") val customerWalletAddress: String, -) \ No newline at end of file +typealias VisaCardWalletDataToSignRequest = VisaActivationOrderInfo \ No newline at end of file diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataToSignByCardWallet.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataToSignByCardWallet.kt index 420aa0ea53..9e95f713e1 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataToSignByCardWallet.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataToSignByCardWallet.kt @@ -1,5 +1,8 @@ package com.tangem.domain.visa.model +import kotlinx.serialization.Serializable + +@Serializable data class VisaDataToSignByCardWallet( val request: VisaCardWalletDataToSignRequest, val hashToSign: String, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt index 1e987bf695..41a398c3a4 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/DefaultOnboardingVisaComponent.kt @@ -96,17 +96,36 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( return when (route) { is OnboardingVisaRoute.Welcome -> OnboardingVisaWelcomeComponent( appComponentContext = factoryContext, + config = OnboardingVisaWelcomeComponent.Config.Welcome, params = OnboardingVisaWelcomeComponent.Params( - isWelcomeBack = route.isWelcomeBack, childParams = childParams, - onDone = { model.navigateFromWelcome(route) }, + onDone = { model.stackNavigation.push(OnboardingVisaRoute.AccessCode) }, + ), + ) + is OnboardingVisaRoute.WelcomeBack -> OnboardingVisaWelcomeComponent( + appComponentContext = factoryContext, + config = OnboardingVisaWelcomeComponent.Config.WelcomeBack( + activationInput = route.activationInput, + dataToSignRequest = route.dataToSignByCardWalletRequest, + scanResponse = model.currentScanResponse.value, + ), + params = OnboardingVisaWelcomeComponent.Params( + childParams = childParams, + onDone = { + val activationReadyEvent = + (it as? OnboardingVisaWelcomeComponent.DoneEvent.WelcomeBackDone)?.activationReadyEvent + + if (activationReadyEvent != null) { + model.navigateFromActivationScreen(activationReadyEvent) + } + }, ), ) OnboardingVisaRoute.AccessCode -> OnboardingVisaAccessCodeComponent( appComponentContext = factoryContext, params = OnboardingVisaAccessCodeComponent.Params( childParams = childParams, - onDone = { model.navigateFromAccessCode(it) }, + onDone = { model.navigateFromActivationScreen(it) }, ), config = OnboardingVisaAccessCodeComponent.Config( scanResponse = model.currentScanResponse.value, @@ -129,7 +148,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( onDone = { model.stackNavigation.push( OnboardingVisaRoute.PinCode( - activationOrderId = "TODO", // TODO [REDACTED_TASK_KEY] [Visa] In progress screen after pin code + activationOrderInfo = TODO(), // TODO [REDACTED_TASK_KEY] [Visa] In progress screen after pin code ), ) }, @@ -139,14 +158,14 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( appComponentContext = factoryContext, config = OnboardingVisaOtherWalletComponent.Config( scanResponse = model.currentScanResponse.value, - visaDataForApprove = route.visaDataForApprove, + preparationDataForApprove = route.preparationDataForApprove, ), params = OnboardingVisaOtherWalletComponent.Params( childParams = childParams, onDone = { model.stackNavigation.push( OnboardingVisaRoute.PinCode( - activationOrderId = route.visaDataForApprove.dataToSign.request.orderId, + activationOrderInfo = TODO(), ), ) }, @@ -156,7 +175,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( appComponentContext = factoryContext, config = OnboardingVisaPinCodeComponent.Config( scanResponse = model.currentScanResponse.value, - activationOrderId = route.activationOrderId, + activationOrderInfo = route.activationOrderInfo, ), params = OnboardingVisaPinCodeComponent.Params( childParams = childParams, @@ -166,7 +185,8 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( is OnboardingVisaRoute.TangemWalletApproveOption -> OnboardingVisaApproveComponent( appComponentContext = factoryContext, config = OnboardingVisaApproveComponent.Config( - visaDataForApprove = route.visaDataForApprove, + customerWalletCardId = route.foundWalletCardId, + preparationDataForApprove = route.preparationDataForApprove, scanResponse = model.currentScanResponse.value, ), params = OnboardingVisaApproveComponent.Params( diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt index 5a8b17a5c2..a67c18c534 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/OnboardingVisaAccessCodeComponent.kt @@ -8,11 +8,12 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.model.OnboardingVisaAccessCodeModel import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.OnboardingVisaAccessCode +import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent import kotlinx.coroutines.launch internal class OnboardingVisaAccessCodeComponent( @@ -43,7 +44,7 @@ internal class OnboardingVisaAccessCodeComponent( BackHandler(onBack = model::onBack) - // DisableScreenshotsDisposableEffect() + DisableScreenshotsDisposableEffect() OnboardingVisaAccessCode(state, modifier) } @@ -54,12 +55,6 @@ internal class OnboardingVisaAccessCodeComponent( data class Params( val childParams: DefaultOnboardingVisaComponent.ChildParams, - val onDone: (DoneEvent) -> Unit, - ) - - data class DoneEvent( - val visaDataForApprove: VisaDataForApprove, - val walletFound: Boolean, - val newScanResponse: ScanResponse, + val onDone: (ActivationReadyEvent) -> Unit, ) } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt index 91a0d88b81..2f56bd8ddb 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/accesscode/model/OnboardingVisaAccessCodeModel.kt @@ -7,17 +7,14 @@ import com.tangem.common.extensions.toHexString import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer -import com.tangem.domain.common.visa.VisaUtilities -import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility import com.tangem.domain.visa.model.VisaCardActivationStatus import com.tangem.domain.visa.model.VisaCardId import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest -import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaAuthRepository -import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.OnboardingVisaAccessCodeComponent import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.state.OnboardingVisaAccessCodeUM +import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent import com.tangem.sdk.api.TangemSdkManager import com.tangem.sdk.api.visa.VisaCardActivationTaskMode import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -37,7 +34,6 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor( @Suppress("UnusedPrivateMember") private val tangemSdkManager: TangemSdkManager, private val visaAuthRepository: VisaAuthRepository, - private val getWalletsUseCase: GetWalletsUseCase, ) : Model() { private val params: OnboardingVisaAccessCodeComponent.Config = paramsContainer.require() @@ -55,7 +51,7 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor( val uiState = _uiState.asStateFlow() val onBack = MutableSharedFlow() - val onDone = MutableSharedFlow() + val onDone = MutableSharedFlow() fun onBack() { if (uiState.value.buttonLoading) return @@ -166,61 +162,25 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor( return@launch } - // load data to sign by customer wallet for the next step - val dataToSign = runCatching { - visaActivationRepository.getCustomerWalletAcceptanceData( - VisaCustomerWalletDataToSignRequest( - orderId = result.data.signedActivationData.dataToSign.request.orderId, - cardWalletAddress = result.data.signedActivationData.cardWalletAddress, - ), - ) - }.getOrElse { - loading(false) - // TODO show alert - return@launch - } - val targetAddress = result.data.signedActivationData.dataToSign.request.customerWalletAddress - val foundCardId = tryToFindExistingWalletCardId(targetAddress) modelScope.launch { onDone.emit( - OnboardingVisaAccessCodeComponent.DoneEvent( - visaDataForApprove = VisaDataForApprove( - targetAddress = targetAddress, - customerWalletCardId = foundCardId, - dataToSign = dataToSign, + ActivationReadyEvent( + customerWalletDataToSignRequest = VisaCustomerWalletDataToSignRequest( + orderId = result.data.signedActivationData.dataToSign.request.orderId, + cardWalletAddress = result.data.signedActivationData.cardWalletAddress, ), - walletFound = foundCardId != null, newScanResponse = params.scanResponse.copy( card = result.data.newCardDTO, ), + customerWalletTargetAddress = targetAddress, ), ) } } } - private fun tryToFindExistingWalletCardId(targetAddress: String): String? { - val wallets = getWalletsUseCase.invokeSync().filter { it.isLocked.not() } - - return wallets.firstOrNull { wallet -> - wallet.scanResponse.card.wallets.any { - val derivedKey = it.derivedKeys[VisaUtilities.visaDefaultDerivationPath] ?: return@any false - - VisaWalletPublicKeyUtility.validateExtendedPublicKey( - targetAddress = targetAddress, - extendedPublicKey = derivedKey, - ).onLeft { - return@any VisaWalletPublicKeyUtility.findKeyWithoutDerivation( - targetAddress = targetAddress, - card = wallet.scanResponse.card, - ).isRight() - }.isRight() - } - }?.cardId - } - private fun loading(state: Boolean) { _uiState.update { it.copy(buttonLoading = state) } } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/OnboardingVisaApproveComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/OnboardingVisaApproveComponent.kt index 758b6af4af..4a30f7f280 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/OnboardingVisaApproveComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/OnboardingVisaApproveComponent.kt @@ -10,10 +10,10 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.child.approve.model.OnboardingVisaApproveModel import com.tangem.features.onboarding.v2.visa.impl.child.approve.ui.OnboardingVisaApprove +import com.tangem.features.onboarding.v2.visa.impl.common.PreparationDataForApprove import kotlinx.coroutines.launch internal class OnboardingVisaApproveComponent( @@ -43,7 +43,8 @@ internal class OnboardingVisaApproveComponent( } data class Config( - val visaDataForApprove: VisaDataForApprove, + val customerWalletCardId: String?, + val preparationDataForApprove: PreparationDataForApprove, val scanResponse: ScanResponse, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/model/OnboardingVisaApproveModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/model/OnboardingVisaApproveModel.kt index ee97c99fc5..245dbb83cb 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/model/OnboardingVisaApproveModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/approve/model/OnboardingVisaApproveModel.kt @@ -7,6 +7,7 @@ import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.domain.visa.model.VisaCardId +import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.features.onboarding.v2.visa.impl.child.approve.OnboardingVisaApproveComponent import com.tangem.features.onboarding.v2.visa.impl.child.approve.ui.state.OnboardingVisaApproveUM @@ -51,8 +52,20 @@ internal class OnboardingVisaApproveModel @Inject constructor( loading(true) modelScope.launch { + val dataToSign = runCatching { + visaActivationRepository.getCustomerWalletAcceptanceData(params.preparationDataForApprove.request) + }.getOrElse { + loading(false) + // TODO show dialog + return@launch + } + val result = tangemSdkManager.visaCustomerWalletApprove( - visaDataForApprove = params.visaDataForApprove, + visaDataForApprove = VisaDataForApprove( + customerWalletCardId = params.customerWalletCardId, + targetAddress = params.preparationDataForApprove.customerWalletAddress, + dataToSign = dataToSign, + ), ) as? CompletionResult.Success ?: run { loading(false) // TODO show dialog diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt index bb6efae8f1..6e25512a2f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/inprogress/model/OnboardingVisaInProgressModel.kt @@ -39,7 +39,7 @@ internal class OnboardingVisaInProgressModel @Inject constructor( visaActivationRepository.getActivationRemoteStateLongPoll() }.getOrNull() - if (result == VisaActivationRemoteState.WaitingPinCode) { + if (result is VisaActivationRemoteState.WaitingPinCode) { onDone.emit(Unit) break } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/OnboardingVisaOtherWalletComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/OnboardingVisaOtherWalletComponent.kt index a9d2297b5f..7c8916e9e3 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/OnboardingVisaOtherWalletComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/OnboardingVisaOtherWalletComponent.kt @@ -10,10 +10,10 @@ import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model.OnboardingVisaOtherWalletModel import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.OnboardingVisaOtherWallet +import com.tangem.features.onboarding.v2.visa.impl.common.PreparationDataForApprove import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -45,7 +45,7 @@ internal class OnboardingVisaOtherWalletComponent( data class Config( val scanResponse: ScanResponse, - val visaDataForApprove: VisaDataForApprove, + val preparationDataForApprove: PreparationDataForApprove, ) data class Params( diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/model/OnboardingVisaOtherWalletModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/model/OnboardingVisaOtherWalletModel.kt index 7b1b3d44bf..77bfa10953 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/model/OnboardingVisaOtherWalletModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/otherwallet/model/OnboardingVisaOtherWalletModel.kt @@ -49,7 +49,7 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor( visaActivationRepository.getActivationRemoteStateLongPoll() }.getOrNull() - if (result == VisaActivationRemoteState.WaitingPinCode) { + if (result is VisaActivationRemoteState.WaitingPinCode) { onDone.emit(Unit) break } diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt index e6840ae79b..b70d2995a1 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/OnboardingVisaPinCodeComponent.kt @@ -11,6 +11,7 @@ import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.visa.model.VisaActivationOrderInfo import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.child.pincode.model.OnboardingVisaPinCodeModel import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.OnboardingVisaPinCode @@ -46,7 +47,7 @@ internal class OnboardingVisaPinCodeComponent( data class Config( val scanResponse: ScanResponse, - val activationOrderId: String, + val activationOrderInfo: VisaActivationOrderInfo, ) data class Params( diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt index 771cb7dfe7..deae6ff93c 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/OnboardingVisaPinCodeModel.kt @@ -76,7 +76,7 @@ internal class OnboardingVisaPinCodeModel @Inject constructor( setVisaPinCodeUseCase( pinCode = _uiState.value.pinCode, visaCardId = visaCardId, - activationOrderId = params.activationOrderId, + activationOrderId = params.activationOrderInfo.orderId, ).onLeft { loading(false) return@launch diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/OnboardingVisaWelcomeComponent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/OnboardingVisaWelcomeComponent.kt index 95264e1199..fe9cd7afa3 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/OnboardingVisaWelcomeComponent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/OnboardingVisaWelcomeComponent.kt @@ -2,32 +2,39 @@ package com.tangem.features.onboarding.v2.visa.impl.child.welcome import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.decompose.ComposableContentComponent +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.visa.model.VisaActivationInput +import com.tangem.domain.visa.model.VisaCardWalletDataToSignRequest import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent +import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.OnboardingVisaWelcomeModel import com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.OnboardingVisaWelcome -import com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.state.OnboardingVisaWelcomeUM +import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent +import kotlinx.coroutines.launch internal class OnboardingVisaWelcomeComponent( appComponentContext: AppComponentContext, + config: Config, private val params: Params, ) : ComposableContentComponent, AppComponentContext by appComponentContext { + private val model: OnboardingVisaWelcomeModel = getOrCreateModel(config) + + init { + componentScope.launch { + model.onDone.collect { params.onDone(it) } + } + } + @Composable override fun Content(modifier: Modifier) { - val state = remember(this) { - OnboardingVisaWelcomeUM( - mode = if (params.isWelcomeBack) { - OnboardingVisaWelcomeUM.Mode.WelcomeBack - } else { - OnboardingVisaWelcomeUM.Mode.Hello - }, - userName = "User", // TODO: - onContinueClick = params.onDone, - ) - } + val state by model.uiState.collectAsStateWithLifecycle() BackHandler(onBack = remember(this) { { params.childParams.onBack() } }) @@ -37,9 +44,26 @@ internal class OnboardingVisaWelcomeComponent( ) } + sealed class Config { + data object Welcome : Config() + + data class WelcomeBack( + val activationInput: VisaActivationInput, + val dataToSignRequest: VisaCardWalletDataToSignRequest, + val scanResponse: ScanResponse, + ) : Config() + } + data class Params( val childParams: DefaultOnboardingVisaComponent.ChildParams, - val isWelcomeBack: Boolean, - val onDone: () -> Unit, + val onDone: (DoneEvent) -> Unit, ) + + sealed class DoneEvent { + data object WelcomeDone : DoneEvent() + + data class WelcomeBackDone( + val activationReadyEvent: ActivationReadyEvent, + ) : DoneEvent() + } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/model/OnboardingVisaWelcomeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/model/OnboardingVisaWelcomeModel.kt new file mode 100644 index 0000000000..526e51806f --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/model/OnboardingVisaWelcomeModel.kt @@ -0,0 +1,116 @@ +package com.tangem.features.onboarding.v2.visa.impl.child.welcome.model + +import com.tangem.common.CompletionResult +import com.tangem.common.extensions.toHexString +import com.tangem.core.decompose.di.ComponentScoped +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.visa.model.VisaCardId +import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest +import com.tangem.domain.visa.repository.VisaActivationRepository +import com.tangem.features.onboarding.v2.visa.impl.child.welcome.OnboardingVisaWelcomeComponent.Config +import com.tangem.features.onboarding.v2.visa.impl.child.welcome.OnboardingVisaWelcomeComponent.DoneEvent +import com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.state.OnboardingVisaWelcomeUM +import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent +import com.tangem.sdk.api.TangemSdkManager +import com.tangem.sdk.api.visa.VisaCardActivationTaskMode +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@ComponentScoped +internal class OnboardingVisaWelcomeModel @Inject constructor( + paramsContainer: ParamsContainer, + override val dispatchers: CoroutineDispatcherProvider, + private val tangemSdkManager: TangemSdkManager, + private val visaActivationRepositoryFactory: VisaActivationRepository.Factory, +) : Model() { + + private val params = paramsContainer.require() + + private val _uiState = MutableStateFlow(getInitialState()) + + val uiState = _uiState.asStateFlow() + val onDone = MutableSharedFlow() + + private fun getInitialState(): OnboardingVisaWelcomeUM { + return OnboardingVisaWelcomeUM( + mode = if (params is Config.WelcomeBack) { + OnboardingVisaWelcomeUM.Mode.WelcomeBack + } else { + OnboardingVisaWelcomeUM.Mode.Hello + }, + onContinueClick = ::onContinueClick, + ) + } + + private fun onContinueClick() { + if (params !is Config.WelcomeBack) { + modelScope.launch { onDone.emit(DoneEvent.WelcomeDone) } + return + } + + val visaActivationRepository = visaActivationRepositoryFactory.create( + VisaCardId( + cardId = params.scanResponse.card.cardId, + cardPublicKey = params.scanResponse.card.cardPublicKey.toHexString(), + ), + ) + + loading(true) + + modelScope.launch { + val dataToSignByCardWallet = runCatching { + visaActivationRepository.getCardWalletAcceptanceData(params.dataToSignRequest) + }.getOrElse { + loading(false) + // TODO show alert + return@launch + } + + val result = tangemSdkManager.activateVisaCard( + mode = VisaCardActivationTaskMode.SignOnly(dataToSignByCardWallet = dataToSignByCardWallet), + activationInput = params.activationInput, + ) as? CompletionResult.Success ?: run { + loading(false) + // TODO show alert + return@launch + } + + runCatching { + visaActivationRepository.activateCard(result.data.signedActivationData) + }.onFailure { + loading(false) + // TODO show alert + return@launch + } + + val targetAddress = result.data.signedActivationData.dataToSign.request.customerWalletAddress + + modelScope.launch { + onDone.emit( + DoneEvent.WelcomeBackDone( + ActivationReadyEvent( + customerWalletDataToSignRequest = VisaCustomerWalletDataToSignRequest( + orderId = result.data.signedActivationData.dataToSign.request.orderId, + cardWalletAddress = result.data.signedActivationData.cardWalletAddress, + ), + newScanResponse = params.scanResponse.copy( + card = result.data.newCardDTO, + ), + customerWalletTargetAddress = targetAddress, + ), + ), + ) + } + } + } + + private fun loading(state: Boolean) { + _uiState.update { it.copy(continueButtonLoading = state) } + } +} \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/OnboardingVisaWelcome.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/OnboardingVisaWelcome.kt index d21c1a2f43..0a209235b0 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/OnboardingVisaWelcome.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/OnboardingVisaWelcome.kt @@ -81,7 +81,8 @@ internal fun OnboardingVisaWelcome(state: OnboardingVisaWelcomeUM, modifier: Mod modifier = Modifier .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) .fillMaxWidth(), - text = "Start Activation", + text = "Continue Activation", + showProgress = state.continueButtonLoading, iconResId = R.drawable.ic_tangem_24, onClick = state.onContinueClick, ) diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/state/OnboardingVisaWelcomeUM.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/state/OnboardingVisaWelcomeUM.kt index 9e1f6ba81b..d1f975f9b3 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/state/OnboardingVisaWelcomeUM.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/ui/state/OnboardingVisaWelcomeUM.kt @@ -3,6 +3,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.state internal data class OnboardingVisaWelcomeUM( val mode: Mode = Mode.Hello, val userName: String = "", + val continueButtonLoading: Boolean = false, val onContinueClick: () -> Unit = {}, ) { enum class Mode { diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/common/ActivationReadyEvent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/common/ActivationReadyEvent.kt new file mode 100644 index 0000000000..d46bca7a3d --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/common/ActivationReadyEvent.kt @@ -0,0 +1,10 @@ +package com.tangem.features.onboarding.v2.visa.impl.common + +import com.tangem.domain.models.scan.ScanResponse +import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest + +internal data class ActivationReadyEvent( + val customerWalletDataToSignRequest: VisaCustomerWalletDataToSignRequest, + val customerWalletTargetAddress: String, + val newScanResponse: ScanResponse, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/common/PreparationDataForApprove.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/common/PreparationDataForApprove.kt new file mode 100644 index 0000000000..a1bd5e8271 --- /dev/null +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/common/PreparationDataForApprove.kt @@ -0,0 +1,10 @@ +package com.tangem.features.onboarding.v2.visa.impl.common + +import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest +import kotlinx.serialization.Serializable + +@Serializable +internal data class PreparationDataForApprove( + val customerWalletAddress: String, + val request: VisaCustomerWalletDataToSignRequest, +) \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/di/ComponentModule.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/di/ComponentModule.kt index 5ee550c847..15349b614b 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/di/ComponentModule.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/di/ComponentModule.kt @@ -10,6 +10,7 @@ import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.model.Onbo import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model.OnboardingVisaInProgressModel import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model.OnboardingVisaOtherWalletModel import com.tangem.features.onboarding.v2.visa.impl.child.pincode.model.OnboardingVisaPinCodeModel +import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.OnboardingVisaWelcomeModel import com.tangem.features.onboarding.v2.visa.impl.model.OnboardingVisaModel import dagger.Binds import dagger.Module @@ -66,4 +67,9 @@ internal interface ModelModule { @IntoMap @ClassKey(OnboardingVisaModel::class) fun provideModel7(model: OnboardingVisaModel): Model + + @Binds + @IntoMap + @ClassKey(OnboardingVisaWelcomeModel::class) + fun provideModel8(model: OnboardingVisaWelcomeModel): Model } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt index e65980a929..b740819d0a 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/model/OnboardingVisaModel.kt @@ -7,12 +7,17 @@ import com.arkivanov.decompose.router.stack.push import com.tangem.core.decompose.di.ComponentScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.domain.common.visa.VisaUtilities +import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility import com.tangem.domain.visa.model.VisaActivationRemoteState import com.tangem.domain.visa.model.VisaCardActivationStatus +import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest +import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onboarding.v2.visa.api.OnboardingVisaComponent import com.tangem.features.onboarding.v2.visa.impl.OnboardingVisaInnerNavigationState -import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.OnboardingVisaAccessCodeComponent import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.OnboardingVisaChooseWalletComponent +import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent +import com.tangem.features.onboarding.v2.visa.impl.common.PreparationDataForApprove import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute import com.tangem.features.onboarding.v2.visa.impl.route.stepNum import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -25,6 +30,7 @@ import javax.inject.Inject internal class OnboardingVisaModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, + private val getWalletsUseCase: GetWalletsUseCase, ) : Model() { @Suppress("UnusedPrivateMember") @@ -49,37 +55,24 @@ internal class OnboardingVisaModel @Inject constructor( ) } - fun navigateFromWelcome(route: OnboardingVisaRoute.Welcome) { - if (route.isWelcomeBack) { - val scanResponse = _currentScanResponse.value - val activationStatus = scanResponse.visaCardActivationStatus - as? VisaCardActivationStatus.ActivationStarted - ?: error("Activation status is not correct for welcome back route") - - when (activationStatus.remoteState) { - is VisaActivationRemoteState.CardWalletSignatureRequired, - is VisaActivationRemoteState.CustomerWalletSignatureRequired, - -> OnboardingVisaRoute.AccessCode - VisaActivationRemoteState.WaitingPinCode -> OnboardingVisaRoute.PinCode - VisaActivationRemoteState.WaitingForActivationFinishing -> OnboardingVisaRoute.InProgress - VisaActivationRemoteState.PaymentAccountDeploying -> OnboardingVisaRoute.InProgress - else -> error("Remote state is not correct for welcome back route") - } - } else { - stackNavigation.push(OnboardingVisaRoute.AccessCode) - } - } - - fun navigateFromAccessCode(result: OnboardingVisaAccessCodeComponent.DoneEvent) { + fun navigateFromActivationScreen(result: ActivationReadyEvent) { _currentScanResponse.value = result.newScanResponse + val foundWalletCardId = tryToFindExistingWalletCardId(result.customerWalletTargetAddress) + val preparationDataForApprove = PreparationDataForApprove( + customerWalletAddress = result.customerWalletTargetAddress, + request = result.customerWalletDataToSignRequest, + ) stackNavigation.push( - if (result.walletFound) { + if (foundWalletCardId != null) { OnboardingVisaRoute.TangemWalletApproveOption( - visaDataForApprove = result.visaDataForApprove, + foundWalletCardId = foundWalletCardId, + preparationDataForApprove = preparationDataForApprove, allowNavigateBack = false, ) } else { - OnboardingVisaRoute.ChooseWallet(result.visaDataForApprove) + OnboardingVisaRoute.ChooseWallet( + preparationDataForApprove = preparationDataForApprove, + ) }, ) } @@ -92,11 +85,14 @@ internal class OnboardingVisaModel @Inject constructor( when (event) { OnboardingVisaChooseWalletComponent.Params.Event.TangemWallet -> OnboardingVisaRoute.TangemWalletApproveOption( - visaDataForApprove = route.visaDataForApprove, + preparationDataForApprove = route.preparationDataForApprove, + foundWalletCardId = null, allowNavigateBack = true, ) OnboardingVisaChooseWalletComponent.Params.Event.OtherWallet -> - OnboardingVisaRoute.OtherWalletApproveOption(route.visaDataForApprove) + OnboardingVisaRoute.OtherWalletApproveOption( + preparationDataForApprove = route.preparationDataForApprove, + ) }, ) } @@ -116,6 +112,7 @@ internal class OnboardingVisaModel @Inject constructor( stackNavigation.pop() } } + is OnboardingVisaRoute.WelcomeBack, is OnboardingVisaRoute.Welcome, is OnboardingVisaRoute.ChooseWallet, OnboardingVisaRoute.InProgress, @@ -129,9 +126,72 @@ internal class OnboardingVisaModel @Inject constructor( val activationStatus = params.scanResponse.visaCardActivationStatus return when (activationStatus) { - is VisaCardActivationStatus.ActivationStarted -> OnboardingVisaRoute.Welcome(isWelcomeBack = true) - is VisaCardActivationStatus.NotStartedActivation -> OnboardingVisaRoute.Welcome(isWelcomeBack = false) + is VisaCardActivationStatus.ActivationStarted -> { + when (val remoteState = activationStatus.remoteState) { + is VisaActivationRemoteState.CardWalletSignatureRequired -> OnboardingVisaRoute.WelcomeBack( + activationInput = activationStatus.activationInput, + dataToSignByCardWalletRequest = remoteState.request, + ) + is VisaActivationRemoteState.CustomerWalletSignatureRequired -> + remoteState.getRoute(activationStatus) + VisaActivationRemoteState.PaymentAccountDeploying -> OnboardingVisaRoute.InProgress // TODO + VisaActivationRemoteState.WaitingForActivationFinishing -> OnboardingVisaRoute.InProgress // TODO + is VisaActivationRemoteState.WaitingPinCode -> OnboardingVisaRoute.PinCode( + activationOrderInfo = remoteState.activationOrderInfo, + ) + VisaActivationRemoteState.Activated, + VisaActivationRemoteState.BlockedForActivation, + -> error("Activation status is not correct for onboarding flow") + } + } + is VisaCardActivationStatus.NotStartedActivation -> OnboardingVisaRoute.Welcome else -> error("Visa activation status is not correct for onboarding flow") } } + + private fun VisaActivationRemoteState.CustomerWalletSignatureRequired.getRoute( + activationStatus: VisaCardActivationStatus.ActivationStarted, + ): OnboardingVisaRoute { + val foundWalletCardId = tryToFindExistingWalletCardId(this.activationOrderInfo.customerWalletAddress) + val request = VisaCustomerWalletDataToSignRequest( + orderId = this.activationOrderInfo.orderId, + cardWalletAddress = activationStatus.cardWalletAddress, + ) + val preparationDataForApprove = PreparationDataForApprove( + customerWalletAddress = this.activationOrderInfo.customerWalletAddress, + request = request, + ) + + return if (foundWalletCardId != null) { + OnboardingVisaRoute.TangemWalletApproveOption( + preparationDataForApprove = preparationDataForApprove, + foundWalletCardId = foundWalletCardId, + allowNavigateBack = false, + ) + } else { + OnboardingVisaRoute.ChooseWallet( + preparationDataForApprove = preparationDataForApprove, + ) + } + } + + private fun tryToFindExistingWalletCardId(targetAddress: String): String? { + val wallets = getWalletsUseCase.invokeSync().filter { it.isLocked.not() } + + return wallets.firstOrNull { wallet -> + wallet.scanResponse.card.wallets.any { + val derivedKey = it.derivedKeys[VisaUtilities.visaDefaultDerivationPath] ?: return@any false + + VisaWalletPublicKeyUtility.validateExtendedPublicKey( + targetAddress = targetAddress, + extendedPublicKey = derivedKey, + ).onLeft { + return@any VisaWalletPublicKeyUtility.findKeyWithoutDerivation( + targetAddress = targetAddress, + card = wallet.scanResponse.card, + ).isRight() + }.isRight() + } + }?.cardId + } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/OnboardingVisaRoute.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/OnboardingVisaRoute.kt index 5abb23b300..8022e8db39 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/OnboardingVisaRoute.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/OnboardingVisaRoute.kt @@ -1,32 +1,46 @@ package com.tangem.features.onboarding.v2.visa.impl.route -import com.tangem.domain.visa.model.VisaDataForApprove +import com.tangem.domain.visa.model.VisaActivationInput +import com.tangem.domain.visa.model.VisaActivationOrderInfo +import com.tangem.domain.visa.model.VisaCardWalletDataToSignRequest +import com.tangem.features.onboarding.v2.visa.impl.common.PreparationDataForApprove import kotlinx.serialization.Serializable @Serializable -sealed class OnboardingVisaRoute { +internal sealed class OnboardingVisaRoute { @Serializable - data class Welcome(val isWelcomeBack: Boolean) : OnboardingVisaRoute() + data object Welcome : OnboardingVisaRoute() + + @Serializable + data class WelcomeBack( + val activationInput: VisaActivationInput, + val dataToSignByCardWalletRequest: VisaCardWalletDataToSignRequest, + ) : OnboardingVisaRoute() @Serializable data object AccessCode : OnboardingVisaRoute() @Serializable - data class ChooseWallet(val visaDataForApprove: VisaDataForApprove) : OnboardingVisaRoute() + data class ChooseWallet( + val preparationDataForApprove: PreparationDataForApprove, + ) : OnboardingVisaRoute() @Serializable data class TangemWalletApproveOption( - val visaDataForApprove: VisaDataForApprove, + val preparationDataForApprove: PreparationDataForApprove, + val foundWalletCardId: String?, val allowNavigateBack: Boolean, ) : OnboardingVisaRoute() @Serializable - data class OtherWalletApproveOption(val visaDataForApprove: VisaDataForApprove) : OnboardingVisaRoute() + data class OtherWalletApproveOption( + val preparationDataForApprove: PreparationDataForApprove, + ) : OnboardingVisaRoute() @Serializable data object InProgress : OnboardingVisaRoute() @Serializable - data class PinCode(val activationOrderId: String) : OnboardingVisaRoute() + data class PinCode(val activationOrderInfo: VisaActivationOrderInfo) : OnboardingVisaRoute() } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt index 4edd313357..5c43e9cdc9 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Step.kt @@ -2,7 +2,9 @@ package com.tangem.features.onboarding.v2.visa.impl.route @Suppress("MagicNumber") internal fun OnboardingVisaRoute.stepNum() = when (this) { - is OnboardingVisaRoute.Welcome -> 1 + is OnboardingVisaRoute.Welcome, + is OnboardingVisaRoute.WelcomeBack, + -> 1 OnboardingVisaRoute.AccessCode -> 2 is OnboardingVisaRoute.ChooseWallet -> 3 is OnboardingVisaRoute.OtherWalletApproveOption -> 4 diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt index adb5cebf9b..d103b8f6a7 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/route/Titles.kt @@ -10,5 +10,7 @@ internal fun OnboardingVisaRoute.screenTitle(): TextReference = when (this) { is OnboardingVisaRoute.OtherWalletApproveOption -> TextReference.Str("Wallet connection") is OnboardingVisaRoute.PinCode -> TextReference.Str("PIN code") is OnboardingVisaRoute.TangemWalletApproveOption -> TextReference.Str("Wallet connection") - is OnboardingVisaRoute.Welcome -> TextReference.Str("Getting started") + is OnboardingVisaRoute.Welcome, + is OnboardingVisaRoute.WelcomeBack, + -> TextReference.Str("Getting started") } \ No newline at end of file