Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-20 11:43:43 +04:00
parent eb07388729
commit 3f6bcd3a0e
23 changed files with 226 additions and 101 deletions

View file

@ -50,6 +50,7 @@ dependencies {
/** Libs - Tangem */
implementation(tangemDeps.blockchain)
implementation(tangemDeps.card.core)
implementation(projects.libs.tangemSdkApi)
/** DI */
implementation(deps.hilt.core)

View file

@ -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<UniversalError, KycStartInfo> = withContext(dispatcherProvider.io) {
val authTokenForSpecificWallet = "get from userWalletId"
request {
tangemPayApi.getKycAccess(
authHeader = authTokenForSpecificWallet,
).getOrThrow().result
override suspend fun getKycStartInfo(address: String, cardId: String): Either<UniversalError, KycStartInfo> {
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
}
}

View file

@ -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")
}
}

View file

@ -65,6 +65,39 @@ internal class DefaultVisaAuthRepository @Inject constructor(
}
}
override suspend fun getCustomerWalletAuthChallenge(
customerWalletAddress: String,
): Either<VisaApiError, VisaAuthChallenge.Wallet> = 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<VisaApiError, String> = 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<VisaApiError, VisaAuthTokens> = withContext(dispatchers.io) {