From 3f6bcd3a0e80edafa5a59dd73894a083e5ef2c24 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 20 Aug 2025 11:43:43 +0400 Subject: [PATCH] Updated on 2026-08-14 --- app/build.gradle.kts | 2 +- .../sdk/impl/DefaultTangemSdkManager.kt | 11 ++-- .../domain/sdk/impl/MockTangemSdkManager.kt | 4 +- .../visa/VisaCustomerWalletApproveTask.kt | 33 +++++++----- .../tangem/datasource/api/pay/TangemPayApi.kt | 22 ++++---- .../GenerateNonceByCustomerWalletRequest.kt | 10 ++++ .../GetTokenByCustomerWalletRequest.kt | 12 +++++ data/visa/build.gradle.kts | 1 + .../tangem/data/pay/DefaultKycRepository.kt | 43 ++++++++++------ .../visa/DefaultVisaActivationRepository.kt | 20 ++++---- .../data/visa/DefaultVisaAuthRepository.kt | 33 ++++++++++++ .../model/VisaDataToSignByCustomerWallet.kt | 2 +- .../VisaSignedChallengeByCustomerWallet.kt | 6 +++ .../domain/pay/repository/KycRepository.kt | 5 +- .../visa/repository/VisaAuthRepository.kt | 10 ++++ .../com/tangem/features/kyc/KycComponent.kt | 7 ++- features/kyc/impl/build.gradle.kts | 3 +- .../features/kyc/DefaultKycComponent.kt | 50 +++++++------------ .../tangem/features/kyc/DefaultKycModel.kt | 32 ++++++++++++ .../tangem/features/kyc/di/FeatureModule.kt | 14 ++++++ features/wallet/impl/build.gradle.kts | 1 + .../com/tangem/sdk/api/TangemSdkManager.kt | 4 +- settings.gradle.kts | 2 +- 23 files changed, 226 insertions(+), 101 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/GenerateNonceByCustomerWalletRequest.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/GetTokenByCustomerWalletRequest.kt create mode 100644 domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaSignedChallengeByCustomerWallet.kt create mode 100644 features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 31cb895042..b22a62d437 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -227,8 +227,8 @@ dependencies { implementation(projects.features.usedesk.impl) implementation(projects.features.hotWallet.api) implementation(projects.features.hotWallet.impl) + implementation(projects.features.kyc.api) //TODO disable for release because of the permissions - // implementation(projects.features.kyc.api) // implementation(projects.features.kyc.impl) implementation(projects.features.welcome.api) implementation(projects.features.welcome.impl) diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt index aaf4f87888..09a9039974 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/DefaultTangemSdkManager.kt @@ -24,9 +24,7 @@ import com.tangem.domain.wallets.derivations.derivationStyleProvider import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.visa.model.VisaActivationInput -import com.tangem.domain.visa.model.VisaDataForApprove -import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet +import com.tangem.domain.visa.model.* import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles import com.tangem.operations.ScanTask import com.tangem.operations.derivation.DerivationTaskResponse @@ -501,7 +499,12 @@ internal class DefaultTangemSdkManager( ): CompletionResult { return runTaskAsyncReturnOnMain( runnable = VisaCustomerWalletApproveTask( - visaDataForApprove = visaDataForApprove, + VisaCustomerWalletApproveTask.Input( + cardId = visaDataForApprove.customerWalletCardId, + targetAddress = visaDataForApprove.targetAddress, + hashToSign = visaDataForApprove.dataToSign.hashToSign, + sign = visaDataForApprove.dataToSign::sign, + ), ), cardId = visaDataForApprove.customerWalletCardId, initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)), diff --git a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt index edca4bea1a..1568e7dfc8 100644 --- a/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/sdk/impl/MockTangemSdkManager.kt @@ -18,9 +18,7 @@ import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.visa.model.VisaActivationInput -import com.tangem.domain.visa.model.VisaDataForApprove -import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet +import com.tangem.domain.visa.model.* import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.preflightread.PreflightReadFilter import com.tangem.operations.wallet.CreateWalletResponse 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 a44a04e051..4fa773c18c 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.toKeccak import com.tangem.blockchain.common.UnmarshalHelper import com.tangem.common.CompletionResult import com.tangem.common.card.Card @@ -10,7 +11,6 @@ import com.tangem.common.core.CardSession 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.toDecompressedPublicKey import com.tangem.common.extensions.toHexString import com.tangem.core.error.ext.tangemError @@ -22,15 +22,13 @@ import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.visa.error.VisaActivationError -import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet -import com.tangem.domain.visa.model.sign import com.tangem.operations.ScanTask import com.tangem.operations.derivation.DeriveWalletPublicKeyTask import com.tangem.operations.sign.SignHashCommand class VisaCustomerWalletApproveTask( - private val visaDataForApprove: VisaDataForApprove, + private val visaDataForApprove: Input, ) : CardSessionRunnable { override fun run(session: CardSession, callback: CompletionCallback) { @@ -44,7 +42,7 @@ class VisaCustomerWalletApproveTask( return } - if (visaDataForApprove.customerWalletCardId != null && card.cardId != visaDataForApprove.customerWalletCardId) { + if (visaDataForApprove.cardId != null && card.cardId != visaDataForApprove.cardId) { callback(CompletionResult.Failure(VisaActivationError.CardIdNotMatched.tangemError)) return } @@ -153,6 +151,12 @@ class VisaCustomerWalletApproveTask( ) } + // TODO: [REDACTED_TASK_KEY] - Get this public function from Blockchain SDK + private fun hashPersonalMessage(message: ByteArray): ByteArray { + val prefix = "\u0019Ethereum Signed Message:\n${message.size}".toByteArray() + return (prefix + message).toKeccak() + } + private fun signApproveData( targetWalletPublicKey: ByteArray, derivationPath: DerivationPath?, @@ -160,10 +164,11 @@ class VisaCustomerWalletApproveTask( session: CardSession, callback: CompletionCallback, ) { - val hashToSign = visaDataForApprove.dataToSign.hashToSign.hexToBytes() + val content = "Tangem Pay wants to sign in with your account. Nonce: ${visaDataForApprove.hashToSign}" + val hash = hashPersonalMessage(content.toByteArray(Charsets.UTF_8)) val signTask = SignHashCommand( - hash = hashToSign, + hash = hash, walletPublicKey = targetWalletPublicKey, derivationPath = derivationPath, ) @@ -173,7 +178,7 @@ class VisaCustomerWalletApproveTask( is CompletionResult.Success -> { val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended( signature = result.data.signature, - hash = hashToSign, + hash = hash, publicKey = extendedPublicKey?.publicKey?.toDecompressedPublicKey() ?: targetWalletPublicKey.toDecompressedPublicKey(), ).asRSVLegacyEVM().toHexString().lowercase() @@ -181,10 +186,7 @@ class VisaCustomerWalletApproveTask( scanCard( session = session, callback = callback, - signedData = visaDataForApprove.dataToSign.sign( - signature = rsvSignature, - customerWalletAddress = visaDataForApprove.targetAddress, - ), + signedData = visaDataForApprove.sign(rsvSignature, visaDataForApprove.targetAddress), ) } is CompletionResult.Failure -> { @@ -211,4 +213,11 @@ class VisaCustomerWalletApproveTask( } } } + + data class Input( + val cardId: String? = null, + val targetAddress: String, + val hashToSign: String, + val sign: (signature: String, customerWalletAddress: String) -> VisaSignedDataByCustomerWallet, + ) } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt index 2749826753..8678e1959c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/TangemPayApi.kt @@ -1,19 +1,7 @@ package com.tangem.datasource.api.pay import com.tangem.datasource.api.common.response.ApiResponse -import com.tangem.datasource.api.pay.models.request.ActivationByCardWalletRequest -import com.tangem.datasource.api.pay.models.request.ActivationByCustomerWalletRequest -import com.tangem.datasource.api.pay.models.request.ActivationStatusRequest -import com.tangem.datasource.api.pay.models.request.ExchangeAccessTokenRequest -import com.tangem.datasource.api.pay.models.request.GenerateNoneByCardIdRequest -import com.tangem.datasource.api.pay.models.request.GenerateNoneByCardWalletRequest -import com.tangem.datasource.api.pay.models.request.GetAccessTokenByCardIdRequest -import com.tangem.datasource.api.pay.models.request.GetAccessTokenByCardWalletRequest -import com.tangem.datasource.api.pay.models.request.GetCardWalletAcceptanceRequest -import com.tangem.datasource.api.pay.models.request.GetCustomerWalletAcceptanceRequest -import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardIdRequest -import com.tangem.datasource.api.pay.models.request.RefreshTokenByCardWalletRequest -import com.tangem.datasource.api.pay.models.request.SetPinCodeRequest +import com.tangem.datasource.api.pay.models.request.* import com.tangem.datasource.api.pay.models.response.* import retrofit2.http.Body import retrofit2.http.GET @@ -33,9 +21,17 @@ interface TangemPayApi { @Body request: GenerateNoneByCardWalletRequest, ): ApiResponse + @POST("v1/auth/challenge") + suspend fun generateNonceByCustomerWallet( + @Body request: GenerateNonceByCustomerWalletRequest, + ): ApiResponse + @POST("v1/auth/token") suspend fun getAccessTokenByCardId(@Body request: GetAccessTokenByCardIdRequest): ApiResponse + @POST("v1/auth/token") + suspend fun getTokenByCustomerWallet(@Body request: GetTokenByCustomerWalletRequest): ApiResponse + @POST("v1/auth/token") suspend fun getAccessTokenByCardWallet(@Body request: GetAccessTokenByCardWalletRequest): ApiResponse diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/GenerateNonceByCustomerWalletRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/GenerateNonceByCustomerWalletRequest.kt new file mode 100644 index 0000000000..edd8260545 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/GenerateNonceByCustomerWalletRequest.kt @@ -0,0 +1,10 @@ +package com.tangem.datasource.api.pay.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GenerateNonceByCustomerWalletRequest( + @Json(name = "auth_type") val authType: String = "customer_wallet", + @Json(name = "customer_wallet_address") val customerWalletAddress: String, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/GetTokenByCustomerWalletRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/GetTokenByCustomerWalletRequest.kt new file mode 100644 index 0000000000..9a5e47e327 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/GetTokenByCustomerWalletRequest.kt @@ -0,0 +1,12 @@ +package com.tangem.datasource.api.pay.models.request + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GetTokenByCustomerWalletRequest( + @Json(name = "auth_type") val authType: String = "customer_wallet", + @Json(name = "session_id") val sessionId: String, + @Json(name = "signature") val signature: String, + @Json(name = "message_format") val messageFormat: String, +) \ No newline at end of file diff --git a/data/visa/build.gradle.kts b/data/visa/build.gradle.kts index ff1d5fb9ef..38d1d2957a 100644 --- a/data/visa/build.gradle.kts +++ b/data/visa/build.gradle.kts @@ -50,6 +50,7 @@ dependencies { /** Libs - Tangem */ implementation(tangemDeps.blockchain) implementation(tangemDeps.card.core) + implementation(projects.libs.tangemSdkApi) /** DI */ implementation(deps.hilt.core) diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultKycRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultKycRepository.kt index e42238404f..cc1a69895e 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultKycRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultKycRepository.kt @@ -2,39 +2,52 @@ package com.tangem.data.pay import arrow.core.Either import com.squareup.moshi.Moshi +import com.tangem.common.map import com.tangem.core.error.UniversalError import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.datasource.api.pay.models.response.VisaErrorResponseJsonAdapter import com.tangem.datasource.di.NetworkMoshi -import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.KycStartInfo import com.tangem.domain.pay.repository.KycRepository import com.tangem.domain.visa.error.VisaApiError -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.assisted.Assisted +import com.tangem.domain.visa.model.VisaDataForApprove +import com.tangem.domain.visa.model.VisaDataToSignByCustomerWallet +import com.tangem.domain.visa.repository.VisaAuthRepository +import com.tangem.sdk.api.TangemSdkManager import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import kotlinx.coroutines.withContext -@Suppress("UnusedPrivateMember") class DefaultKycRepository @AssistedInject constructor( - @Assisted userWalletId: UserWalletId, @NetworkMoshi moshi: Moshi, private val tangemPayApi: TangemPayApi, - private val dispatcherProvider: CoroutineDispatcherProvider, + private val visaAuthRepository: VisaAuthRepository, + private val tangemSdkManager: TangemSdkManager, ) : KycRepository { private val visaErrorAdapter = VisaErrorResponseJsonAdapter(moshi) - override suspend fun getKycStartInfo(): Either = withContext(dispatcherProvider.io) { - val authTokenForSpecificWallet = "get from userWalletId" - - request { - tangemPayApi.getKycAccess( - authHeader = authTokenForSpecificWallet, - ).getOrThrow().result + override suspend fun getKycStartInfo(address: String, cardId: String): Either { + var authHeader = "" + visaAuthRepository.getCustomerWalletAuthChallenge(address).getOrNull()?.let { result -> + tangemSdkManager.visaCustomerWalletApprove( + VisaDataForApprove( + customerWalletCardId = cardId, + targetAddress = address, + dataToSign = VisaDataToSignByCustomerWallet(hashToSign = result.challenge), + ), + ).map { signResult -> + visaAuthRepository.getTokenWithCustomerWallet( + sessionId = result.session.sessionId, + signature = signResult.signature, + nonce = signResult.dataToSign.hashToSign, + ).getOrNull()?.let { authHeader = it } + } + } + return request { + authHeader.ifEmpty { error("Cannot get auth header for KYC") } + tangemPayApi.getKycAccess(authHeader = authHeader).getOrThrow().result }.map { KycStartInfo( token = it.token, @@ -65,6 +78,6 @@ class DefaultKycRepository @AssistedInject constructor( @AssistedFactory interface Factory : KycRepository.Factory { - override fun create(userWalletId: UserWalletId): DefaultKycRepository + override fun create(): DefaultKycRepository } } \ No newline at end of file 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 30457ea047..b38b9a82a8 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 @@ -131,16 +131,18 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( val authTokens = checkNotNull(visaAuthTokenStorage.get(visaCardId.cardId)) { "Visa auth tokens are not stored" } - visaApi.activateByCustomerWallet( - authHeader = authTokens.getAuthHeader(), - body = ActivationByCustomerWalletRequest( - orderId = signedData.dataToSign.request.orderId, - customerWallet = ActivationByCustomerWalletRequest.CustomerWallet( - deployAcceptanceSignature = signedData.signature, - customerWalletAddress = signedData.customerWalletAddress, + signedData.dataToSign.request?.orderId?.let { orderId -> + visaApi.activateByCustomerWallet( + authHeader = authTokens.getAuthHeader(), + body = ActivationByCustomerWalletRequest( + orderId = orderId, + customerWallet = ActivationByCustomerWalletRequest.CustomerWallet( + deployAcceptanceSignature = signedData.signature, + customerWalletAddress = signedData.customerWalletAddress, + ), ), - ), - ).getOrThrow() + ).getOrThrow() + } ?: error("Order Id cannot be null") } } 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 c9c07624e5..8b3024f1fe 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 @@ -65,6 +65,39 @@ internal class DefaultVisaAuthRepository @Inject constructor( } } + override suspend fun getCustomerWalletAuthChallenge( + customerWalletAddress: String, + ): Either = withContext(dispatchers.io) { + request { + visaAuthApi.generateNonceByCustomerWallet( + GenerateNonceByCustomerWalletRequest(customerWalletAddress = customerWalletAddress), + ).getOrThrow() + }.map { response -> + VisaAuthChallenge.Wallet( + challenge = response.result.nonce, + session = VisaAuthSession(response.result.sessionId), + ) + } + } + + override suspend fun getTokenWithCustomerWallet( + sessionId: String, + signature: String, + nonce: String, + ): Either = withContext(dispatchers.io) { + request { + visaAuthApi.getTokenByCustomerWallet( + GetTokenByCustomerWalletRequest( + sessionId = sessionId, + signature = signature, + messageFormat = "Tangem Pay wants to sign in with your account. Nonce: $nonce", + ), + ).getOrThrow() + }.map { response -> + "Bearer ${response.result.accessToken}" + } + } + override suspend fun getAccessTokens( signedChallenge: VisaAuthSignedChallenge, ): Either = withContext(dispatchers.io) { diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataToSignByCustomerWallet.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataToSignByCustomerWallet.kt index 17de705186..d6c97a1919 100644 --- a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataToSignByCustomerWallet.kt +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaDataToSignByCustomerWallet.kt @@ -4,8 +4,8 @@ import kotlinx.serialization.Serializable @Serializable data class VisaDataToSignByCustomerWallet( - val request: VisaCustomerWalletDataToSignRequest, val hashToSign: String, + val request: VisaCustomerWalletDataToSignRequest? = null, ) fun VisaDataToSignByCustomerWallet.sign(signature: String, customerWalletAddress: String) = diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaSignedChallengeByCustomerWallet.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaSignedChallengeByCustomerWallet.kt new file mode 100644 index 0000000000..7c46288fc1 --- /dev/null +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/visa/model/VisaSignedChallengeByCustomerWallet.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.visa.model + +data class VisaSignedChallengeByCustomerWallet( + val challenge: String, + val signature: String, +) \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/KycRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/KycRepository.kt index e072914e92..7d46ff2d52 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/KycRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/KycRepository.kt @@ -3,13 +3,12 @@ package com.tangem.domain.pay.repository import arrow.core.Either import com.tangem.core.error.UniversalError import com.tangem.domain.pay.KycStartInfo -import com.tangem.domain.models.wallet.UserWalletId interface KycRepository { - suspend fun getKycStartInfo(): Either + suspend fun getKycStartInfo(address: String, cardId: String): Either interface Factory { - fun create(userWalletId: UserWalletId): KycRepository + fun create(): KycRepository } } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaAuthRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaAuthRepository.kt index f7f19ca0e9..098ca44c00 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaAuthRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaAuthRepository.kt @@ -18,6 +18,16 @@ interface VisaAuthRepository { cardWalletAddress: String, ): Either + suspend fun getCustomerWalletAuthChallenge( + customerWalletAddress: String, + ): Either + + suspend fun getTokenWithCustomerWallet( + sessionId: String, + signature: String, + nonce: String, + ): Either + suspend fun getAccessTokens(signedChallenge: VisaAuthSignedChallenge): Either suspend fun refreshAccessTokens(refreshToken: VisaAuthTokens.RefreshToken): Either diff --git a/features/kyc/api/src/main/kotlin/com/tangem/features/kyc/KycComponent.kt b/features/kyc/api/src/main/kotlin/com/tangem/features/kyc/KycComponent.kt index 6a18888000..de05f5dc62 100644 --- a/features/kyc/api/src/main/kotlin/com/tangem/features/kyc/KycComponent.kt +++ b/features/kyc/api/src/main/kotlin/com/tangem/features/kyc/KycComponent.kt @@ -4,9 +4,14 @@ import com.tangem.core.decompose.context.AppComponentContext interface KycComponent { - fun launch() + fun launch(params: Params) interface Factory { fun create(appComponentContext: AppComponentContext): KycComponent } + + data class Params( + val targetAddress: String, + val cardId: String, + ) } \ No newline at end of file diff --git a/features/kyc/impl/build.gradle.kts b/features/kyc/impl/build.gradle.kts index 3e07cc6bd5..1bf8fc66f2 100644 --- a/features/kyc/impl/build.gradle.kts +++ b/features/kyc/impl/build.gradle.kts @@ -13,8 +13,7 @@ android { dependencies { /** Api */ - //TODO disable for release because of the permissions - // implementation(projects.features.kyc.api) + implementation(projects.features.kyc.api) /** Domain */ implementation(projects.domain.visa) diff --git a/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycComponent.kt b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycComponent.kt index afabc4e184..92d39cee70 100644 --- a/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycComponent.kt +++ b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycComponent.kt @@ -1,57 +1,41 @@ package com.tangem.features.kyc import com.sumsub.sns.core.SNSMobileSDK -import com.sumsub.sns.core.data.listener.SNSCompleteHandler import com.sumsub.sns.core.data.listener.TokenExpirationHandler -import com.sumsub.sns.core.data.model.SNSCompletionResult -import com.sumsub.sns.core.data.model.SNSInitConfig -import com.sumsub.sns.core.data.model.SNSSDKState import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.domain.pay.repository.KycRepository -import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.features.kyc.theme.TangemSNSTheme +import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.features.kyc.theme.TangemSNSIconHandler +import com.tangem.features.kyc.theme.TangemSNSTheme import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import java.util.Locale class DefaultKycComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, - private val kycRepositoryFactory: KycRepository.Factory, ) : KycComponent, AppComponentContext by appComponentContext { - private val kycRepository = kycRepositoryFactory.create(UserWalletId("0FFFFF")) + private val model: DefaultKycModel = getOrCreateModel() - override fun launch() { + override fun launch(params: KycComponent.Params) { componentScope.launch { - val startInfo = kycRepository.getKycStartInfo().getOrNull() ?: return@launch - - val tokenExpirationHandler = object : TokenExpirationHandler { - override fun onTokenExpired(): String? { - val newToken = runBlocking { kycRepository.getKycStartInfo().getOrNull()?.token } - return newToken + model.uiState.collect { + it?.let { startInfo -> + val tokenExpirationHandler = object : TokenExpirationHandler { + override fun onTokenExpired() = "" + } + val snsSdk = SNSMobileSDK.Builder(activity) + .withAccessToken(accessToken = startInfo.token, onTokenExpiration = tokenExpirationHandler) + .withTheme(TangemSNSTheme.theme(activity)) + .withIconHandler(TangemSNSIconHandler()) + .withLocale(Locale("en")) + .build() + snsSdk.launch() } } - - val snsSdk = SNSMobileSDK.Builder(activity) - .withAccessToken(accessToken = startInfo.token, onTokenExpiration = tokenExpirationHandler) - .withConf(SNSInitConfig(strings = mapOf())) - .withTheme(TangemSNSTheme.theme(activity)) - .withIconHandler(TangemSNSIconHandler()) - .withLocale(Locale("en")) - .withCompleteHandler( - object : SNSCompleteHandler { - override fun onComplete(result: SNSCompletionResult, state: SNSSDKState) { - } - }, - ) - .build() - - snsSdk.launch() } + model.getKycToken(params) } @AssistedFactory diff --git a/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycModel.kt b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycModel.kt new file mode 100644 index 0000000000..aff81ad334 --- /dev/null +++ b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/DefaultKycModel.kt @@ -0,0 +1,32 @@ +package com.tangem.features.kyc + +import androidx.compose.runtime.Stable +import com.tangem.core.decompose.di.ModelScoped +import com.tangem.core.decompose.model.Model +import com.tangem.domain.pay.KycStartInfo +import com.tangem.domain.pay.repository.KycRepository +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@Stable +@ModelScoped +class DefaultKycModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + kycRepositoryFactory: KycRepository.Factory, +) : Model() { + + private val kycRepository = kycRepositoryFactory.create() + + private val _uiState: MutableStateFlow = MutableStateFlow(null) + val uiState = _uiState.asStateFlow() + + fun getKycToken(params: KycComponent.Params) { + modelScope.launch { + kycRepository.getKycStartInfo(address = params.targetAddress, cardId = params.cardId).getOrNull() + ?.let { _uiState.emit(it) } + } + } +} \ No newline at end of file diff --git a/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/di/FeatureModule.kt b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/di/FeatureModule.kt index c72f282775..a2fefafc7b 100644 --- a/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/di/FeatureModule.kt +++ b/features/kyc/impl/src/main/kotlin/com/tangem/features/kyc/di/FeatureModule.kt @@ -1,11 +1,16 @@ package com.tangem.features.kyc.di +import com.tangem.core.decompose.di.ModelComponent +import com.tangem.core.decompose.model.Model import com.tangem.features.kyc.DefaultKycComponent +import com.tangem.features.kyc.DefaultKycModel import com.tangem.features.kyc.KycComponent import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap @Module @InstallIn(SingletonComponent::class) @@ -13,4 +18,13 @@ internal interface FeatureModule { @Binds fun bindComponentFactory(impl: DefaultKycComponent.Factory): KycComponent.Factory +} + +@Module +@InstallIn(ModelComponent::class) +internal interface ModelModule { + @Binds + @IntoMap + @ClassKey(DefaultKycModel::class) + fun provideModel(model: DefaultKycModel): Model } \ No newline at end of file diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index e6d1f16109..5d506674c2 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -113,6 +113,7 @@ dependencies { implementation(projects.features.biometry.api) implementation(projects.features.nft.api) implementation(projects.features.sendV2.api) + implementation(projects.features.kyc.api) /** Common modules */ implementation(projects.common) diff --git a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt index f15198c725..12aeb18278 100644 --- a/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt +++ b/libs/tangem-sdk-api/src/main/kotlin/com/tangem/sdk/api/TangemSdkManager.kt @@ -15,10 +15,8 @@ import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.ScanResponse -import com.tangem.domain.visa.model.VisaActivationInput -import com.tangem.domain.visa.model.VisaDataForApprove -import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.visa.model.* import com.tangem.operations.derivation.DerivationTaskResponse import com.tangem.operations.preflightread.PreflightReadFilter import com.tangem.operations.wallet.CreateWalletResponse diff --git a/settings.gradle.kts b/settings.gradle.kts index 2e4a4b6f20..b77ede786a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -260,8 +260,8 @@ include(":features:walletconnect:impl") include(":features:hot-wallet:api") include(":features:hot-wallet:impl") +include(":features:kyc:api") //TODO disable for release because of the permissions -// include(":features:kyc:api") // include(":features:kyc:impl") include(":features:create-wallet-selection:api")