Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-11 13:35:31 +03:00
parent 19fb8f2bcd
commit ecdb72e55c
58 changed files with 1082 additions and 346 deletions

View file

@ -1,8 +1,11 @@
package com.tangem.tap.data
import android.content.Context
import com.tangem.common.extensions.toByteArray
import com.tangem.common.extensions.toInt
import com.tangem.common.services.secure.SecureStorage
import com.tangem.datasource.local.visa.VisaOTPStorage
import com.tangem.datasource.local.visa.VisaOtpData
import com.tangem.sdk.storage.AndroidSecureStorage
import com.tangem.sdk.storage.createEncryptedSharedPreferences
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -24,19 +27,27 @@ class DefaultVisaOTPStorage @Inject constructor(
),
)
override suspend fun saveOTP(cardId: String, otp: ByteArray) = withContext(dispatcherProvider.io) {
secureStorage.store(otp, VISA_OTP_KEY_PREFIX + cardId)
override suspend fun saveOTP(cardId: String, data: VisaOtpData) = withContext(dispatcherProvider.io) {
secureStorage.store(data.rootOTP, VISA_ROOT_OTP_KEY_PREFIX + cardId)
secureStorage.store(data.counter.toByteArray(), VISA_OTP_COUNTER_KEY_PREFIX + cardId)
}
override suspend fun getOTP(cardId: String): ByteArray? = withContext(dispatcherProvider.io) {
secureStorage.get(VISA_OTP_KEY_PREFIX + cardId)
override suspend fun getOTP(cardId: String): VisaOtpData? = withContext(dispatcherProvider.io) {
val rootOTP = secureStorage.get(VISA_ROOT_OTP_KEY_PREFIX + cardId) ?: return@withContext null
val counter = secureStorage.get(VISA_OTP_COUNTER_KEY_PREFIX + cardId)?.toInt() ?: return@withContext null
VisaOtpData(
rootOTP = rootOTP,
counter = counter,
)
}
override suspend fun removeOTP(cardId: String) = withContext(dispatcherProvider.io) {
secureStorage.delete(VISA_OTP_KEY_PREFIX + cardId)
secureStorage.delete(VISA_ROOT_OTP_KEY_PREFIX + cardId)
secureStorage.delete(VISA_OTP_COUNTER_KEY_PREFIX + cardId)
}
private companion object {
const val VISA_OTP_KEY_PREFIX = "visa_otp_"
const val VISA_ROOT_OTP_KEY_PREFIX = "visa_root_otp_"
const val VISA_OTP_COUNTER_KEY_PREFIX = "visa_otp_counter_"
}
}

View file

@ -25,8 +25,8 @@ import com.tangem.domain.common.util.derivationStyleProvider
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.VisaAuthChallenge
import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
import com.tangem.operations.ScanTask
@ -35,12 +35,12 @@ import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
import com.tangem.operations.pins.SetUserCodeCommand
import com.tangem.operations.preflightread.PreflightReadFilter
import com.tangem.operations.sign.SignHashResponse
import com.tangem.operations.usersetttings.SetUserCodeRecoveryAllowedTask
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.sdk.api.visa.VisaCardActivationResponse
import com.tangem.sdk.api.visa.VisaCardActivationTaskMode
import com.tangem.tap.derivationsFinder
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
@ -480,15 +480,13 @@ internal class DefaultTangemSdkManager(
// region Visa-specific
override suspend fun activateVisaCard(
accessCode: String,
challengeToSign: VisaAuthChallenge.Card?,
mode: VisaCardActivationTaskMode,
activationInput: VisaActivationInput,
): CompletionResult<VisaCardActivationResponse> {
return coroutineScope {
runTaskAsyncReturnOnMain(
runnable = visaCardActivationTaskFactory.create(
accessCode = accessCode,
challengeToSign = challengeToSign,
mode = mode,
activationInput = activationInput,
coroutineScope = this,
),
@ -499,7 +497,7 @@ internal class DefaultTangemSdkManager(
override suspend fun visaCustomerWalletApprove(
visaDataForApprove: VisaDataForApprove,
): CompletionResult<SignHashResponse> {
): CompletionResult<VisaSignedDataByCustomerWallet> {
return runTaskAsyncReturnOnMain(
runnable = VisaCustomerWalletApproveTask(
visaDataForApprove = visaDataForApprove,

View file

@ -18,16 +18,16 @@ 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.VisaAuthChallenge
import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.operations.derivation.DerivationTaskResponse
import com.tangem.operations.preflightread.PreflightReadFilter
import com.tangem.operations.sign.SignHashResponse
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.sdk.api.CreateProductWalletTaskResponse
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.sdk.api.visa.VisaCardActivationResponse
import com.tangem.sdk.api.visa.VisaCardActivationTaskMode
import com.tangem.tap.domain.sdk.mocks.MockProvider
@Suppress("TooManyFunctions")
@ -203,8 +203,7 @@ class MockTangemSdkManager(
// region Visa-specific
override suspend fun activateVisaCard(
accessCode: String,
challengeToSign: VisaAuthChallenge.Card?,
mode: VisaCardActivationTaskMode,
activationInput: VisaActivationInput,
): CompletionResult<VisaCardActivationResponse> {
error("Not implemented")
@ -212,7 +211,7 @@ class MockTangemSdkManager(
override suspend fun visaCustomerWalletApprove(
visaDataForApprove: VisaDataForApprove,
): CompletionResult<SignHashResponse> {
): CompletionResult<VisaSignedDataByCustomerWallet> {
error("Not implemented")
}

View file

@ -1,5 +1,9 @@
package com.tangem.tap.domain.tasks.visa
import arrow.core.Either
import arrow.core.getOrElse
import arrow.core.raise.catch
import arrow.core.raise.either
import com.tangem.common.CompletionResult
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
@ -12,8 +16,10 @@ import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource
import com.tangem.crypto.CryptoUtils
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
import com.tangem.datasource.local.visa.VisaOTPStorage
import com.tangem.datasource.local.visa.VisaOtpData
import com.tangem.datasource.local.visa.hasSavedOTP
import com.tangem.domain.common.visa.VisaUtilities
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.visa.model.*
import com.tangem.domain.visa.repository.VisaActivationRepository
@ -25,19 +31,18 @@ import com.tangem.operations.sign.SignHashCommand
import com.tangem.operations.sign.SignHashResponse
import com.tangem.operations.wallet.CreateWalletTask
import com.tangem.sdk.api.visa.VisaCardActivationResponse
import com.tangem.sdk.api.visa.VisaCardActivationTaskMode
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.*
import timber.log.Timber
import kotlin.coroutines.resume
import kotlin.jvm.Throws
import kotlin.time.measureTimedValue
@Suppress("LongParameterList")
class VisaCardActivationTask @AssistedInject constructor(
@Assisted private val accessCode: String,
@Assisted private val challengeToSign: VisaAuthChallenge.Card?,
@Assisted private val mode: VisaCardActivationTaskMode,
@Assisted private val activationInput: VisaActivationInput,
@Assisted private val coroutineScope: CoroutineScope,
private val otpStorage: VisaOTPStorage,
@ -65,7 +70,12 @@ class VisaCardActivationTask @AssistedInject constructor(
return CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.WrongCard.message))
}
val visaActivationRepository = visaActivationRepositoryFactory.create(card.cardId)
val visaActivationRepository = visaActivationRepositoryFactory.create(
VisaCardId(
cardId = card.cardId,
cardPublicKey = card.cardPublicKey.toHexString(),
),
)
val context = SessionContext(
visaActivationRepository = visaActivationRepository,
@ -74,15 +84,13 @@ class VisaCardActivationTask @AssistedInject constructor(
)
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
if (challengeToSign != null) {
context.signAuthorizationChallenge(challengeToSign)
} else {
val activationOrder = runCatching { visaActivationRepository.getActivationOrderToSign() }
.getOrElse {
return CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: ""))
}
context.signOrder(activationOrder)
when (mode) {
is VisaCardActivationTaskMode.Full -> {
context.signAuthorizationChallenge(mode.authorizationChallenge)
}
is VisaCardActivationTaskMode.SignOnly -> {
context.signData(mode.dataToSignByCardWallet)
}
}
}
Timber.i("VisaCardActivationTask all time: ${timedResult.duration}")
@ -123,45 +131,50 @@ class VisaCardActivationTask @AssistedInject constructor(
signedChallenge: VisaAuthSignedChallenge,
): CompletionResult<VisaCardActivationResponse> {
return coroutineScope {
val activationOrderDeferred = async { getActivationOrderToSign(signedChallenge) }
val otpTaskDeferred = async { createWallet(session) }
val dataToSignDeferred = async { getDataToSign(signedChallenge) }
val otpTaskDeferred = async { createWallet() }
val order = runCatching { activationOrderDeferred.await() }
val dataToSign = dataToSignDeferred.await()
.getOrElse {
otpTaskDeferred.cancel()
return@coroutineScope CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: ""))
return@coroutineScope CompletionResult.Failure(it)
}
otpTaskDeferred.await()
signOrder(order)
signData(dataToSign)
}
}
@Throws(TangemSdkError::class)
private suspend fun SessionContext.getActivationOrderToSign(
private suspend fun SessionContext.getDataToSign(
signedChallenge: VisaAuthSignedChallenge,
): ActivationOrder {
val tokens = runCatching {
visaAuthRepository.getAccessTokens(signedChallenge)
}.getOrElse {
throw TangemSdkError.Underlying(
"Underlying network error: ${it.message ?: ""}",
)
}
): Either<TangemSdkError.Underlying, VisaDataToSignByCardWallet> = either {
catch(
block = {
val tokens = visaAuthRepository.getAccessTokens(signedChallenge)
visaAuthTokenStorage.store(cardId, tokens)
visaAuthTokenStorage.store(cardId, tokens)
return visaActivationRepository.getActivationOrderToSign()
val remoteState = visaActivationRepository.getActivationRemoteState()
if (remoteState !is VisaActivationRemoteState.CardWalletSignatureRequired) {
raise(TangemSdkError.Underlying(VisaActivationError.WrongRemoteState.message))
}
visaActivationRepository.getCardWalletAcceptanceData(remoteState.request)
},
catch = {
raise(TangemSdkError.Underlying("Underlying network error: ${it.message ?: ""}"))
},
)
}
private suspend fun SessionContext.createWallet(session: CardSession): CompletionResult<Unit> {
private suspend fun SessionContext.createWallet(): CompletionResult<Unit> {
coroutineScope { ensureActive() }
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
return if (card.wallets.any { it.curve == VisaUtilities.mandatoryCurve }) {
createOTP(session)
createOTP()
} else {
val createWalletTask = CreateWalletTask(VisaUtilities.mandatoryCurve)
@ -178,7 +191,7 @@ class VisaCardActivationTask @AssistedInject constructor(
when (val result = timedResult.value) {
is CompletionResult.Success -> {
Timber.i("CreateWalletTask success")
createOTP(session)
createOTP()
}
is CompletionResult.Failure -> {
Timber.e("CreateWalletTask failure ${result.error}")
@ -188,46 +201,58 @@ class VisaCardActivationTask @AssistedInject constructor(
}
}
private suspend fun SessionContext.createOTP(session: CardSession): CompletionResult<Unit> {
private suspend fun SessionContext.createOTP(): CompletionResult<Unit> {
coroutineScope { ensureActive() }
return if (otpStorage.hasSavedOTP(cardId)) {
CompletionResult.Success(Unit)
} else {
val otpCommand = GenerateOTPCommand()
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
suspendCancellableCoroutine { continuation ->
otpCommand.run(session) { otpResult ->
continuation.resume(otpResult)
}
if (otpStorage.hasSavedOTP(cardId)) {
return CompletionResult.Success(Unit)
}
val otpCommand = GenerateOTPCommand()
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
suspendCancellableCoroutine { continuation ->
otpCommand.run(session) { otpResult ->
continuation.resume(otpResult)
}
}
}
Timber.i("GenerateOTPCommand time: ${timedResult.duration}")
Timber.i("GenerateOTPCommand time: ${timedResult.duration}")
when (val result = timedResult.value) {
is CompletionResult.Success -> {
Timber.i("GenerateOTPCommand success")
otpStorage.saveOTP(cardId, result.data.rootOTP)
CompletionResult.Success(Unit)
}
is CompletionResult.Failure -> {
Timber.e("GenerateOTPCommand failure ${result.error}")
CompletionResult.Failure(result.error)
}
return when (val result = timedResult.value) {
is CompletionResult.Success -> {
Timber.i("GenerateOTPCommand success")
otpStorage.saveOTP(
cardId = cardId,
data = VisaOtpData(result.data.rootOTP, result.data.rootOTPCounter),
)
CompletionResult.Success(Unit)
}
is CompletionResult.Failure -> {
Timber.e("GenerateOTPCommand failure ${result.error}")
CompletionResult.Failure(result.error)
}
}
}
private suspend fun SessionContext.signOrder(order: ActivationOrder): CompletionResult<VisaCardActivationResponse> {
private suspend fun SessionContext.signData(
dataToSign: VisaDataToSignByCardWallet,
): CompletionResult<VisaCardActivationResponse> {
val card =
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
val wallet =
card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve }
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
val derivedPublicKey = wallet.derivedKeys[VisaUtilities.visaDefaultDerivationPath]
?: return CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.MissingWallet.message))
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnVisaCurve(derivedPublicKey.publicKey)
.getOrElse { return CompletionResult.Failure(TangemSdkError.Underlying(it.message)) }
.value
val task = SignHashCommand(
hash = order.hash.hexToBytes(),
hash = dataToSign.hashToSign.hexToBytes(),
walletPublicKey = wallet.publicKey,
derivationPath = VisaUtilities.visaDefaultDerivationPath,
)
@ -245,8 +270,9 @@ class VisaCardActivationTask @AssistedInject constructor(
return when (val result = timedResult.value) {
is CompletionResult.Success -> {
Timber.i("SignHashCommand success")
handleSignedOrder(
activationOrder = order,
handleSignedData(
dataToSign = dataToSign,
walletAddress = walletAddress,
response = result.data,
)
}
@ -257,26 +283,29 @@ class VisaCardActivationTask @AssistedInject constructor(
}
}
private suspend fun SessionContext.handleSignedOrder(
activationOrder: ActivationOrder,
private suspend fun SessionContext.handleSignedData(
dataToSign: VisaDataToSignByCardWallet,
walletAddress: String,
response: SignHashResponse,
): CompletionResult<VisaCardActivationResponse> {
val signedOrder = SignedActivationOrder(
activationOrder = activationOrder,
val otp = otpStorage.getOTP(cardId) ?: run {
createOTP()
otpStorage.getOTP(cardId) ?: return CompletionResult.Failure(TangemSdkError.Underlying("OTP not found"))
}
val signedActivationData = dataToSign.sign(
cardWalletAddress = walletAddress,
rootOTP = otp.rootOTP.toHexString(),
otpCounter = otp.counter,
signature = response.signature.toHexString(),
)
val otp = otpStorage.getOTP(cardId) ?: return CompletionResult.Failure(
TangemSdkError.Underlying(VisaActivationError.MissingRootOTP.message),
)
return setupAccessCode().map {
val card =
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
val card = session.environment.card
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
VisaCardActivationResponse(
signedActivationOrder = signedOrder,
rootOTP = VisaRootOTP(otp.toHexString()),
signedActivationData = signedActivationData,
newCardDTO = CardDTO(card),
)
}
@ -285,13 +314,13 @@ class VisaCardActivationTask @AssistedInject constructor(
private suspend fun SessionContext.setupAccessCode(): CompletionResult<Unit> {
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
if (card.isAccessCodeSet) {
if (card.isAccessCodeSet || mode !is VisaCardActivationTaskMode.Full) {
return CompletionResult.Success(Unit)
}
Timber.i("Setting access code")
val task = SetUserCodeCommand.changeAccessCode(accessCode)
val task = SetUserCodeCommand.changeAccessCode(mode.accessCode)
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
suspendCancellableCoroutine { continuation ->
@ -318,8 +347,7 @@ class VisaCardActivationTask @AssistedInject constructor(
@AssistedFactory
interface Factory {
fun create(
accessCode: String,
challengeToSign: VisaAuthChallenge.Card?,
mode: VisaCardActivationTaskMode,
activationInput: VisaActivationInput,
coroutineScope: CoroutineScope,
): VisaCardActivationTask

View file

@ -9,6 +9,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.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.common.util.derivationStyleProvider
@ -18,16 +19,17 @@ import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDe
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.visa.model.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
import com.tangem.operations.sign.SignHashResponse
class VisaCustomerWalletApproveTask(
private val visaDataForApprove: VisaDataForApprove,
) : CardSessionRunnable<SignHashResponse> {
) : CardSessionRunnable<VisaSignedDataByCustomerWallet> {
override fun run(session: CardSession, callback: CompletionCallback<SignHashResponse>) {
override fun run(session: CardSession, callback: CompletionCallback<VisaSignedDataByCustomerWallet>) {
val card = session.environment.card ?: run {
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
return
@ -54,7 +56,11 @@ class VisaCustomerWalletApproveTask(
}
}
private fun proceedApprove(card: Card, session: CardSession, callback: CompletionCallback<SignHashResponse>) {
private fun proceedApprove(
card: Card,
session: CardSession,
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
) {
val cardDTO = CardDTO(card)
val derivationStyle = cardDTO.derivationStyleProvider.getDerivationStyle() ?: run {
@ -108,7 +114,7 @@ class VisaCustomerWalletApproveTask(
extendedPublicKey: ExtendedPublicKey,
derivationPath: DerivationPath,
session: CardSession,
callback: CompletionCallback<SignHashResponse>,
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
) {
val validationResult = VisaWalletPublicKeyUtility.validateExtendedPublicKey(
targetAddress = visaDataForApprove.targetAddress,
@ -131,7 +137,7 @@ class VisaCustomerWalletApproveTask(
private fun proceedApproveWithLegacyCard(
card: Card,
session: CardSession,
callback: CompletionCallback<SignHashResponse>,
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
) {
val publicKey = findKeyWithoutDerivation(
targetAddress = visaDataForApprove.targetAddress,
@ -153,10 +159,10 @@ class VisaCustomerWalletApproveTask(
targetWalletPublicKey: ByteArray,
derivationPath: DerivationPath?,
session: CardSession,
callback: CompletionCallback<SignHashResponse>,
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
) {
val signTask = SignHashCommand(
hash = visaDataForApprove.approveHash.hexToBytes(),
hash = visaDataForApprove.dataToSign.hashToSign.hexToBytes(),
walletPublicKey = targetWalletPublicKey,
derivationPath = derivationPath,
)
@ -165,9 +171,12 @@ class VisaCustomerWalletApproveTask(
when (result) {
is CompletionResult.Success -> {
scanCard(
signHashResponse = result.data,
session = session,
callback = callback,
signedData = visaDataForApprove.dataToSign.sign(
signature = result.data.signature.toHexString(),
customerWalletAddress = visaDataForApprove.targetAddress,
),
)
}
is CompletionResult.Failure -> {
@ -178,15 +187,15 @@ class VisaCustomerWalletApproveTask(
}
private fun scanCard(
signHashResponse: SignHashResponse,
signedData: VisaSignedDataByCustomerWallet,
session: CardSession,
callback: CompletionCallback<SignHashResponse>,
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
) {
val scanTask = ScanTask()
scanTask.run(session) { result ->
when (result) {
is CompletionResult.Success -> {
callback(CompletionResult.Success(signHashResponse))
callback(CompletionResult.Success(signedData))
}
is CompletionResult.Failure -> {
callback(CompletionResult.Failure(result.error))

View file

@ -8,6 +8,7 @@ import com.tangem.common.json.TangemSdkAdapter
import com.tangem.common.services.secure.SecureStorage
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.models.scan.serialization.*
import com.tangem.domain.visa.model.VisaActivationRemoteState
import com.tangem.domain.visa.model.VisaCardActivationStatus
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.sdk.storage.AndroidSecureStorage
@ -61,8 +62,9 @@ internal object UserWalletsListManagerModule {
.add(TangemSdkAdapter.DateAdapter())
.add(TangemSdkAdapter.DerivationNodeAdapter())
.add(TangemSdkAdapter.FirmwareVersionAdapter()) // For PrimaryCard model
.add(VisaActivationRemoteState.jsonAdapter)
.add(VisaCardActivationStatus.jsonAdapter)
.add(KotlinJsonAdapterFactory())
.addLast(KotlinJsonAdapterFactory())
.build()
val secureStorage = AndroidSecureStorage(

View file

@ -1,5 +1,6 @@
package com.tangem.tap.domain.visa
import arrow.core.getOrElse
import com.tangem.common.CompletionResult
import com.tangem.common.card.CardWallet
import com.tangem.common.core.CardSession
@ -10,6 +11,7 @@ import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
import com.tangem.domain.common.visa.VisaUtilities
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
import com.tangem.domain.visa.model.*
import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.domain.visa.repository.VisaAuthRepository
@ -43,7 +45,12 @@ internal class VisaCardScanHandler @Inject constructor(
return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
}
val visaActivationRepository = visaActivationRepositoryFactory.create(card.cardId)
val visaActivationRepository = visaActivationRepositoryFactory.create(
VisaCardId(
cardId = card.cardId,
cardPublicKey = card.cardPublicKey.toHexString(),
),
)
val context = SessionContext(
visaActivationRepository = visaActivationRepository,
@ -53,7 +60,7 @@ internal class VisaCardScanHandler @Inject constructor(
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run {
val activationInput =
VisaActivationInput(card.cardId, card.cardPublicKey, card.isAccessCodeSet)
VisaActivationInput(card.cardId, card.cardPublicKey.toHexString(), card.isAccessCodeSet)
val activationStatus = VisaCardActivationStatus.NotStartedActivation(activationInput)
return CompletionResult.Success(activationStatus)
}
@ -119,14 +126,21 @@ internal class VisaCardScanHandler @Inject constructor(
)
}
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnVisaCurve(extendedPublicKey.publicKey)
.getOrElse {
return CompletionResult.Failure(
TangemSdkError.Underlying("Cannot generate address on Visa curve"),
)
}
Timber.i("Requesting challenge for wallet authorization")
val challengeResponse = runCatching {
visaAuthRepository.getCustomerWalletAuthChallenge(
cardId = card.cardId,
walletPublicKey = extendedPublicKey.publicKey.toHexString(),
)
visaAuthRepository.getCardWalletAuthChallenge(cardWalletAddress = walletAddress.value)
}.getOrElse {
return CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: "Unknown error"))
Timber.i(
"Failed to get Access token for Wallet public key authoziation. Authorizing using Card Pub key",
)
return handleCardAuthorization()
}
val signChallengeResult = signChallengeWithWallet(
@ -162,11 +176,6 @@ internal class VisaCardScanHandler @Inject constructor(
return handleCardAuthorization()
}
visaAuthTokenStorage.store(
cardId = cardId,
tokens = authorizationTokensResponse,
)
Timber.i("Authorized using Wallet public key successfully")
return CompletionResult.Success(VisaCardActivationStatus.Activated(authorizationTokensResponse))
@ -240,7 +249,7 @@ internal class VisaCardScanHandler @Inject constructor(
val activationInput = VisaActivationInput(
cardId = card.cardId,
cardPublicKey = card.cardPublicKey,
cardPublicKey = card.cardPublicKey.toHexString(),
isAccessCodeSet = card.isAccessCodeSet,
)

View file

@ -2,6 +2,7 @@ package com.tangem.tap.network.auth
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
import com.tangem.domain.visa.model.VisaCardActivationStatus
import com.tangem.domain.visa.model.getAuthHeader
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import javax.inject.Inject
@ -13,8 +14,6 @@ internal class DefaultVisaAuthProvider @Inject constructor(
val card = userWalletsListManager.userWalletsSync.firstOrNull { it.cardId == cardId }
val status = card?.scanResponse?.visaCardActivationStatus as? VisaCardActivationStatus.Activated
?: return "Error in the app!"
val accessToken = status.visaAuthTokens.accessToken
return "Bearer $accessToken"
return status.visaAuthTokens.getAuthHeader()
}
}