From 538757a7ceca494fb4d8461e4c9af8f1fbebeb73 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 22 Apr 2025 11:52:10 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../tasks/visa/VisaCardActivationTask.kt | 103 +++++++++++----- .../visa/VisaCustomerWalletApproveTask.kt | 19 ++- .../tap/domain/visa/VisaCardScanHandler.kt | 11 +- .../api/common/config/TangemVisa.kt | 2 +- .../api/common/config/TangemVisaAuth.kt | 2 +- .../datasource/api/visa/TangemVisaApi.kt | 46 +++---- .../datasource/api/visa/TangemVisaAuthApi.kt | 35 +++--- .../request/ActivationByCardWalletRequest.kt | 15 +-- .../ActivationByCustomerWalletRequest.kt | 13 +- .../models/request/ActivationStatusRequest.kt | 10 ++ .../request/GenerateNoneByCardIdRequest.kt | 11 ++ .../GenerateNoneByCardWalletRequest.kt | 10 ++ .../request/GetAccessTokenByCardIdRequest.kt | 12 ++ .../GetAccessTokenByCardWalletRequest.kt | 11 ++ .../request/GetCardWalletAcceptanceRequest.kt | 11 ++ .../GetCustomerWalletAcceptanceRequest.kt | 11 ++ .../request/RefreshTokenByCardIdRequest.kt | 10 ++ .../RefreshTokenByCardWalletRequest.kt | 10 ++ .../visa/models/request/SetPinCodeRequest.kt | 16 +-- .../CardActivationRemoteStateResponse.kt | 14 ++- .../CustomerWalletDataToSignResponse.kt | 14 --- .../models/response/GenerateNonceResponse.kt | 11 +- .../api/visa/models/response/JWTResponse.kt | 23 ++-- ...nResponse.kt => VisaDataToSignResponse.kt} | 6 +- .../managers/ProdApiConfigsManagerTest.kt | 4 +- .../visa/DefaultVisaActivationRepository.kt | 115 ++++++------------ .../data/visa/DefaultVisaAuthRepository.kt | 108 ++++++++-------- .../data/visa/MockVisaActivationRepository.kt | 7 +- .../VisaActivationStatusConverter.kt | 60 --------- .../VisaActivationStatusConverterWithState.kt | 81 ++++++++++++ .../com/tangem/data/visa/di/VisaDataModule.kt | 18 +-- .../data/visa/utils/VisaApiRequestMaker.kt | 13 +- .../common/visa/VisaWalletPublicKeyUtility.kt | 2 +- domain/visa/build.gradle.kts | 3 + .../com/tangem/domain/visa/error/VisaError.kt | 1 + .../visa/model/VisaActivationOrderInfo.kt | 1 + .../visa/model/VisaActivationRemoteState.kt | 52 ++++---- .../domain/visa/model/VisaAuthTokens.kt | 11 +- .../model/VisaCardWalletDataToSignRequest.kt | 11 +- .../VisaCustomerWalletDataToSignRequest.kt | 1 + .../visa/model/VisaDataToSignByCardWallet.kt | 3 +- .../VisaSignedActivationDataByCardWallet.kt | 1 - .../domain/visa/SetVisaPinCodeUseCase.kt | 30 +++-- .../repository/VisaActivationRepository.kt | 2 - .../impl/DefaultOnboardingVisaComponent.kt | 6 +- .../model/OnboardingVisaAccessCodeModel.kt | 26 ++-- .../model/OnboardingVisaApproveModel.kt | 5 +- .../model/OnboardingVisaInProgressModel.kt | 27 +++- .../model/OnboardingVisaOtherWalletModel.kt | 4 +- .../model/OnboardingVisaPinCodeModel.kt | 41 +------ .../child/pincode/model/PinCodeValidation.kt | 5 +- .../model/OnboardingVisaWelcomeModel.kt | 13 +- .../model/analytics/VisaAnalyticsEvent.kt | 10 +- .../v2/visa/impl/model/OnboardingVisaModel.kt | 23 ++-- 54 files changed, 622 insertions(+), 488 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/ActivationStatusRequest.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GenerateNoneByCardIdRequest.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GenerateNoneByCardWalletRequest.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetAccessTokenByCardIdRequest.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetAccessTokenByCardWalletRequest.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetCardWalletAcceptanceRequest.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetCustomerWalletAcceptanceRequest.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/RefreshTokenByCardIdRequest.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/RefreshTokenByCardWalletRequest.kt delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/CustomerWalletDataToSignResponse.kt rename core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/{CardWalletDataToSignResponse.kt => VisaDataToSignResponse.kt} (66%) delete mode 100644 data/visa/src/main/kotlin/com/tangem/data/visa/converter/VisaActivationStatusConverter.kt create mode 100644 data/visa/src/main/kotlin/com/tangem/data/visa/converter/VisaActivationStatusConverterWithState.kt diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt index 890ce4ba9b..521c1444d8 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCardActivationTask.kt @@ -4,14 +4,12 @@ import arrow.core.Either import arrow.core.getOrElse import arrow.core.raise.catch import arrow.core.raise.either +import com.tangem.blockchain.blockchains.ethereum.EthereumUtils import com.tangem.common.CompletionResult import com.tangem.common.card.EllipticCurve -import com.tangem.common.core.CardSession -import com.tangem.common.core.CardSessionRunnable -import com.tangem.common.core.CompletionCallback -import com.tangem.common.core.TangemError -import com.tangem.common.core.TangemSdkError +import com.tangem.common.core.* import com.tangem.common.extensions.hexToBytes +import com.tangem.common.extensions.toDecompressedPublicKey import com.tangem.common.extensions.toHexString import com.tangem.common.map import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource @@ -46,7 +44,7 @@ import timber.log.Timber import kotlin.coroutines.resume import kotlin.time.measureTimedValue -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") class VisaCardActivationTask @AssistedInject constructor( @Assisted private val mode: VisaCardActivationTaskMode, @Assisted private val activationInput: VisaActivationInput, @@ -97,7 +95,23 @@ class VisaCardActivationTask @AssistedInject constructor( context.signAuthorizationChallenge(mode.authorizationChallenge) } is VisaCardActivationTaskMode.SignOnly -> { - context.signData(mode.dataToSignByCardWallet) + val wallet = + card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } + ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) + + val derivedPublicKey = when (val deriveKeyResult = context.deriveKey(wallet.publicKey)) { + is CompletionResult.Failure -> { + return CompletionResult.Failure(deriveKeyResult.error) + } + is CompletionResult.Success -> { + deriveKeyResult.data + } + } + + context.signData( + mode.dataToSignByCardWallet, + derivedPublicKey, + ) } } } @@ -138,9 +152,35 @@ class VisaCardActivationTask @AssistedInject constructor( private suspend fun SessionContext.processSignedAuthorizationChallenge( signedChallenge: VisaAuthSignedChallenge, ): CompletionResult { + when (val createWalletResult = createWallet()) { + is CompletionResult.Failure -> return CompletionResult.Failure(createWalletResult.error) + is CompletionResult.Success -> {} + } + + val card = + session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) + + val wallet = + card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } + ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) + + val derivedPublicKey = when (val deriveKeyResult = deriveKey(wallet.publicKey)) { + is CompletionResult.Failure -> { + return CompletionResult.Failure(deriveKeyResult.error) + } + is CompletionResult.Success -> { + deriveKeyResult.data + } + } + + val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey) + .getOrElse { return CompletionResult.Failure(it.tangemError) } + .value + return coroutineScope { - val dataToSignDeferred = async { getDataToSign(signedChallenge) } - val otpTaskDeferred = async { createWallet() } + val dataToSignDeferred = + async { getDataToSign(signedChallenge = signedChallenge, cardWalletAddress = walletAddress) } + val otpTaskDeferred = async { createOTP() } val dataToSign = dataToSignDeferred.await() .getOrElse { @@ -150,12 +190,16 @@ class VisaCardActivationTask @AssistedInject constructor( otpTaskDeferred.await() - signData(dataToSign) + signData( + dataToSign = dataToSign, + derivedPublicKey = derivedPublicKey, + ) } } private suspend fun SessionContext.getDataToSign( signedChallenge: VisaAuthSignedChallenge, + cardWalletAddress: String, ): Either = either { catch( block = { @@ -168,7 +212,12 @@ class VisaCardActivationTask @AssistedInject constructor( raise(VisaActivationError.WrongRemoteState.tangemError) } - visaActivationRepository.getCardWalletAcceptanceData(remoteState.request) + visaActivationRepository.getCardWalletAcceptanceData( + VisaCardWalletDataToSignRequest( + activationOrderInfo = remoteState.activationOrderInfo, + cardWalletAddress = cardWalletAddress, + ), + ) }, catch = { raise(VisaAuthorizationAPIError.tangemError) @@ -182,7 +231,7 @@ class VisaCardActivationTask @AssistedInject constructor( val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) return if (card.wallets.any { it.curve == EllipticCurve.Secp256k1 }) { - createOTP() + CompletionResult.Success(Unit) } else { val createWalletTask = CreateWalletTask(EllipticCurve.Secp256k1) @@ -199,7 +248,7 @@ class VisaCardActivationTask @AssistedInject constructor( when (val result = timedResult.value) { is CompletionResult.Success -> { Timber.i("CreateWalletTask success") - createOTP() + CompletionResult.Success(Unit) } is CompletionResult.Failure -> { Timber.e("CreateWalletTask failure ${result.error}") @@ -245,26 +294,15 @@ class VisaCardActivationTask @AssistedInject constructor( private suspend fun SessionContext.signData( dataToSign: VisaDataToSignByCardWallet, + derivedPublicKey: ExtendedPublicKey, ): CompletionResult { val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) + val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) - val derivedPublicKey = when (val deriveKeyResult = deriveKey(wallet.publicKey)) { - is CompletionResult.Failure -> { - return CompletionResult.Failure(deriveKeyResult.error) - } - is CompletionResult.Success -> { - deriveKeyResult.data - } - } - - val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey) - .getOrElse { return CompletionResult.Failure(it.tangemError) } - .value - val task = SignHashCommand( hash = dataToSign.hashToSign.hexToBytes(), walletPublicKey = wallet.publicKey, @@ -286,8 +324,8 @@ class VisaCardActivationTask @AssistedInject constructor( Timber.i("SignHashCommand success") handleSignedData( dataToSign = dataToSign, - walletAddress = walletAddress, response = result.data, + derivedPublicKey = derivedPublicKey, ) } is CompletionResult.Failure -> { @@ -313,7 +351,7 @@ class VisaCardActivationTask @AssistedInject constructor( private suspend fun SessionContext.handleSignedData( dataToSign: VisaDataToSignByCardWallet, - walletAddress: String, + derivedPublicKey: ExtendedPublicKey, response: SignHashResponse, ): CompletionResult { val otp = otpStorage.getOTP(cardId) ?: run { @@ -321,11 +359,16 @@ class VisaCardActivationTask @AssistedInject constructor( otpStorage.getOTP(cardId) ?: return CompletionResult.Failure(VisaActivationError.MissingRootOTP.tangemError) } + val rsvSignature = EthereumUtils.prepareSignedMessageData( + signedHash = response.signature, + hashToSign = dataToSign.hashToSign.hexToBytes(), + publicKey = derivedPublicKey.publicKey.toDecompressedPublicKey(), + ) + val signedActivationData = dataToSign.sign( - cardWalletAddress = walletAddress, rootOTP = otp.rootOTP.toHexString(), otpCounter = otp.counter, - signature = response.signature.toHexString(), + signature = rsvSignature, ) return setupAccessCode().map { diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt index 18ec47b7fb..2fb700cbd7 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/visa/VisaCustomerWalletApproveTask.kt @@ -1,6 +1,7 @@ package com.tangem.tap.domain.tasks.visa import arrow.core.getOrElse +import com.tangem.blockchain.blockchains.ethereum.EthereumUtils import com.tangem.common.CompletionResult import com.tangem.common.card.Card import com.tangem.common.card.CardWallet @@ -10,7 +11,7 @@ import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CompletionCallback import com.tangem.common.core.TangemSdkError import com.tangem.common.extensions.hexToBytes -import com.tangem.common.extensions.toHexString +import com.tangem.common.extensions.toDecompressedPublicKey import com.tangem.core.error.ext.tangemError import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey @@ -129,6 +130,7 @@ class VisaCustomerWalletApproveTask( targetWalletPublicKey = wallet.publicKey, derivationPath = derivationPath, session = session, + extendedPublicKey = extendedPublicKey, callback = callback, ) } @@ -149,6 +151,7 @@ class VisaCustomerWalletApproveTask( signApproveData( targetWalletPublicKey = publicKey, derivationPath = null, + extendedPublicKey = null, session = session, callback = callback, ) @@ -157,11 +160,14 @@ class VisaCustomerWalletApproveTask( private fun signApproveData( targetWalletPublicKey: ByteArray, derivationPath: DerivationPath?, + extendedPublicKey: ExtendedPublicKey?, session: CardSession, callback: CompletionCallback, ) { + val hashToSign = visaDataForApprove.dataToSign.hashToSign.hexToBytes() + val signTask = SignHashCommand( - hash = visaDataForApprove.dataToSign.hashToSign.hexToBytes(), + hash = hashToSign, walletPublicKey = targetWalletPublicKey, derivationPath = derivationPath, ) @@ -169,11 +175,18 @@ class VisaCustomerWalletApproveTask( signTask.run(session) { result -> when (result) { is CompletionResult.Success -> { + val rsvSignature = EthereumUtils.prepareSignedMessageData( + signedHash = result.data.signature, + hashToSign = hashToSign, + publicKey = extendedPublicKey?.publicKey?.toDecompressedPublicKey() + ?: targetWalletPublicKey.toDecompressedPublicKey(), + ) + scanCard( session = session, callback = callback, signedData = visaDataForApprove.dataToSign.sign( - signature = result.data.signature.toHexString(), + signature = rsvSignature, customerWalletAddress = visaDataForApprove.targetAddress, ), ) 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 ad20167f85..b83d23f8d9 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 @@ -128,6 +128,8 @@ internal class VisaCardScanHandler @Inject constructor( Timber.i("Requesting challenge for wallet authorization") val challengeResponse = runCatching { + // TODO [REDACTED_TASK_KEY] + error("sign and get specific error to switch to card_id flow") visaAuthRepository.getCardWalletAuthChallenge(cardWalletAddress = walletAddress.value) }.getOrElse { Timber.i( @@ -180,6 +182,7 @@ internal class VisaCardScanHandler @Inject constructor( return CompletionResult.Success(VisaCardActivationStatus.Activated(authorizationTokensResponse)) } + @Suppress("LongMethod") private suspend fun SessionContext.handleCardAuthorization( cardWalletAddress: String, ): CompletionResult { @@ -232,11 +235,17 @@ internal class VisaCardScanHandler @Inject constructor( tokens = authorizationTokensResponse, ) - val activationRemoteState = visaActivationRepository.getActivationRemoteState() + val activationRemoteState = runCatching { + visaActivationRepository.getActivationRemoteState() + }.getOrElse { + Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.message}") + return CompletionResult.Failure(VisaAuthorizationAPIError.tangemError) + } val error = when (activationRemoteState) { VisaActivationRemoteState.BlockedForActivation -> VisaActivationError.BlockedForActivation VisaActivationRemoteState.Activated -> VisaActivationError.InvalidActivationState + VisaActivationRemoteState.Failed -> VisaActivationError.FailedRemoteState else -> null } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisa.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisa.kt index 9c1f78bb43..5310f5b308 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisa.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisa.kt @@ -15,7 +15,7 @@ internal class TangemVisa( private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( environment = ApiEnvironment.PROD, - baseUrl = "https://bff.tangem.com/", + baseUrl = "[REDACTED_ENV_URL]", headers = createHeaders(), ) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisaAuth.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisaAuth.kt index e0a9068c02..833cde2111 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisaAuth.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/TangemVisaAuth.kt @@ -15,7 +15,7 @@ internal class TangemVisaAuth( private fun createStageEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( environment = ApiEnvironment.STAGE, - baseUrl = "https://api-s.tangem.org/", + baseUrl = "[REDACTED_ENV_URL]", headers = createHeaders(), ) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt index a0d08a74a1..16e643329b 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt @@ -1,9 +1,11 @@ package com.tangem.datasource.api.visa import com.tangem.datasource.api.common.response.ApiResponse -import com.tangem.datasource.api.utils.ReadTimeout import com.tangem.datasource.api.visa.models.request.ActivationByCardWalletRequest import com.tangem.datasource.api.visa.models.request.ActivationByCustomerWalletRequest +import com.tangem.datasource.api.visa.models.request.ActivationStatusRequest +import com.tangem.datasource.api.visa.models.request.GetCardWalletAcceptanceRequest +import com.tangem.datasource.api.visa.models.request.GetCustomerWalletAcceptanceRequest import com.tangem.datasource.api.visa.models.request.SetPinCodeRequest import com.tangem.datasource.api.visa.models.response.* import retrofit2.http.Body @@ -11,60 +13,40 @@ import retrofit2.http.GET import retrofit2.http.Header import retrofit2.http.POST import retrofit2.http.Query -import java.util.concurrent.TimeUnit interface TangemVisaApi { - @GET("product_instance/activation_status") + @POST("v1/activation/status") suspend fun getRemoteActivationStatus( @Header("Authorization") authHeader: String, - @Query("customer_id") customerId: String, - @Query("product_instance_id") productInstanceId: String, - @Query("card_id") cardId: String, - @Query("card_public_key") cardPublicKey: String, + @Body request: ActivationStatusRequest, ): ApiResponse - @ReadTimeout(duration = 20, TimeUnit.MINUTES) - @GET("product_instance/activation_status") - suspend fun getRemoteActivationStatusLongPoll( - @Header("Authorization") authHeader: String, - @Query("customer_id") customerId: String, - @Query("product_instance_id") productInstanceId: String, - @Query("card_id") cardId: String, - @Query("card_public_key") cardPublicKey: String, - ): ApiResponse - - @GET("product_instance/card_wallet_acceptance") + @POST("v1/activation/acceptance/message") suspend fun getCardWalletAcceptance( @Header("Authorization") authHeader: String, - @Query("customer_id") customerId: String, - @Query("product_instance_id") productInstanceId: String, - @Query("activation_order") activationOrderId: String, - @Query("customer_wallet_address") customerWalletAddress: String, - ): ApiResponse + @Body request: GetCardWalletAcceptanceRequest, + ): ApiResponse - @GET("product_instance/customer_wallet_acceptance") + @POST("v1/activation/acceptance/message") suspend fun getCustomerWalletAcceptance( @Header("Authorization") authHeader: String, - @Query("customer_id") customerId: String, - @Query("product_instance_id") productInstanceId: String, - @Query("activation_order") activationOrderId: String, - @Query("card_wallet_address") cardWalletAddress: String, - ): ApiResponse + @Body request: GetCustomerWalletAcceptanceRequest, + ): ApiResponse - @POST("product_instance/activation_by_card_wallet") + @POST("v1/activation/data") suspend fun activateByCardWallet( @Header("Authorization") authHeader: String, @Body body: ActivationByCardWalletRequest, ): ApiResponse - @POST("product_instance/activation_by_customer_wallet") + @POST("v1/activation/data") suspend fun activateByCustomerWallet( @Header("Authorization") authHeader: String, @Body body: ActivationByCustomerWalletRequest, ): ApiResponse - @POST("product_instance/issuer_activation") + @POST("v1/activation/pin") suspend fun setPinCode( @Header("Authorization") authHeader: String, @Body body: SetPinCodeRequest, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt index b2ed8d19be..57b91ca1c3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt @@ -1,32 +1,29 @@ package com.tangem.datasource.api.visa import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.visa.models.request.* import com.tangem.datasource.api.visa.models.response.GenerateNonceResponse import com.tangem.datasource.api.visa.models.response.JWTResponse -import retrofit2.http.Field +import retrofit2.http.Body import retrofit2.http.POST -import retrofit2.http.Query interface TangemVisaAuthApi { - @POST("auth/card_wallet") - suspend fun generateNonceByWalletAddress( - @Query("card_wallet_address") cardWalletAddress: String, - ): GenerateNonceResponse + @POST("v1/auth/challenge") + suspend fun generateNonceByCardId(@Body request: GenerateNoneByCardIdRequest): GenerateNonceResponse - @POST("auth/card_id") - suspend fun generateNonceByCard( - @Query("card_id") cardId: String, - @Query("card_public_key") cardPublicKey: String, - ): GenerateNonceResponse + @POST("v1/auth/challenge") + suspend fun generateNonceByCardWallet(@Body request: GenerateNoneByCardWalletRequest): GenerateNonceResponse - @POST("auth/get_token") - suspend fun getAccessToken( - @Query("session_id") sessionId: String, - @Query("signature") signature: String, - @Query("salt") salt: String?, - ): JWTResponse + @POST("v1/auth/token") + suspend fun getAccessTokenByCardId(@Body request: GetAccessTokenByCardIdRequest): JWTResponse - @POST("auth/refresh_token") - suspend fun refreshAccessToken(@Field("refresh_token") refreshToken: String): ApiResponse + @POST("v1/auth/token") + suspend fun getAccessTokenByCardWallet(@Body request: GetAccessTokenByCardWalletRequest): JWTResponse + + @POST("v1/auth/token/refresh") + suspend fun refreshCardIdAccessToken(@Body request: RefreshTokenByCardIdRequest): ApiResponse + + @POST("v1/auth/token/refresh") + suspend fun refreshCardWalletAccessToken(@Body request: RefreshTokenByCardWalletRequest): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/ActivationByCardWalletRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/ActivationByCardWalletRequest.kt index d19f87f592..c16198f0c6 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/ActivationByCardWalletRequest.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/ActivationByCardWalletRequest.kt @@ -5,22 +5,15 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class ActivationByCardWalletRequest( - @Json(name = "customer_id") val customerId: String, - @Json(name = "product_instance_id") val productInstanceId: String, - @Json(name = "activation_order_id") val activationOrderId: String, - @Json(name = "data") val data: Data, + @Json(name = "order_id") val orderId: String, + @Json(name = "card_wallet") val cardWallet: CardWallet, + @Json(name = "deploy_acceptance_signature") val deployAcceptanceSignature: String, + @Json(name = "otp") val otp: Otp, ) { - @JsonClass(generateAdapter = true) - data class Data( - @Json(name = "card_wallet") val cardWallet: CardWallet, - @Json(name = "otp") val otp: Otp, - ) - @JsonClass(generateAdapter = true) data class CardWallet( @Json(name = "address") val address: String, @Json(name = "card_wallet_confirmation") val cardWalletConfirmation: CardWalletConfirmation?, - @Json(name = "deploy_acceptance_signature") val deployAcceptanceSignature: String, ) @JsonClass(generateAdapter = true) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/ActivationByCustomerWalletRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/ActivationByCustomerWalletRequest.kt index 47bdbc4132..ace2090603 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/ActivationByCustomerWalletRequest.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/ActivationByCustomerWalletRequest.kt @@ -5,19 +5,12 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class ActivationByCustomerWalletRequest( - @Json(name = "customer_id") val customerId: String, - @Json(name = "product_instance_id") val productInstanceId: String, - @Json(name = "activation_order_id") val activationOrderId: String, - @Json(name = "data") val data: Data, + @Json(name = "order_id") val orderId: String, + @Json(name = "customer_wallet") val customerWallet: CustomerWallet, ) { - @JsonClass(generateAdapter = true) - data class Data( - @Json(name = "customer_wallet") val customerWallet: CustomerWallet, - ) - @JsonClass(generateAdapter = true) data class CustomerWallet( - @Json(name = "address") val address: String, @Json(name = "deploy_acceptance_signature") val deployAcceptanceSignature: String, + @Json(name = "address") val customerWalletAddress: String, ) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/ActivationStatusRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/ActivationStatusRequest.kt new file mode 100644 index 0000000000..7ce0614abc --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/ActivationStatusRequest.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.api.visa.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class ActivationStatusRequest( + @Json(name = "card_id") val cardId: String, + @Json(name = "card_public_key") val cardPublicKey: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GenerateNoneByCardIdRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GenerateNoneByCardIdRequest.kt new file mode 100644 index 0000000000..d3aa5eaa50 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GenerateNoneByCardIdRequest.kt @@ -0,0 +1,11 @@ +package com.tangem.datasource.api.visa.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GenerateNoneByCardIdRequest( + @Json(name = "auth_type") val authType: String = "card_id", + @Json(name = "card_id") val cardId: String, + @Json(name = "card_public_key") val cardPublicKey: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GenerateNoneByCardWalletRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GenerateNoneByCardWalletRequest.kt new file mode 100644 index 0000000000..172c39e727 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GenerateNoneByCardWalletRequest.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.api.visa.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GenerateNoneByCardWalletRequest( + @Json(name = "auth_type") val authType: String = "card_wallet", + @Json(name = "card_wallet_address") val cardWalletAddress: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetAccessTokenByCardIdRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetAccessTokenByCardIdRequest.kt new file mode 100644 index 0000000000..c212c81412 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetAccessTokenByCardIdRequest.kt @@ -0,0 +1,12 @@ +package com.tangem.datasource.api.visa.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GetAccessTokenByCardIdRequest( + @Json(name = "auth_type") val authType: String = "card_id", + @Json(name = "session_id") val sessionId: String, + @Json(name = "signature") val signature: String, + @Json(name = "salt") val salt: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetAccessTokenByCardWalletRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetAccessTokenByCardWalletRequest.kt new file mode 100644 index 0000000000..c80e97b0c9 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetAccessTokenByCardWalletRequest.kt @@ -0,0 +1,11 @@ +package com.tangem.datasource.api.visa.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GetAccessTokenByCardWalletRequest( + @Json(name = "auth_type") val authType: String = "card_wallet", + @Json(name = "session_id") val sessionId: String, + @Json(name = "signature") val signature: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetCardWalletAcceptanceRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetCardWalletAcceptanceRequest.kt new file mode 100644 index 0000000000..dd084fa326 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetCardWalletAcceptanceRequest.kt @@ -0,0 +1,11 @@ +package com.tangem.datasource.api.visa.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GetCardWalletAcceptanceRequest( + @Json(name = "type") val type: String = "card_wallet", + @Json(name = "customer_wallet_address") val customerWalletAddress: String, + @Json(name = "card_wallet_address") val cardWalletAddress: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetCustomerWalletAcceptanceRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetCustomerWalletAcceptanceRequest.kt new file mode 100644 index 0000000000..3948fc43b3 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/GetCustomerWalletAcceptanceRequest.kt @@ -0,0 +1,11 @@ +package com.tangem.datasource.api.visa.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GetCustomerWalletAcceptanceRequest( + @Json(name = "type") val type: String = "customer_wallet", + @Json(name = "customer_wallet_address") val customerWalletAddress: String, + @Json(name = "card_wallet_address") val cardWalletAddress: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/RefreshTokenByCardIdRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/RefreshTokenByCardIdRequest.kt new file mode 100644 index 0000000000..f94a59a280 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/RefreshTokenByCardIdRequest.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.api.visa.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class RefreshTokenByCardIdRequest( + @Json(name = "auth_type") val authType: String = "card_id", + @Json(name = "refresh_token") val refreshToken: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/RefreshTokenByCardWalletRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/RefreshTokenByCardWalletRequest.kt new file mode 100644 index 0000000000..108469e1b6 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/RefreshTokenByCardWalletRequest.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.api.visa.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class RefreshTokenByCardWalletRequest( + @Json(name = "auth_type") val authType: String = "card_wallet", + @Json(name = "refresh_token") val refreshToken: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/SetPinCodeRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/SetPinCodeRequest.kt index c3a5290c8c..69e1a5ac08 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/SetPinCodeRequest.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/request/SetPinCodeRequest.kt @@ -5,14 +5,8 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class SetPinCodeRequest( - @Json(name = "customer_id") val customerId: String, - @Json(name = "activation_order_id") val activationOrderId: String, - @Json(name = "product_instance_id") val productInstanceId: String, - @Json(name = "data") val data: Data, -) { - data class Data( - @Json(name = "session_key") val sessionKey: String, - @Json(name = "iv") val iv: String, - @Json(name = "encrypted_pin") val encryptedPin: String, - ) -} \ No newline at end of file + @Json(name = "order_id") val orderId: String, + @Json(name = "session_id") val sessionId: String, + @Json(name = "iv") val iv: String, + @Json(name = "pin") val pin: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/CardActivationRemoteStateResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/CardActivationRemoteStateResponse.kt index aeb498dac8..4134fa9c0e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/CardActivationRemoteStateResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/CardActivationRemoteStateResponse.kt @@ -5,15 +5,21 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class CardActivationRemoteStateResponse( - @Json(name = "activation_status") val status: String, - @Json(name = "activation_order") val activationOrder: ActivationOrder?, - @Json(name = "stepChangeCode") val stepChangeCode: Int?, - @Json(name = "updatedAt") val updatedAt: String?, + @Json(name = "result") val result: Result, ) { + @JsonClass(generateAdapter = true) + data class Result( + @Json(name = "status") val status: String, + @Json(name = "order") val activationOrder: ActivationOrder?, + @Json(name = "stepChangeCode") val stepChangeCode: Int?, + @Json(name = "updatedAt") val updatedAt: String?, + ) + @JsonClass(generateAdapter = true) data class ActivationOrder( @Json(name = "id") val id: String, @Json(name = "customer_id") val customerId: String, @Json(name = "customer_wallet_address") val customerWalletAddress: String, + @Json(name = "card_wallet_address") val cardWalletAddress: String, ) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/CustomerWalletDataToSignResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/CustomerWalletDataToSignResponse.kt deleted file mode 100644 index e4575a1587..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/CustomerWalletDataToSignResponse.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.datasource.api.visa.models.response - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass - -@JsonClass(generateAdapter = true) -data class CustomerWalletDataToSignResponse( - @Json(name = "data_for_customer_wallet") val dataForCardWallet: Data, -) { - @JsonClass(generateAdapter = true) - data class Data( - @Json(name = "hash") val hash: String, - ) -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/GenerateNonceResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/GenerateNonceResponse.kt index 002e43b16e..9a79a457b8 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/GenerateNonceResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/GenerateNonceResponse.kt @@ -5,6 +5,11 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class GenerateNonceResponse( - @Json(name = "nonce") val nonce: String, - @Json(name = "session_id") val sessionId: String, -) \ No newline at end of file + @Json(name = "result") val result: Result, +) { + @JsonClass(generateAdapter = true) + data class Result( + @Json(name = "nonce") val nonce: String, + @Json(name = "session_id") val sessionId: String, + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/JWTResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/JWTResponse.kt index e4fc0d2579..0380074d02 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/JWTResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/JWTResponse.kt @@ -5,12 +5,17 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class JWTResponse( - @Json(name = "access_token") val accessToken: String, - @Json(name = "expires_in") val expiresIn: Int, - @Json(name = "refresh_expires_in") val refreshExpiresIn: Int, - @Json(name = "refresh_token") val refreshToken: String, - @Json(name = "token_type") val tokenType: String, - @Json(name = "not-before-policy") val notBeforePolicy: Int, - @Json(name = "session_state") val sessionState: String, - @Json(name = "scope") val scope: String, -) \ No newline at end of file + @Json(name = "result") val result: Result, +) { + @JsonClass(generateAdapter = true) + data class Result( + @Json(name = "access_token") val accessToken: String, + @Json(name = "expires_in") val expiresIn: Int, + @Json(name = "refresh_expires_in") val refreshExpiresIn: Int, + @Json(name = "refresh_token") val refreshToken: String, + @Json(name = "token_type") val tokenType: String, + @Json(name = "not-before-policy") val notBeforePolicy: Int, + @Json(name = "session_state") val sessionState: String, + @Json(name = "scope") val scope: String, + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/CardWalletDataToSignResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaDataToSignResponse.kt similarity index 66% rename from core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/CardWalletDataToSignResponse.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaDataToSignResponse.kt index 288ab32e39..a19d3e7ca4 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/CardWalletDataToSignResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaDataToSignResponse.kt @@ -4,11 +4,11 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -data class CardWalletDataToSignResponse( - @Json(name = "dataForCardWallet") val dataForCardWallet: Data, +data class VisaDataToSignResponse( + @Json(name = "result") val result: Result, ) { @JsonClass(generateAdapter = true) - data class Data( + data class Result( @Json(name = "hash") val hash: String, ) } \ No newline at end of file diff --git a/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt b/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt index e0df7b4096..6537f31453 100644 --- a/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt +++ b/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt @@ -186,7 +186,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) { id = ApiConfig.ID.TangemVisaAuth, expected = ApiEnvironmentConfig( environment = ApiEnvironment.STAGE, - baseUrl = "https://api-s.tangem.org/", + baseUrl = "[REDACTED_ENV_URL]", headers = mapOf( "version" to ProviderSuspend { VERSION_NAME }, "platform" to ProviderSuspend { "Android" }, @@ -200,7 +200,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) { id = ApiConfig.ID.TangemVisa, expected = ApiEnvironmentConfig( environment = ApiEnvironment.PROD, - baseUrl = "https://bff.tangem.com/", + baseUrl = "[REDACTED_ENV_URL]", headers = mapOf( "version" to ProviderSuspend { VERSION_NAME }, "platform" to ProviderSuspend { "Android" }, diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt index b4bb976ce9..bf72bc979e 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt @@ -1,13 +1,15 @@ package com.tangem.data.visa import com.tangem.data.visa.config.VisaLibLoader -import com.tangem.data.visa.converter.AccessCodeDataConverter -import com.tangem.data.visa.converter.VisaActivationStatusConverter +import com.tangem.data.visa.converter.VisaActivationStatusConverterWithState import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.visa.TangemVisaApi import com.tangem.datasource.api.visa.models.request.ActivationByCardWalletRequest import com.tangem.datasource.api.visa.models.request.ActivationByCustomerWalletRequest +import com.tangem.datasource.api.visa.models.request.ActivationStatusRequest +import com.tangem.datasource.api.visa.models.request.GetCardWalletAcceptanceRequest +import com.tangem.datasource.api.visa.models.request.GetCustomerWalletAcceptanceRequest import com.tangem.datasource.api.visa.models.request.SetPinCodeRequest import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.domain.visa.exception.RefreshTokenExpiredException @@ -25,9 +27,7 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( @Assisted private val visaCardId: VisaCardId, private val visaApi: TangemVisaApi, private val dispatcherProvider: CoroutineDispatcherProvider, - private val visaActivationStatusConverter: VisaActivationStatusConverter, private val visaAuthTokenStorage: VisaAuthTokenStorage, - private val accessCodeDataConverter: AccessCodeDataConverter, private val visaAuthRepository: VisaAuthRepository, private val visaLibLoader: VisaLibLoader, ) : VisaActivationRepository { @@ -36,59 +36,38 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( val result = request { val authTokens = checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" } - val accessCodeData = accessCodeDataConverter.convert(authTokens) visaApi.getRemoteActivationStatus( authHeader = authTokens.getAuthHeader(), - customerId = accessCodeData.customerId, - productInstanceId = accessCodeData.productInstanceId, - cardId = visaCardId.cardId, - cardPublicKey = visaCardId.cardPublicKey, + request = ActivationStatusRequest( + cardId = visaCardId.cardId, + cardPublicKey = visaCardId.cardPublicKey, + ), ).getOrThrow() } - visaActivationStatusConverter.convert(result) + VisaActivationStatusConverterWithState.convert(result) } - override suspend fun getActivationRemoteStateLongPoll(): VisaActivationRemoteState = - withContext(dispatcherProvider.io) { - val result = request { - val authTokens = - checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" } - val accessCodeData = accessCodeDataConverter.convert(authTokens) - - visaApi.getRemoteActivationStatusLongPoll( - authHeader = authTokens.getAuthHeader(), - customerId = accessCodeData.customerId, - productInstanceId = accessCodeData.productInstanceId, - cardId = visaCardId.cardId, - cardPublicKey = visaCardId.cardPublicKey, - ).getOrThrow() - } - - visaActivationStatusConverter.convert(result) - } - override suspend fun getCardWalletAcceptanceData( request: VisaCardWalletDataToSignRequest, ): VisaDataToSignByCardWallet = withContext(dispatcherProvider.io) { val result = request { val authTokens = checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" } - val accessCodeData = accessCodeDataConverter.convert(authTokens) visaApi.getCardWalletAcceptance( authHeader = authTokens.getAuthHeader(), - customerId = accessCodeData.customerId, - productInstanceId = accessCodeData.productInstanceId, - activationOrderId = request.orderId, - customerWalletAddress = request.customerWalletAddress, + request = GetCardWalletAcceptanceRequest( + customerWalletAddress = request.activationOrderInfo.customerWalletAddress, + cardWalletAddress = request.cardWalletAddress, + ), ).getOrThrow() } VisaDataToSignByCardWallet( request = request, - hashToSign = result.dataForCardWallet.hash, + hashToSign = result.result.hash, ) } @@ -98,20 +77,19 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( val result = request { val authTokens = checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" } - val accessCodeData = accessCodeDataConverter.convert(authTokens) visaApi.getCustomerWalletAcceptance( authHeader = authTokens.getAuthHeader(), - customerId = accessCodeData.customerId, - productInstanceId = accessCodeData.productInstanceId, - activationOrderId = request.orderId, - cardWalletAddress = request.cardWalletAddress, + request = GetCustomerWalletAcceptanceRequest( + cardWalletAddress = request.cardWalletAddress, + customerWalletAddress = request.customerWalletAddress, + ), ).getOrThrow() } VisaDataToSignByCustomerWallet( request = request, - hashToSign = result.dataForCardWallet.hash, + hashToSign = result.result.hash, ) } @@ -120,27 +98,22 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( request { val authTokens = checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" } - val accessCodeData = accessCodeDataConverter.convert(authTokens) visaApi.activateByCardWallet( authHeader = authTokens.getAuthHeader(), body = ActivationByCardWalletRequest( - customerId = accessCodeData.customerId, - productInstanceId = accessCodeData.productInstanceId, - activationOrderId = signedData.dataToSign.request.orderId, - data = ActivationByCardWalletRequest.Data( - cardWallet = ActivationByCardWalletRequest.CardWallet( - address = signedData.cardWalletAddress, - cardWalletConfirmation = null, // for second iteration - deployAcceptanceSignature = signedData.signature, - ), - otp = ActivationByCardWalletRequest.Otp( - rootOtp = signedData.rootOTP, - counter = signedData.otpCounter, - ), + orderId = signedData.dataToSign.request.activationOrderInfo.orderId, + cardWallet = ActivationByCardWalletRequest.CardWallet( + address = signedData.dataToSign.request.cardWalletAddress, + cardWalletConfirmation = null, // for second iteration + ), + deployAcceptanceSignature = signedData.signature, + otp = ActivationByCardWalletRequest.Otp( + rootOtp = signedData.rootOTP, + counter = signedData.otpCounter, ), ), - ) + ).getOrThrow() } } } @@ -150,22 +123,17 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( request { val authTokens = checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" } - val accessCodeData = accessCodeDataConverter.convert(authTokens) visaApi.activateByCustomerWallet( authHeader = authTokens.getAuthHeader(), body = ActivationByCustomerWalletRequest( - customerId = accessCodeData.customerId, - productInstanceId = accessCodeData.productInstanceId, - activationOrderId = signedData.dataToSign.request.orderId, - data = ActivationByCustomerWalletRequest.Data( - customerWallet = ActivationByCustomerWalletRequest.CustomerWallet( - address = signedData.customerWalletAddress, - deployAcceptanceSignature = signedData.signature, - ), + orderId = signedData.dataToSign.request.orderId, + customerWallet = ActivationByCustomerWalletRequest.CustomerWallet( + deployAcceptanceSignature = signedData.signature, + customerWalletAddress = signedData.customerWalletAddress, ), ), - ) + ).getOrThrow() } } } @@ -175,21 +143,16 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( request { val authTokens = checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" } - val accessCodeData = accessCodeDataConverter.convert(authTokens) visaApi.setPinCode( authHeader = authTokens.getAuthHeader(), body = SetPinCodeRequest( - customerId = accessCodeData.customerId, - productInstanceId = accessCodeData.productInstanceId, - activationOrderId = pinCode.activationOrderId, - data = SetPinCodeRequest.Data( - sessionKey = pinCode.sessionId, - iv = pinCode.iv, - encryptedPin = pinCode.encryptedPin, - ), + orderId = pinCode.activationOrderId, + sessionId = pinCode.sessionId, + iv = pinCode.iv, + pin = pinCode.encryptedPin, ), - ) + ).getOrThrow() } } } diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt index 9e5d4a5122..9e3be2327c 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRepository.kt @@ -1,8 +1,8 @@ package com.tangem.data.visa -import com.tangem.common.extensions.toHexString -import com.tangem.crypto.CryptoUtils +import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.visa.TangemVisaAuthApi +import com.tangem.datasource.api.visa.models.request.* import com.tangem.domain.visa.model.VisaAuthChallenge import com.tangem.domain.visa.model.VisaAuthSession import com.tangem.domain.visa.model.VisaAuthSignedChallenge @@ -20,74 +20,80 @@ internal class DefaultVisaAuthRepository @Inject constructor( override suspend fun getCardAuthChallenge(cardId: String, cardPublicKey: String): VisaAuthChallenge.Card = withContext(dispatchers.io) { - // val response = visaAuthApi.generateNonceByCard( - // cardId = cardId, - // cardPublicKey = cardPublicKey, - // ) - // - // VisaAuthChallenge.Card( - // challenge = response.nonce, - // session = VisaAuthSession(response.sessionId), - // ) - + val response = visaAuthApi.generateNonceByCardId( + GenerateNoneByCardIdRequest( + cardId = cardId, + cardPublicKey = cardPublicKey, + ), + ) VisaAuthChallenge.Card( - challenge = CryptoUtils.generateRandomBytes(length = 16).toHexString(), - session = VisaAuthSession("session"), + challenge = response.result.nonce, + session = VisaAuthSession(response.result.sessionId), ) } override suspend fun getCardWalletAuthChallenge(cardWalletAddress: String): VisaAuthChallenge.Wallet = withContext(dispatchers.io) { - // val response = visaAuthApi.generateNonceByWalletAddress( - // customerId = cardId, - // customerWalletAddress = walletPublicKey, - // ) - // - // VisaAuthChallenge.Wallet( - // challenge = response.nonce, - // session = VisaAuthSession(response.sessionId), - // ) + val response = visaAuthApi.generateNonceByCardWallet( + GenerateNoneByCardWalletRequest( + cardWalletAddress = cardWalletAddress, + ), + ) VisaAuthChallenge.Wallet( - challenge = CryptoUtils.generateRandomBytes(length = 32).toHexString(), - session = VisaAuthSession("session"), + challenge = response.result.nonce, + session = VisaAuthSession(response.result.sessionId), ) } override suspend fun getAccessTokens(signedChallenge: VisaAuthSignedChallenge): VisaAuthTokens = withContext(dispatchers.io) { - // val response = when (signedChallenge) { - // is VisaAuthSignedChallenge.ByCardPublicKey -> { - // visaAuthApi.getAccessToken( - // sessionId = signedChallenge.challenge.session.sessionId, - // signature = signedChallenge.signature, - // salt = signedChallenge.salt, - // ) - // } - // is VisaAuthSignedChallenge.ByWallet -> { - // visaAuthApi.getAccessToken( - // sessionId = signedChallenge.challenge.session.sessionId, - // signature = signedChallenge.signature, - // salt = null, - // ) - // } - // } - // - // VisaAuthTokens( - // accessToken = response.accessToken, - // refreshToken = response.refreshToken, - // ) + val response = when (signedChallenge) { + is VisaAuthSignedChallenge.ByCardPublicKey -> { + visaAuthApi.getAccessTokenByCardId( + GetAccessTokenByCardIdRequest( + sessionId = signedChallenge.challenge.session.sessionId, + signature = signedChallenge.signature, + salt = signedChallenge.salt, + ), + ) + } + is VisaAuthSignedChallenge.ByWallet -> { + visaAuthApi.getAccessTokenByCardWallet( + GetAccessTokenByCardWalletRequest( + sessionId = signedChallenge.challenge.session.sessionId, + signature = signedChallenge.signature, + ), + ) + } + } VisaAuthTokens( - accessToken = "accessToken", - refreshToken = VisaAuthTokens.RefreshToken("refreshToken"), + accessToken = response.result.accessToken, + refreshToken = VisaAuthTokens.RefreshToken( + value = response.result.refreshToken, + authType = when (signedChallenge) { + is VisaAuthSignedChallenge.ByCardPublicKey -> VisaAuthTokens.RefreshToken.Type.CardId + is VisaAuthSignedChallenge.ByWallet -> VisaAuthTokens.RefreshToken.Type.CardWallet + }, + ), ) } override suspend fun refreshAccessTokens(refreshToken: VisaAuthTokens.RefreshToken): VisaAuthTokens = withContext(dispatchers.io) { - // TODO + val response = when (refreshToken.authType) { + VisaAuthTokens.RefreshToken.Type.CardId -> + visaAuthApi.refreshCardIdAccessToken( + RefreshTokenByCardIdRequest(refreshToken = refreshToken.value), + ) + VisaAuthTokens.RefreshToken.Type.CardWallet -> + visaAuthApi.refreshCardIdAccessToken( + RefreshTokenByCardIdRequest(refreshToken = refreshToken.value), + ) + }.getOrThrow() + VisaAuthTokens( - accessToken = "accessToken", - refreshToken = VisaAuthTokens.RefreshToken("new refreshToken"), + accessToken = response.result.accessToken, + refreshToken = refreshToken.copy(value = response.result.refreshToken), ) } } \ No newline at end of file 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 5a33fb6a70..57bddf8b6c 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 @@ -15,18 +15,15 @@ class MockVisaActivationRepository @AssistedInject constructor( override suspend fun getActivationRemoteState(): VisaActivationRemoteState { return VisaActivationRemoteState.CardWalletSignatureRequired( - request = VisaCardWalletDataToSignRequest( + activationOrderInfo = VisaActivationOrderInfo( orderId = "orderId", customerId = "customerId", customerWalletAddress = "customerWalletAddress", + cardWalletAddress = null, ), ) } - override suspend fun getActivationRemoteStateLongPoll(): VisaActivationRemoteState { - return VisaActivationRemoteState.PaymentAccountDeploying - } - override suspend fun getCardWalletAcceptanceData( request: VisaCardWalletDataToSignRequest, ): VisaDataToSignByCardWallet { diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/converter/VisaActivationStatusConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/converter/VisaActivationStatusConverter.kt deleted file mode 100644 index b67a8484e1..0000000000 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/converter/VisaActivationStatusConverter.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.tangem.data.visa.converter - -import com.tangem.datasource.api.visa.models.response.CardActivationRemoteStateResponse -import com.tangem.domain.visa.model.VisaActivationOrderInfo -import com.tangem.domain.visa.model.VisaActivationRemoteState -import com.tangem.utils.converter.Converter -import kotlinx.coroutines.flow.MutableStateFlow -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class VisaActivationStatusConverter @Inject constructor() : - Converter { - - private val lastUpdatedAt = MutableStateFlow(null) - - override fun convert(value: CardActivationRemoteStateResponse): VisaActivationRemoteState { - // handle pin code error - // either we entered pin code before with an error (WasError) or after receiving an error (InProgress) - if ( - value.status == Status.AwaitingPin.stringValue && - value.stepChangeCode != null && - value.stepChangeCode == PIN_CODE_VALIDATION_ERROR - ) { - return if (lastUpdatedAt.value != value.updatedAt) { - lastUpdatedAt.value == value.updatedAt - - VisaActivationRemoteState.AwaitingPinCode( - activationOrderInfo = value.activationOrder!!.convert(), - status = VisaActivationRemoteState.AwaitingPinCode.Status.WasError, - ) - } else { - VisaActivationRemoteState.AwaitingPinCode( - activationOrderInfo = value.activationOrder!!.convert(), - status = VisaActivationRemoteState.AwaitingPinCode.Status.InProgress, - ) - } - } - - // TODO Will be implemented in the future - return VisaActivationRemoteState.Activated - } - - private fun CardActivationRemoteStateResponse.ActivationOrder.convert(): VisaActivationOrderInfo { - return VisaActivationOrderInfo( - orderId = id, - customerId = customerId, - customerWalletAddress = customerWalletAddress, - ) - } - - private enum class Status(val stringValue: String) { - AwaitingPin("AWAITING_PIN"), - // TODO complete statuses list when backend is ready - } - - private companion object { - const val PIN_CODE_VALIDATION_ERROR = 1000 - } -} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/converter/VisaActivationStatusConverterWithState.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/converter/VisaActivationStatusConverterWithState.kt new file mode 100644 index 0000000000..ff8c166266 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/converter/VisaActivationStatusConverterWithState.kt @@ -0,0 +1,81 @@ +package com.tangem.data.visa.converter + +import com.tangem.datasource.api.visa.models.response.CardActivationRemoteStateResponse +import com.tangem.domain.visa.model.VisaActivationOrderInfo +import com.tangem.domain.visa.model.VisaActivationRemoteState +import com.tangem.utils.converter.Converter +import kotlinx.coroutines.flow.MutableStateFlow + +private const val PIN_CODE_VALIDATION_ERROR = 1000 + +object VisaActivationStatusConverterWithState : + Converter { + + private val lastUpdatedAt = MutableStateFlow(null) + + override fun convert(value: CardActivationRemoteStateResponse): VisaActivationRemoteState { + // handle pin code error + // either we entered pin code before with an error (WasError) or after receiving an error (InProgress) + // TODO [REDACTED_TASK_KEY] + val result = value.result + if (result.status == Status.PinCodeRequired.stringValue && result.stepChangeCode == PIN_CODE_VALIDATION_ERROR) { + return if (lastUpdatedAt.value != result.updatedAt) { + lastUpdatedAt.value = result.updatedAt + + VisaActivationRemoteState.AwaitingPinCode( + activationOrderInfo = result.activationOrder!!.convert(), + status = VisaActivationRemoteState.AwaitingPinCode.Status.WasError, + ) + } else { + VisaActivationRemoteState.AwaitingPinCode( + activationOrderInfo = result.activationOrder!!.convert(), + status = VisaActivationRemoteState.AwaitingPinCode.Status.InProgress, + ) + } + } + + val status = Status.entries.find { it.stringValue == result.status } ?: error( + "Unknown status: ${result.status}", + ) + + return when (status) { + Status.Activated -> VisaActivationRemoteState.Activated + Status.Failed -> VisaActivationRemoteState.Failed + Status.BlockedForActivation -> VisaActivationRemoteState.BlockedForActivation + Status.CardWalletSignatureRequired -> + VisaActivationRemoteState.CardWalletSignatureRequired( + activationOrderInfo = result.activationOrder!!.convert(), + ) + Status.CustomerWalletSignatureRequired -> + VisaActivationRemoteState.CustomerWalletSignatureRequired( + activationOrderInfo = result.activationOrder!!.convert(), + ) + Status.PaymentAccountDeploying -> VisaActivationRemoteState.PaymentAccountDeploying + Status.PinCodeRequired -> VisaActivationRemoteState.AwaitingPinCode( + activationOrderInfo = result.activationOrder!!.convert(), + status = VisaActivationRemoteState.AwaitingPinCode.Status.WaitingForPinCode, + ) + Status.WaitingForActivation -> VisaActivationRemoteState.WaitingForActivationFinishing + } + } + + private fun CardActivationRemoteStateResponse.ActivationOrder.convert(): VisaActivationOrderInfo { + return VisaActivationOrderInfo( + orderId = id, + customerId = customerId, + customerWalletAddress = customerWalletAddress, + cardWalletAddress = cardWalletAddress.takeIf { it.isNotBlank() }, + ) + } + + private enum class Status(val stringValue: String) { + Activated("activated"), + Failed("failed"), + BlockedForActivation("blocked_for_activation"), + CardWalletSignatureRequired("card_wallet_signature_required"), + CustomerWalletSignatureRequired("customer_wallet_signature_required"), + PaymentAccountDeploying("payment_account_deploying"), + PinCodeRequired("pin_code_required"), + WaitingForActivation("waiting_for_activation"), + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt index e1cc459371..a173f8c5ea 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt @@ -1,8 +1,8 @@ package com.tangem.data.visa.di +import com.tangem.data.visa.DefaultVisaActivationRepository import com.tangem.data.visa.DefaultVisaAuthRepository import com.tangem.data.visa.MockVisaRepository -import com.tangem.data.visa.MockVisaActivationRepository import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaAuthRepository import com.tangem.domain.visa.repository.VisaRepository @@ -20,19 +20,19 @@ internal interface VisaDataModule { @Singleton fun bindVisaAuthRepository(repository: DefaultVisaAuthRepository): VisaAuthRepository - // @Binds - // @Singleton - // fun bindVisaActivationRepositoryFactory( - // repository: DefaultVisaActivationRepository.Factory, - // ): VisaActivationRepository.Factory - - // Mocked @Binds @Singleton fun bindVisaActivationRepositoryFactory( - repository: MockVisaActivationRepository.Factory, + repository: DefaultVisaActivationRepository.Factory, ): VisaActivationRepository.Factory + // Mocked + // @Binds + // @Singleton + // fun bindVisaActivationRepositoryFactory( + // repository: MockVisaActivationRepository.Factory, + // ): VisaActivationRepository.Factory + // @Binds // fun bindVisaRepository(repository: DefaultVisaRepository): VisaRepository diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt index 538c665358..2c7cae2eb4 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt @@ -6,6 +6,7 @@ import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.visa.TangemVisaAuthApi +import com.tangem.datasource.api.visa.models.request.RefreshTokenByCardWalletRequest import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.visa.exception.RefreshTokenExpiredException @@ -79,16 +80,18 @@ internal class VisaApiRequestMaker @Inject constructor( } private suspend fun refreshAccessTokens(refreshToken: VisaAuthTokens.RefreshToken): VisaAuthTokens { - val result = visaAuthApi.refreshAccessToken(refreshToken.value).getOrThrow() + val result = visaAuthApi.refreshCardWalletAccessToken( + RefreshTokenByCardWalletRequest(refreshToken = refreshToken.value), + ).getOrThrow() return VisaAuthTokens( - accessToken = result.accessToken, - refreshToken = VisaAuthTokens.RefreshToken(result.refreshToken), + accessToken = result.result.accessToken, + refreshToken = refreshToken.copy(value = result.result.refreshToken), ) } @Throws - private suspend fun getAuthTokens(userWalletId: UserWalletId): VisaAuthTokens { + private fun getAuthTokens(userWalletId: UserWalletId): VisaAuthTokens { val userWallet = findVisaUserWallet(userWalletId) val status = userWallet.scanResponse.visaCardActivationStatus ?: error("Visa card activation status not found") @@ -99,7 +102,7 @@ internal class VisaApiRequestMaker @Inject constructor( return (status as? VisaCardActivationStatus.Activated)?.visaAuthTokens ?: error("Visa card is not activated") } - private suspend fun findVisaUserWallet(userWalletId: UserWalletId): UserWallet { + private fun findVisaUserWallet(userWalletId: UserWalletId): UserWallet { val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { "No user wallet found: $userWalletId" } diff --git a/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaWalletPublicKeyUtility.kt b/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaWalletPublicKeyUtility.kt index 2bc3ecbc4a..758ec7a589 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaWalletPublicKeyUtility.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/common/visa/VisaWalletPublicKeyUtility.kt @@ -57,7 +57,7 @@ object VisaWalletPublicKeyUtility { either { val address = generateAddressOnSecp256k1(publicKey).bind() - if (address.value != targetAddress) { + if (address.value.lowercase() != targetAddress.lowercase()) { raise(VisaActivationError.AddressNotMatched) } } diff --git a/domain/visa/build.gradle.kts b/domain/visa/build.gradle.kts index 8ce37bc6d1..dab90f1ecd 100644 --- a/domain/visa/build.gradle.kts +++ b/domain/visa/build.gradle.kts @@ -20,6 +20,9 @@ dependencies { implementation(projects.domain.tokens.models) implementation(projects.domain.appCurrency.models) + /** Security */ + implementation(deps.spongecastle.core) + /** Libs - Other */ implementation(deps.jodatime) implementation(deps.androidx.paging.runtime) diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/error/VisaError.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/error/VisaError.kt index f73861c111..bf9bd888dd 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/error/VisaError.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/error/VisaError.kt @@ -44,6 +44,7 @@ enum class VisaActivationError( FailedToCreateAddress(104003007), AddressNotMatched(104003008), InconsistentRemoteState(104003009), + FailedRemoteState(104003010), } object VisaAuthorizationAPIError : UniversalError { 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 index 76389945fd..0ee54c8be3 100644 --- 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 @@ -10,4 +10,5 @@ data class VisaActivationOrderInfo( @Json(name = "orderId") val orderId: String, @Json(name = "customerId") val customerId: String, @Json(name = "customerWalletAddress") val customerWalletAddress: String, + @Json(name = "cardWalletAddress") val cardWalletAddress: 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 889037bee3..b34d72d2a6 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 @@ -1,6 +1,7 @@ package com.tangem.domain.visa.model import com.squareup.moshi.* +import com.tangem.domain.visa.model.VisaActivationRemoteState.* import kotlinx.serialization.Serializable @Serializable @@ -8,7 +9,7 @@ sealed class VisaActivationRemoteState { @Serializable data class CardWalletSignatureRequired( - val request: VisaCardWalletDataToSignRequest, + val activationOrderInfo: VisaActivationOrderInfo, ) : VisaActivationRemoteState() @Serializable @@ -38,6 +39,9 @@ sealed class VisaActivationRemoteState { @Serializable data object BlockedForActivation : VisaActivationRemoteState() + @Serializable + data object Failed : VisaActivationRemoteState() + companion object { val jsonAdapter: VisaActivationRemoteState_JsonAdapter = VisaActivationRemoteState_JsonAdapter() } @@ -46,7 +50,6 @@ sealed class VisaActivationRemoteState { @Suppress("ClassNaming") class VisaActivationRemoteState_Json( @Json(name = "type") val type: VisaActivationRemoteState_Type, - @Json(name = "requestCardWallet") val requestCardWallet: VisaCardWalletDataToSignRequest? = null, @Json(name = "activationOrderInfo") val activationOrderInfo: VisaActivationOrderInfo? = null, @Json(name = "awaiting_pin_code_status") val awaitingPinCodeStatus: AwaitingPinCodeStatus_Type? = null, ) @@ -62,6 +65,7 @@ enum class VisaActivationRemoteState_Type { WaitingForActivationFinishing, Activated, BlockedForActivation, + Failed, } @Suppress("ClassNaming") @@ -77,64 +81,66 @@ class VisaActivationRemoteState_JsonAdapter { fun fromJson(value: VisaActivationRemoteState_Json): VisaActivationRemoteState { return when (value.type) { VisaActivationRemoteState_Type.CardWalletSignatureRequired -> - VisaActivationRemoteState.CardWalletSignatureRequired(value.requestCardWallet!!) + CardWalletSignatureRequired(value.activationOrderInfo!!) VisaActivationRemoteState_Type.CustomerWalletSignatureRequired -> - VisaActivationRemoteState.CustomerWalletSignatureRequired(value.activationOrderInfo!!) - VisaActivationRemoteState_Type.PaymentAccountDeploying -> VisaActivationRemoteState.PaymentAccountDeploying + CustomerWalletSignatureRequired(value.activationOrderInfo!!) + VisaActivationRemoteState_Type.PaymentAccountDeploying -> PaymentAccountDeploying VisaActivationRemoteState_Type.WaitingPinCode -> - VisaActivationRemoteState.AwaitingPinCode( + AwaitingPinCode( activationOrderInfo = value.activationOrderInfo!!, status = when (value.awaitingPinCodeStatus!!) { AwaitingPinCodeStatus_Type.WaitingForPinCode -> - VisaActivationRemoteState.AwaitingPinCode.Status.WaitingForPinCode + AwaitingPinCode.Status.WaitingForPinCode AwaitingPinCodeStatus_Type.InProgress -> - VisaActivationRemoteState.AwaitingPinCode.Status.InProgress + AwaitingPinCode.Status.InProgress AwaitingPinCodeStatus_Type.WasError -> - VisaActivationRemoteState.AwaitingPinCode.Status.WasError + AwaitingPinCode.Status.WasError }, ) VisaActivationRemoteState_Type.WaitingForActivationFinishing -> - VisaActivationRemoteState.WaitingForActivationFinishing - VisaActivationRemoteState_Type.Activated -> VisaActivationRemoteState.Activated - VisaActivationRemoteState_Type.BlockedForActivation -> VisaActivationRemoteState.BlockedForActivation + WaitingForActivationFinishing + VisaActivationRemoteState_Type.Activated -> Activated + VisaActivationRemoteState_Type.BlockedForActivation -> BlockedForActivation + VisaActivationRemoteState_Type.Failed -> Failed } } @ToJson fun toJson(value: VisaActivationRemoteState): VisaActivationRemoteState_Json { return when (value) { - is VisaActivationRemoteState.CardWalletSignatureRequired -> + is CardWalletSignatureRequired -> VisaActivationRemoteState_Json( type = VisaActivationRemoteState_Type.CardWalletSignatureRequired, - requestCardWallet = value.request, + activationOrderInfo = value.activationOrderInfo, ) - is VisaActivationRemoteState.CustomerWalletSignatureRequired -> + is CustomerWalletSignatureRequired -> VisaActivationRemoteState_Json( type = VisaActivationRemoteState_Type.CustomerWalletSignatureRequired, activationOrderInfo = value.activationOrderInfo, ) - is VisaActivationRemoteState.PaymentAccountDeploying -> + is PaymentAccountDeploying -> VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.PaymentAccountDeploying) - is VisaActivationRemoteState.AwaitingPinCode -> + is AwaitingPinCode -> VisaActivationRemoteState_Json( VisaActivationRemoteState_Type.WaitingPinCode, activationOrderInfo = value.activationOrderInfo, awaitingPinCodeStatus = when (value.status) { - VisaActivationRemoteState.AwaitingPinCode.Status.WaitingForPinCode -> + AwaitingPinCode.Status.WaitingForPinCode -> AwaitingPinCodeStatus_Type.WaitingForPinCode - VisaActivationRemoteState.AwaitingPinCode.Status.InProgress -> + AwaitingPinCode.Status.InProgress -> AwaitingPinCodeStatus_Type.InProgress - VisaActivationRemoteState.AwaitingPinCode.Status.WasError -> + AwaitingPinCode.Status.WasError -> AwaitingPinCodeStatus_Type.WasError }, ) - is VisaActivationRemoteState.WaitingForActivationFinishing -> + is WaitingForActivationFinishing -> VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.WaitingForActivationFinishing) - is VisaActivationRemoteState.Activated -> VisaActivationRemoteState_Json( + is Activated -> VisaActivationRemoteState_Json( VisaActivationRemoteState_Type.Activated, ) - is VisaActivationRemoteState.BlockedForActivation -> + is BlockedForActivation -> VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.BlockedForActivation) + is Failed -> VisaActivationRemoteState_Json(VisaActivationRemoteState_Type.Failed) } } } \ No newline at end of file diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthTokens.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthTokens.kt index b39a31f25b..32984786d7 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthTokens.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaAuthTokens.kt @@ -12,8 +12,15 @@ data class VisaAuthTokens( ) { @Serializable - @JvmInline - value class RefreshToken(val value: String) + @JsonClass(generateAdapter = true) + data class RefreshToken( + @Json(name = "value") val value: String, + @Json(name = "authType") val authType: Type, + ) { + enum class Type { + CardId, CardWallet, + } + } } fun VisaAuthTokens.getAuthHeader(): String { 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 831a4b1d6c..05f50c2d2b 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,3 +1,12 @@ package com.tangem.domain.visa.model -typealias VisaCardWalletDataToSignRequest = VisaActivationOrderInfo \ No newline at end of file +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import kotlinx.serialization.Serializable + +@Serializable +@JsonClass(generateAdapter = true) +data class VisaCardWalletDataToSignRequest( + @Json(name = "activationOrderInfo") val activationOrderInfo: VisaActivationOrderInfo, + @Json(name = "cardWalletAddress") val cardWalletAddress: String, +) \ No newline at end of file diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCustomerWalletDataToSignRequest.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCustomerWalletDataToSignRequest.kt index 0103e34df4..7532659cad 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCustomerWalletDataToSignRequest.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaCustomerWalletDataToSignRequest.kt @@ -9,4 +9,5 @@ import kotlinx.serialization.Serializable data class VisaCustomerWalletDataToSignRequest( @Json(name = "orderId") val orderId: String, @Json(name = "card_wallet_address") val cardWalletAddress: 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/VisaDataToSignByCardWallet.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataToSignByCardWallet.kt index 9e95f713e1..aac9421715 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 @@ -8,10 +8,9 @@ data class VisaDataToSignByCardWallet( val hashToSign: String, ) -fun VisaDataToSignByCardWallet.sign(cardWalletAddress: String, rootOTP: String, otpCounter: Int, signature: String) = +fun VisaDataToSignByCardWallet.sign(rootOTP: String, otpCounter: Int, signature: String) = VisaSignedActivationDataByCardWallet( dataToSign = this, - cardWalletAddress = cardWalletAddress, rootOTP = rootOTP, otpCounter = otpCounter, signature = signature, diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaSignedActivationDataByCardWallet.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaSignedActivationDataByCardWallet.kt index 5b37c067ec..246767c013 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaSignedActivationDataByCardWallet.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaSignedActivationDataByCardWallet.kt @@ -2,7 +2,6 @@ package com.tangem.domain.visa.model class VisaSignedActivationDataByCardWallet( val dataToSign: VisaDataToSignByCardWallet, - val cardWalletAddress: String, val rootOTP: String, val otpCounter: Int, val signature: String, diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt index 6fda7af7df..c15d97e074 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt @@ -1,4 +1,5 @@ @file:Suppress("MaximumLineLength") + package com.tangem.domain.visa import android.util.Base64 @@ -14,14 +15,12 @@ import java.security.spec.X509EncodedKeySpec import javax.crypto.Cipher import javax.crypto.KeyGenerator import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec private const val KEY_SIZE = 256 -class SetVisaPinCodeUseCase( - private val visaActivationRepositoryFactory: VisaActivationRepository.Factory, -) { +class SetVisaPinCodeUseCase(private val visaActivationRepositoryFactory: VisaActivationRepository.Factory) { + @Suppress("MagicNumber") suspend operator fun invoke( visaCardId: VisaCardId, activationOrderId: String, @@ -29,19 +28,17 @@ class SetVisaPinCodeUseCase( ): Either = Either.catch { val visaActivationRepository = visaActivationRepositoryFactory.create(visaCardId) val rsaPublicKey = visaActivationRepository.getPinCodeRsaEncryptionPublicKey() + val formattedPin = "24$pinCode${"f".repeat(n = 8)}FF" val sessionKey = generateSessionKey() val sessionId = getSessionId(rsaPublicKey, sessionKey) - val secureRandom = SecureRandom() - val iv = ByteArray(size = 16) - secureRandom.nextBytes(iv) + val iv = generateIV() val ivParameterSpec = IvParameterSpec(iv) val ivPayload = Base64.encodeToString(iv, Base64.NO_WRAP) - val aesKey = SecretKeySpec(sessionKey.encoded, 0, sessionKey.encoded.size, "AES") - val aesCipher = Cipher.getInstance("AES/CBC/PKCS5PADDING") - aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, ivParameterSpec) - val encryptedPin = Base64.encodeToString(aesCipher.doFinal(pinCode.toByteArray()), Base64.NO_WRAP) + val aesCipher = Cipher.getInstance("AES/GCM/NoPadding") + aesCipher.init(Cipher.ENCRYPT_MODE, sessionKey, ivParameterSpec) + val encryptedPin = Base64.encodeToString(aesCipher.doFinal(formattedPin.encodeToByteArray()), Base64.NO_WRAP) visaActivationRepository.sendPinCode( VisaEncryptedPinCode( @@ -59,6 +56,12 @@ class SetVisaPinCodeUseCase( return generator.generateKey() } + private fun generateIV(): ByteArray { + val iv = ByteArray(size = 16) + SecureRandom().nextBytes(iv) + return iv + } + private fun getPublicKey(rsaPublicKey: String): PublicKey { val keyBytes = Base64.decode(rsaPublicKey, Base64.NO_WRAP) val spec = X509EncodedKeySpec(keyBytes) @@ -67,8 +70,9 @@ class SetVisaPinCodeUseCase( private fun getSessionId(rsaPublicKey: String, sessionKey: Key): String { val publicKey = getPublicKey(rsaPublicKey) - val cipher = Cipher.getInstance("RSA") + val cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding") cipher.init(Cipher.ENCRYPT_MODE, publicKey) - return Base64.encodeToString(cipher.doFinal(sessionKey.encoded), Base64.NO_WRAP) + val base64SessionKey = Base64.encodeToString(sessionKey.encoded, Base64.NO_WRAP) + return Base64.encodeToString(cipher.doFinal(base64SessionKey.encodeToByteArray()), Base64.NO_WRAP) } } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt index f6d451fb7b..339f8c7e9c 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt @@ -6,8 +6,6 @@ interface VisaActivationRepository { suspend fun getActivationRemoteState(): VisaActivationRemoteState - suspend fun getActivationRemoteStateLongPoll(): VisaActivationRemoteState - suspend fun getCardWalletAcceptanceData(request: VisaCardWalletDataToSignRequest): VisaDataToSignByCardWallet suspend fun getCustomerWalletAcceptanceData( 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 c18f5585e7..047c16e1d5 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 @@ -167,7 +167,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( params = OnboardingVisaOtherWalletComponent.Params( childParams = childParams, onDone = { - model.stackNavigation.pushNew( + model.stackNavigation.replaceAll( OnboardingVisaRoute.PinCode(activationOrderInfo = it, pinCodeValidationError = false), ) }, @@ -183,7 +183,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( params = OnboardingVisaPinCodeComponent.Params( childParams = childParams, onDone = { - model.stackNavigation.pushNew( + model.stackNavigation.replaceAll( OnboardingVisaRoute.InProgress(from = OnboardingVisaRoute.InProgress.From.PinCode), ) }, @@ -199,7 +199,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor( params = OnboardingVisaApproveComponent.Params( childParams = childParams, onDone = { - model.stackNavigation.pushNew( + model.stackNavigation.replaceAll( OnboardingVisaRoute.InProgress(from = OnboardingVisaRoute.InProgress.From.Approve), ) }, 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 a07b1acd3a..6c1dcc74e1 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 @@ -20,7 +20,6 @@ import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaAuthRepository 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.child.welcome.model.analytics.ONBOARDING_SOURCE import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.VisaAnalyticsEvent import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent @@ -55,9 +54,12 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor( cardPublicKey = params.scanResponse.card.cardPublicKey.toHexString(), ), ) - private val activationStatus = - params.scanResponse.visaCardActivationStatus as? VisaCardActivationStatus.NotStartedActivation - ?: error("Visa activation status is not set or incorrect for this step") + + private val activationInput = when (val status = params.scanResponse.visaCardActivationStatus) { + is VisaCardActivationStatus.NotStartedActivation -> status.activationInput + is VisaCardActivationStatus.ActivationStarted -> status.activationInput + else -> error("Visa activation status is not set or incorrect for this step") + } private val _uiState = MutableStateFlow(getInitialState()) @@ -153,8 +155,8 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor( modelScope.launch { val challengeToSign = runCatching { visaAuthRepository.getCardAuthChallenge( - cardId = activationStatus.activationInput.cardId, - cardPublicKey = activationStatus.activationInput.cardPublicKey, + cardId = activationInput.cardId, + cardPublicKey = activationInput.cardPublicKey, ) }.getOrElse { loading(false) @@ -167,7 +169,7 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor( accessCode = accessCode, authorizationChallenge = challengeToSign, ), - activationInput = activationStatus.activationInput, + activationInput = activationInput, ) val resultData = when (result) { @@ -175,7 +177,7 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor( loading(false) uiMessageSender.showErrorDialog(result.error.universalError) analyticsEventsHandler.send( - VisaAnalyticsEvent.Errors(result.error.code.toString(), ONBOARDING_SOURCE), + VisaAnalyticsEvent.ErrorOnboarding(result.error.universalError), ) return@launch } @@ -190,14 +192,16 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor( return@launch } - val targetAddress = result.data.signedActivationData.dataToSign.request.customerWalletAddress + val targetAddress = + result.data.signedActivationData.dataToSign.request.activationOrderInfo.customerWalletAddress modelScope.launch { onDone.emit( ActivationReadyEvent( customerWalletDataToSignRequest = VisaCustomerWalletDataToSignRequest( - orderId = result.data.signedActivationData.dataToSign.request.orderId, - cardWalletAddress = result.data.signedActivationData.cardWalletAddress, + orderId = result.data.signedActivationData.dataToSign.request.activationOrderInfo.orderId, + cardWalletAddress = result.data.signedActivationData.dataToSign.request.cardWalletAddress, + customerWalletAddress = targetAddress, ), newScanResponse = params.scanResponse.copy( card = result.data.newCardDTO, 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 9f14644a5c..31f7aa82ed 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 @@ -16,7 +16,6 @@ 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 -import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.ONBOARDING_SOURCE import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.VisaAnalyticsEvent import com.tangem.sdk.api.TangemSdkManager @@ -88,9 +87,7 @@ internal class OnboardingVisaApproveModel @Inject constructor( is CompletionResult.Failure -> { loading(false) uiMessageSender.showErrorDialog(result.error.universalError) - analyticsEventHandler.send( - VisaAnalyticsEvent.Errors(result.error.code.toString(), ONBOARDING_SOURCE), - ) + analyticsEventHandler.send(VisaAnalyticsEvent.ErrorOnboarding(result.error.universalError)) return@launch } is CompletionResult.Success -> result.data 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 a138341670..c0fdbbd57d 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 @@ -22,6 +22,7 @@ import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent.Config import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent.Params import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent +import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.VisaAnalyticsEvent import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.delay @@ -57,10 +58,14 @@ internal class OnboardingVisaInProgressModel @Inject constructor( init { analyticsEventHandler.send(OnboardingVisaAnalyticsEvent.ActivationInProgressScreen) + runShortPolling() + } + + private fun runShortPolling() { modelScope.launch { while (true) { val result = runCatching { - visaActivationRepository.getActivationRemoteStateLongPoll() + visaActivationRepository.getActivationRemoteState() }.getOrNull() ?: continue when (result) { @@ -68,6 +73,16 @@ internal class OnboardingVisaInProgressModel @Inject constructor( VisaActivationRemoteState.BlockedForActivation, -> { uiMessageSender.showErrorDialog(VisaActivationError.InconsistentRemoteState) + analyticsEventHandler.send( + VisaAnalyticsEvent.ErrorOnboarding(VisaActivationError.InconsistentRemoteState), + ) + return@launch + } + VisaActivationRemoteState.Failed -> { + uiMessageSender.showErrorDialog(VisaActivationError.FailedRemoteState) + analyticsEventHandler.send( + VisaAnalyticsEvent.ErrorOnboarding(VisaActivationError.FailedRemoteState), + ) return@launch } is VisaActivationRemoteState.CustomerWalletSignatureRequired, @@ -86,14 +101,14 @@ internal class OnboardingVisaInProgressModel @Inject constructor( } } - delay(timeMillis = 1000) + delay(timeMillis = 2000) } } } private suspend inline fun navigateToPinCodeIfNeeded( remoteState: VisaActivationRemoteState.AwaitingPinCode, - returnBlock: () -> Unit, + complete: () -> Unit, ) { when (remoteState.status) { VisaActivationRemoteState.AwaitingPinCode.Status.WaitingForPinCode -> { @@ -105,7 +120,7 @@ internal class OnboardingVisaInProgressModel @Inject constructor( ), ), ) - returnBlock() + complete() } VisaActivationRemoteState.AwaitingPinCode.Status.WasError -> { onDone.emit( @@ -116,10 +131,10 @@ internal class OnboardingVisaInProgressModel @Inject constructor( ), ), ) - returnBlock() + complete() } VisaActivationRemoteState.AwaitingPinCode.Status.InProgress -> { - /** waiting for new state */ + /** waiting for the new state */ } } } 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 fa1adb5d3c..28fca46909 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 @@ -51,7 +51,7 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor( modelScope.launch { while (true) { val result = runCatching { - visaActivationRepository.getActivationRemoteStateLongPoll() + visaActivationRepository.getActivationRemoteState() }.getOrNull() if (result is VisaActivationRemoteState.AwaitingPinCode) { @@ -59,7 +59,7 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor( break } - delay(timeMillis = 1000) + delay(timeMillis = 2000) } } } 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 432a024889..3deb78230c 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 @@ -6,15 +6,12 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.extensions.resourceReference -import com.tangem.datasource.local.visa.VisaAuthTokenStorage -import com.tangem.domain.models.scan.ScanResponse +import com.tangem.core.ui.utils.showErrorDialog import com.tangem.domain.visa.SetVisaPinCodeUseCase -import com.tangem.domain.visa.model.VisaCardActivationStatus +import com.tangem.domain.visa.error.VisaAuthorizationAPIError import com.tangem.domain.visa.model.VisaCardId -import com.tangem.domain.wallets.builder.UserWalletBuilder -import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.models.UserWallet import com.tangem.features.onboarding.v2.impl.R import com.tangem.features.onboarding.v2.visa.impl.child.pincode.OnboardingVisaPinCodeComponent import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state.OnboardingVisaPinCodeUM @@ -25,7 +22,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import javax.inject.Inject @Stable @@ -34,12 +30,9 @@ import javax.inject.Inject internal class OnboardingVisaPinCodeModel @Inject constructor( paramsContainer: ParamsContainer, override val dispatchers: CoroutineDispatcherProvider, - private val userWalletBuilderFactory: UserWalletBuilder.Factory, - private val userWalletsListManager: UserWalletsListManager, - private val authTokenStorage: VisaAuthTokenStorage, - private val otpStorage: VisaAuthTokenStorage, private val setVisaPinCodeUseCase: SetVisaPinCodeUseCase, private val analyticsEventHandler: AnalyticsEventHandler, + private val uiMessageSender: UiMessageSender, ) : Model() { private val params = paramsContainer.require() @@ -102,11 +95,11 @@ internal class OnboardingVisaPinCodeModel @Inject constructor( activationOrderId = params.activationOrderInfo.orderId, ).onLeft { loading(false) + uiMessageSender.showErrorDialog(VisaAuthorizationAPIError) return@launch } - saveWallet() - + onDone.emit(Unit) loading(false) } } @@ -114,26 +107,4 @@ internal class OnboardingVisaPinCodeModel @Inject constructor( private fun loading(state: Boolean) { _uiState.update { it.copy(submitButtonLoading = state) } } - - private suspend fun saveWallet() { - val userWallet = createUserWallet(params.scanResponse) - userWalletsListManager.save(userWallet) - authTokenStorage.remove(params.scanResponse.card.cardId) - otpStorage.remove(params.scanResponse.card.cardId) - onDone.emit(Unit) - } - - private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet = withContext(dispatchers.io) { - val newActivationStatus = VisaCardActivationStatus.Activated( - visaAuthTokens = authTokenStorage.get(scanResponse.card.cardId) - ?: error("Impossible state. Wrong feature implementation"), - ) - - requireNotNull( - value = userWalletBuilderFactory.create( - scanResponse = scanResponse.copy(visaCardActivationStatus = newActivationStatus), - ).build(), - lazyMessage = { "User wallet not created" }, - ) - } } \ No newline at end of file diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/PinCodeValidation.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/PinCodeValidation.kt index 99c721134d..4e026f18ec 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/PinCodeValidation.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/pincode/model/PinCodeValidation.kt @@ -22,10 +22,11 @@ internal object PinCodeValidation { } private fun validateNoRepeatedDigits(pinCode: String): Boolean { - return pinCode.toSet().size == PIN_CODE_LENGTH + return pinCode.toSet().size > 1 } private fun validateNoConsecutiveDigits(pinCode: String): Boolean { - return pinCode.windowed(2).all { it[0] + 1 == it[1] }.not() + return pinCode.zipWithNext().any { it.second != it.first + 1 } && + pinCode.zipWithNext().any { it.second != it.first - 1 } } } \ 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 index 35869da702..7c602c0e14 100644 --- 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 @@ -15,7 +15,6 @@ 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.model.analytics.ONBOARDING_SOURCE import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.OnboardingVisaAnalyticsEvent import com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics.VisaAnalyticsEvent import com.tangem.features.onboarding.v2.visa.impl.child.welcome.ui.state.OnboardingVisaWelcomeUM @@ -96,9 +95,7 @@ internal class OnboardingVisaWelcomeModel @Inject constructor( is CompletionResult.Failure -> { loading(false) uiMessageSender.showErrorDialog(result.error.universalError) - analyticsEventsHandler.send( - VisaAnalyticsEvent.Errors(result.error.code.toString(), ONBOARDING_SOURCE), - ) + analyticsEventsHandler.send(VisaAnalyticsEvent.ErrorOnboarding(result.error.universalError)) return@launch } is CompletionResult.Success -> result.data @@ -112,15 +109,17 @@ internal class OnboardingVisaWelcomeModel @Inject constructor( return@launch } - val targetAddress = result.data.signedActivationData.dataToSign.request.customerWalletAddress + val request = result.data.signedActivationData.dataToSign.request + val targetAddress = request.activationOrderInfo.customerWalletAddress modelScope.launch { onDone.emit( DoneEvent.WelcomeBackDone( ActivationReadyEvent( customerWalletDataToSignRequest = VisaCustomerWalletDataToSignRequest( - orderId = result.data.signedActivationData.dataToSign.request.orderId, - cardWalletAddress = result.data.signedActivationData.cardWalletAddress, + orderId = request.activationOrderInfo.orderId, + cardWalletAddress = request.cardWalletAddress, + customerWalletAddress = targetAddress, ), newScanResponse = params.scanResponse.copy( card = result.data.newCardDTO, diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/model/analytics/VisaAnalyticsEvent.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/model/analytics/VisaAnalyticsEvent.kt index f31e8c0a83..e77e0bb36f 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/model/analytics/VisaAnalyticsEvent.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/visa/impl/child/welcome/model/analytics/VisaAnalyticsEvent.kt @@ -1,6 +1,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.welcome.model.analytics import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.error.UniversalError internal const val ONBOARDING_SOURCE = "Onboarding" internal const val MAIN_SOURCE = "Main" @@ -10,14 +11,11 @@ internal sealed class VisaAnalyticsEvent( params: Map = mapOf(), ) : AnalyticsEvent("Visa", event, params) { - data class Errors( - val errorCode: String, - val source: String, - ) : VisaAnalyticsEvent( + data class ErrorOnboarding(val error: UniversalError) : VisaAnalyticsEvent( event = "Errors", params = mapOf( - "Error Code" to errorCode, - "Source" to source, + "Error Code" to error.errorCode.toString(), + "Source" to ONBOARDING_SOURCE, ), ) } \ 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 8bc8b14032..9961ec2459 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 @@ -1,10 +1,10 @@ package com.tangem.features.onboarding.v2.visa.impl.model import androidx.compose.runtime.Stable -import com.arkivanov.decompose.ExperimentalDecomposeApi import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.pushNew +import com.arkivanov.decompose.router.stack.replaceAll import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer @@ -12,6 +12,7 @@ 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.VisaCardWalletDataToSignRequest import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onboarding.v2.visa.api.OnboardingVisaComponent @@ -83,14 +84,13 @@ internal class OnboardingVisaModel @Inject constructor( ) } - @OptIn(ExperimentalDecomposeApi::class) fun navigateFromInProgress(event: OnboardingVisaInProgressComponent.Params.DoneEvent) { when (event) { OnboardingVisaInProgressComponent.Params.DoneEvent.Activated -> { modelScope.launch { onDone.emit(Unit) } } is OnboardingVisaInProgressComponent.Params.DoneEvent.NavigateTo -> { - stackNavigation.pushNew(event.route) + stackNavigation.replaceAll(event.route) } } } @@ -145,10 +145,17 @@ internal class OnboardingVisaModel @Inject constructor( is VisaCardActivationStatus.ActivationStarted -> { when (val remoteState = activationStatus.remoteState) { is VisaActivationRemoteState.CardWalletSignatureRequired -> { - OnboardingVisaRoute.WelcomeBack( - activationInput = activationStatus.activationInput, - dataToSignByCardWalletRequest = remoteState.request, - ) + if (activationStatus.activationInput.isAccessCodeSet) { + OnboardingVisaRoute.WelcomeBack( + activationInput = activationStatus.activationInput, + dataToSignByCardWalletRequest = VisaCardWalletDataToSignRequest( + activationOrderInfo = remoteState.activationOrderInfo, + cardWalletAddress = activationStatus.cardWalletAddress, + ), + ) + } else { + OnboardingVisaRoute.AccessCode + } } is VisaActivationRemoteState.CustomerWalletSignatureRequired -> { remoteState.getRoute(activationStatus) @@ -167,6 +174,7 @@ internal class OnboardingVisaModel @Inject constructor( } VisaActivationRemoteState.Activated, VisaActivationRemoteState.BlockedForActivation, + VisaActivationRemoteState.Failed, -> error("Activation status is not correct for onboarding flow") } } @@ -182,6 +190,7 @@ internal class OnboardingVisaModel @Inject constructor( val request = VisaCustomerWalletDataToSignRequest( orderId = this.activationOrderInfo.orderId, cardWalletAddress = activationStatus.cardWalletAddress, + customerWalletAddress = this.activationOrderInfo.customerWalletAddress, ) val preparationDataForApprove = PreparationDataForApprove( customerWalletAddress = this.activationOrderInfo.customerWalletAddress,