Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-01 09:06:11 +03:00
parent ded577faff
commit aea7f5f415
21 changed files with 220 additions and 136 deletions

View file

@ -122,6 +122,7 @@ dependencies {
implementation(projects.core.utils) implementation(projects.core.utils)
implementation(projects.core.decompose) implementation(projects.core.decompose)
implementation(projects.core.deepLinks) implementation(projects.core.deepLinks)
implementation(projects.core.error.ext)
implementation(projects.libs.crypto) implementation(projects.libs.crypto)
implementation(projects.libs.auth) implementation(projects.libs.auth)
implementation(projects.libs.blockchainSdk) implementation(projects.libs.blockchainSdk)

View file

@ -9,11 +9,13 @@ import com.tangem.common.card.EllipticCurve
import com.tangem.common.core.CardSession import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.CompletionCallback import com.tangem.common.core.CompletionCallback
import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString import com.tangem.common.extensions.toHexString
import com.tangem.common.map import com.tangem.common.map
import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource import com.tangem.common.timemeasure.RealtimeMonotonicTimeSource
import com.tangem.core.error.ext.tangemError
import com.tangem.crypto.CryptoUtils import com.tangem.crypto.CryptoUtils
import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.datasource.local.visa.VisaAuthTokenStorage
import com.tangem.datasource.local.visa.VisaOTPStorage import com.tangem.datasource.local.visa.VisaOTPStorage
@ -22,6 +24,8 @@ import com.tangem.datasource.local.visa.hasSavedOTP
import com.tangem.domain.common.visa.VisaUtilities import com.tangem.domain.common.visa.VisaUtilities
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.visa.error.VisaActivationError
import com.tangem.domain.visa.error.VisaAuthorizationAPIError
import com.tangem.domain.visa.model.* import com.tangem.domain.visa.model.*
import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.domain.visa.repository.VisaAuthRepository import com.tangem.domain.visa.repository.VisaAuthRepository
@ -68,7 +72,7 @@ class VisaCardActivationTask @AssistedInject constructor(
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
if (card.cardId != activationInput.cardId) { if (card.cardId != activationInput.cardId) {
return CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.WrongCard.message)) return CompletionResult.Failure(VisaActivationError.WrongCard.tangemError)
} }
val visaActivationRepository = visaActivationRepositoryFactory.create( val visaActivationRepository = visaActivationRepositoryFactory.create(
@ -149,7 +153,7 @@ class VisaCardActivationTask @AssistedInject constructor(
private suspend fun SessionContext.getDataToSign( private suspend fun SessionContext.getDataToSign(
signedChallenge: VisaAuthSignedChallenge, signedChallenge: VisaAuthSignedChallenge,
): Either<TangemSdkError.Underlying, VisaDataToSignByCardWallet> = either { ): Either<TangemError, VisaDataToSignByCardWallet> = either {
catch( catch(
block = { block = {
val tokens = visaAuthRepository.getAccessTokens(signedChallenge) val tokens = visaAuthRepository.getAccessTokens(signedChallenge)
@ -158,13 +162,13 @@ class VisaCardActivationTask @AssistedInject constructor(
val remoteState = visaActivationRepository.getActivationRemoteState() val remoteState = visaActivationRepository.getActivationRemoteState()
if (remoteState !is VisaActivationRemoteState.CardWalletSignatureRequired) { if (remoteState !is VisaActivationRemoteState.CardWalletSignatureRequired) {
raise(TangemSdkError.Underlying(VisaActivationError.WrongRemoteState.message)) raise(VisaActivationError.WrongRemoteState.tangemError)
} }
visaActivationRepository.getCardWalletAcceptanceData(remoteState.request) visaActivationRepository.getCardWalletAcceptanceData(remoteState.request)
}, },
catch = { catch = {
raise(TangemSdkError.Underlying("Underlying network error: ${it.message ?: ""}")) raise(VisaAuthorizationAPIError.tangemError)
}, },
) )
} }
@ -246,10 +250,10 @@ class VisaCardActivationTask @AssistedInject constructor(
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
val derivedPublicKey = wallet.derivedKeys[VisaUtilities.visaDefaultDerivationPath] val derivedPublicKey = wallet.derivedKeys[VisaUtilities.visaDefaultDerivationPath]
?: return CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.MissingWallet.message)) ?: return CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError)
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey) val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey)
.getOrElse { return CompletionResult.Failure(TangemSdkError.Underlying(it.message)) } .getOrElse { return CompletionResult.Failure(it.tangemError) }
.value .value
val task = SignHashCommand( val task = SignHashCommand(
@ -291,7 +295,7 @@ class VisaCardActivationTask @AssistedInject constructor(
): CompletionResult<VisaCardActivationResponse> { ): CompletionResult<VisaCardActivationResponse> {
val otp = otpStorage.getOTP(cardId) ?: run { val otp = otpStorage.getOTP(cardId) ?: run {
createOTP() createOTP()
otpStorage.getOTP(cardId) ?: return CompletionResult.Failure(TangemSdkError.Underlying("OTP not found")) otpStorage.getOTP(cardId) ?: return CompletionResult.Failure(VisaActivationError.MissingRootOTP.tangemError)
} }
val signedActivationData = dataToSign.sign( val signedActivationData = dataToSign.sign(

View file

@ -11,6 +11,7 @@ import com.tangem.common.core.CompletionCallback
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString import com.tangem.common.extensions.toHexString
import com.tangem.core.error.ext.tangemError
import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.common.util.derivationStyleProvider import com.tangem.domain.common.util.derivationStyleProvider
@ -18,7 +19,7 @@ import com.tangem.domain.common.visa.VisaUtilities
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation
import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.visa.model.VisaActivationError import com.tangem.domain.visa.error.VisaActivationError
import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
import com.tangem.domain.visa.model.sign import com.tangem.domain.visa.model.sign
@ -37,6 +38,7 @@ class VisaCustomerWalletApproveTask(
} }
if (VisaUtilities.isVisaCard(card.firmwareVersion.doubleValue, card.batchId)) { if (VisaUtilities.isVisaCard(card.firmwareVersion.doubleValue, card.batchId)) {
// TODO TVF-21
callback(CompletionResult.Failure(TangemSdkError.Underlying("Can't use Visa card for approve"))) callback(CompletionResult.Failure(TangemSdkError.Underlying("Can't use Visa card for approve")))
return return
} }
@ -44,7 +46,7 @@ class VisaCustomerWalletApproveTask(
if (visaDataForApprove.customerWalletCardId != null && card.cardId != visaDataForApprove.customerWalletCardId) { if (visaDataForApprove.customerWalletCardId != null && card.cardId != visaDataForApprove.customerWalletCardId) {
callback( callback(
CompletionResult.Failure( CompletionResult.Failure(
TangemSdkError.Underlying("Use tangem wallet specified during visa registration"), TangemSdkError.Underlying("Use tangem wallet specified during visa registration"), // TODO TVF-21
), ),
) )
return return
@ -74,16 +76,12 @@ class VisaCustomerWalletApproveTask(
} }
val derivationPath = VisaUtilities.visaDefaultDerivationPath(derivationStyle) ?: run { val derivationPath = VisaUtilities.visaDefaultDerivationPath(derivationStyle) ?: run {
callback( callback(CompletionResult.Failure(VisaActivationError.FailedToCreateAddress.tangemError))
CompletionResult.Failure(
TangemSdkError.Underlying("Failed to generate derivation path with provided derivation style"),
),
)
return return
} }
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run { val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
callback(CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.MissingWallet.message))) callback(CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError))
return return
} }
@ -123,7 +121,7 @@ class VisaCustomerWalletApproveTask(
) )
validationResult.onLeft { validationResult.onLeft {
callback(CompletionResult.Failure(TangemSdkError.Underlying(it.message))) callback(CompletionResult.Failure(it.tangemError))
return return
} }
@ -144,7 +142,7 @@ class VisaCustomerWalletApproveTask(
targetAddress = visaDataForApprove.targetAddress, targetAddress = visaDataForApprove.targetAddress,
card = CardDTO(card), card = CardDTO(card),
).getOrElse { ).getOrElse {
callback(CompletionResult.Failure(TangemSdkError.Underlying(it.message))) callback(CompletionResult.Failure(it.tangemError))
return return
} }

View file

@ -8,11 +8,15 @@ import com.tangem.common.core.CardSession
import com.tangem.common.core.TangemSdkError import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString import com.tangem.common.extensions.toHexString
import com.tangem.core.error.ext.tangemError
import com.tangem.crypto.hdWallet.DerivationPath import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.datasource.local.visa.VisaAuthTokenStorage
import com.tangem.domain.common.visa.VisaUtilities import com.tangem.domain.common.visa.VisaUtilities
import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility import com.tangem.domain.common.visa.VisaWalletPublicKeyUtility
import com.tangem.domain.visa.error.VisaActivationError
import com.tangem.domain.visa.error.VisaAuthorizationAPIError
import com.tangem.domain.visa.error.VisaCardScanError
import com.tangem.domain.visa.model.* import com.tangem.domain.visa.model.*
import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.domain.visa.repository.VisaAuthRepository import com.tangem.domain.visa.repository.VisaAuthRepository
@ -71,9 +75,7 @@ internal class VisaCardScanHandler @Inject constructor(
val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run { val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run {
Timber.e("Failed to create derivation path while first scan") Timber.e("Failed to create derivation path while first scan")
return CompletionResult.Failure( return CompletionResult.Failure(VisaCardScanError.FailedToCreateDerivationPath.tangemError)
TangemSdkError.Underlying(VisaCardScanHandlerError.FailedToCreateDerivationPath.errorDescription),
)
} }
val derivationTask = DeriveWalletPublicKeyTask(wallet.publicKey, derivationPath) val derivationTask = DeriveWalletPublicKeyTask(wallet.publicKey, derivationPath)
@ -104,32 +106,24 @@ internal class VisaCardScanHandler @Inject constructor(
val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run { val derivationPath = VisaUtilities.visaDefaultDerivationPath ?: run {
Timber.e("Failed to create derivation path while handling wallet authorization") Timber.e("Failed to create derivation path while handling wallet authorization")
return CompletionResult.Failure( return CompletionResult.Failure(VisaCardScanError.FailedToCreateDerivationPath.tangemError)
TangemSdkError.Underlying(VisaCardScanHandlerError.FailedToCreateDerivationPath.errorDescription),
)
} }
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead()) val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run { val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
Timber.e("Failed to find extended public key while handling wallet authorization") Timber.e("Failed to find extended public key while handling wallet authorization")
return CompletionResult.Failure( return CompletionResult.Failure(VisaCardScanError.FailedToFindWallet.tangemError)
TangemSdkError.Underlying(VisaCardScanHandlerError.FailedToFindDerivedWalletKey.errorDescription),
)
} }
val extendedPublicKey = wallet.derivedKeys[derivationPath] ?: run { val extendedPublicKey = wallet.derivedKeys[derivationPath] ?: run {
Timber.e("Failed to find extended public key while handling wallet authorization") Timber.e("Failed to find extended public key while handling wallet authorization")
return CompletionResult.Failure( return CompletionResult.Failure(VisaCardScanError.FailedToFindDerivedWalletKey.tangemError)
TangemSdkError.Underlying(VisaCardScanHandlerError.FailedToFindDerivedWalletKey.errorDescription),
)
} }
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(extendedPublicKey.publicKey) val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(extendedPublicKey.publicKey)
.getOrElse { .getOrElse {
return CompletionResult.Failure( return CompletionResult.Failure(it.tangemError)
TangemSdkError.Underlying("Cannot generate address on Visa curve"),
)
} }
Timber.i("Requesting challenge for wallet authorization") Timber.i("Requesting challenge for wallet authorization")
@ -200,7 +194,7 @@ internal class VisaCardScanHandler @Inject constructor(
) )
}.getOrElse { }.getOrElse {
Timber.e("Failed to get challenge for Card authorization. Plain error: ${it.message}") Timber.e("Failed to get challenge for Card authorization. Plain error: ${it.message}")
return CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: "Unknown error")) return CompletionResult.Failure(VisaAuthorizationAPIError.tangemError)
} }
Timber.i("Received challenge to sign: ${challengeResponse.challenge}") Timber.i("Received challenge to sign: ${challengeResponse.challenge}")
@ -230,11 +224,7 @@ internal class VisaCardScanHandler @Inject constructor(
) )
}.getOrElse { }.getOrElse {
Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.message}") Timber.e("Failed to sign challenge with Card public key. Plain error: ${it.message}")
return CompletionResult.Failure( return CompletionResult.Failure(VisaAuthorizationAPIError.tangemError)
TangemSdkError.Underlying(
customMessage = it.message ?: "Unknown error",
),
)
} }
visaAuthTokenStorage.store( visaAuthTokenStorage.store(
@ -251,7 +241,7 @@ internal class VisaCardScanHandler @Inject constructor(
} }
if (error != null) { if (error != null) {
return CompletionResult.Failure(TangemSdkError.Underlying(error.message)) return CompletionResult.Failure(error.tangemError)
} }
val activationInput = VisaActivationInput( val activationInput = VisaActivationInput(

View file

@ -1,9 +0,0 @@
package com.tangem.tap.domain.visa
private const val COMMON_DESCRIPTION = "Error occurred. Please contact support."
internal enum class VisaCardScanHandlerError(val errorDescription: String) {
FailedToCreateDerivationPath(COMMON_DESCRIPTION),
FailedToFindWallet(COMMON_DESCRIPTION),
FailedToFindDerivedWalletKey(COMMON_DESCRIPTION),
}

View file

@ -1,14 +1,4 @@
plugins { plugins {
alias(deps.plugins.android.library) alias(deps.plugins.kotlin.jvm)
alias(deps.plugins.kotlin.android)
id("configuration") id("configuration")
}
android {
namespace = "com.tangem.core.error"
}
dependencies {
implementation(tangemDeps.card.core)
implementation(tangemDeps.blockchain)
} }

1
core/error/ext/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,16 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
id("configuration")
}
android {
namespace = "com.tangem.core.error.ext"
}
dependencies {
api(projects.core.error)
implementation(tangemDeps.card.core)
implementation(tangemDeps.blockchain)
}

View file

@ -0,0 +1,27 @@
package com.tangem.core.error.ext
import com.tangem.common.core.TangemSdkError
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.core.TangemError
import com.tangem.core.error.UniversalError
val TangemError.universalError: UniversalError
get() = when (this) {
is TangemSdkError -> object : UniversalError {
override val errorCode: Int = 101000000 + code
}
else -> object : UniversalError {
override val errorCode: Int = code
}
}
val BlockchainSdkError.universalError: UniversalError
get() = object : UniversalError {
override val errorCode: Int = 102000000 + code
}
val UniversalError.tangemError: TangemError
get() = object : TangemError(errorCode) {
override var customMessage: String = errorCode.toString()
override val messageResId: Int? = null
}

View file

@ -1,14 +0,0 @@
package com.tangem.core.error
import com.tangem.common.core.TangemSdkError
import com.tangem.blockchain.common.BlockchainSdkError
val TangemSdkError.universalError: UniversalError
get() = object : UniversalError {
override val errorCode: Int = 101000000 + code
}
val BlockchainSdkError.universalError: UniversalError
get() = object : UniversalError {
override val errorCode: Int = 102000000 + code
}

View file

@ -12,6 +12,7 @@ android {
dependencies { dependencies {
implementation(projects.core.datasource) implementation(projects.core.datasource)
implementation(projects.core.utils) implementation(projects.core.utils)
implementation(projects.core.error)
implementation(projects.common) implementation(projects.common)
implementation(projects.libs.auth) implementation(projects.libs.auth)
implementation(projects.libs.blockchainSdk) implementation(projects.libs.blockchainSdk)

View file

@ -8,29 +8,33 @@ import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.card.EllipticCurve import com.tangem.common.card.EllipticCurve
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.visa.error.VisaActivationError
object VisaWalletPublicKeyUtility { object VisaWalletPublicKeyUtility {
fun validateExtendedPublicKey(targetAddress: String, extendedPublicKey: ExtendedPublicKey): Either<Error, Unit> = fun validateExtendedPublicKey(
either { targetAddress: String,
validatePublicKey( extendedPublicKey: ExtendedPublicKey,
targetAddress = targetAddress, ): Either<VisaActivationError, Unit> = either {
publicKey = extendedPublicKey.publicKey,
).bind()
}
fun findKeyWithoutDerivation(targetAddress: String, card: CardDTO): Either<Error, ByteArray> = either {
val wallet = findWalletOnSecp256k1(card).bind()
validatePublicKey( validatePublicKey(
targetAddress = targetAddress, targetAddress = targetAddress,
publicKey = wallet.publicKey, publicKey = extendedPublicKey.publicKey,
).bind() ).bind()
wallet.publicKey
} }
fun generateAddressOnSecp256k1(walletPublicKey: ByteArray): Either<Error, Address> = either { fun findKeyWithoutDerivation(targetAddress: String, card: CardDTO): Either<VisaActivationError, ByteArray> =
either {
val wallet = findWalletOnSecp256k1(card).bind()
validatePublicKey(
targetAddress = targetAddress,
publicKey = wallet.publicKey,
).bind()
wallet.publicKey
}
fun generateAddressOnSecp256k1(walletPublicKey: ByteArray): Either<VisaActivationError, Address> = either {
val addresses = catch( val addresses = catch(
block = { block = {
VisaUtilities.visaBlockchain.makeAddresses( VisaUtilities.visaBlockchain.makeAddresses(
@ -39,27 +43,22 @@ object VisaWalletPublicKeyUtility {
curve = EllipticCurve.Secp256k1, curve = EllipticCurve.Secp256k1,
) )
}, },
catch = { raise(Error.FailedToCreateAddress) }, catch = { raise(VisaActivationError.FailedToCreateAddress) },
) )
addresses.firstOrNull { it.type == AddressType.Default } ?: raise(Error.FailedToCreateAddress) addresses.firstOrNull { it.type == AddressType.Default } ?: raise(VisaActivationError.FailedToCreateAddress)
} }
private fun findWalletOnSecp256k1(card: CardDTO): Either<Error, CardDTO.Wallet> = either { private fun findWalletOnSecp256k1(card: CardDTO): Either<VisaActivationError, CardDTO.Wallet> = either {
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: raise(Error.MissingWalletOnTargetCurve) card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: raise(VisaActivationError.MissingWallet)
} }
private fun validatePublicKey(targetAddress: String, publicKey: ByteArray): Either<Error, Unit> = either { private fun validatePublicKey(targetAddress: String, publicKey: ByteArray): Either<VisaActivationError, Unit> =
val address = generateAddressOnSecp256k1(publicKey).bind() either {
val address = generateAddressOnSecp256k1(publicKey).bind()
if (address.value != targetAddress) { if (address.value != targetAddress) {
raise(Error.AddressNotMatched) raise(VisaActivationError.AddressNotMatched)
}
} }
}
enum class Error(val message: String) {
AddressNotMatched("ValidationError: Address not matched"),
FailedToCreateAddress("ValidationError: Failed to create address"),
MissingWalletOnTargetCurve("ValidationError: Missing wallet on target curve"),
}
} }

View file

@ -10,4 +10,5 @@ dependencies {
ksp(deps.moshi.kotlin.codegen) ksp(deps.moshi.kotlin.codegen)
implementation(deps.moshi.adapters) implementation(deps.moshi.adapters)
implementation(deps.kotlin.serialization) implementation(deps.kotlin.serialization)
implementation(projects.core.error)
} }

View file

@ -0,0 +1,51 @@
@file:Suppress("MagicNumber")
package com.tangem.domain.visa.error
import com.tangem.core.error.UniversalError
/**
* Each error code must follow this format: xxxyyyzzz where
* xxx - Feature code
* yyy - Subsystem code
* zzz - Specific error code
* If you need to add new subsystem add it to list below incrementing last code.
* `Subsystems`:
* `001` - Common API
* `002` - First card scan error
* `003` - Activation
* `004` - Authorization API
*/
object VisaError : UniversalError {
override val errorCode: Int = 104000000
}
object VisaAPIError : UniversalError {
override val errorCode: Int = 104001000
}
enum class VisaCardScanError(
override val errorCode: Int,
) : UniversalError {
FailedToCreateDerivationPath(104002001),
FailedToFindWallet(104002002),
FailedToFindDerivedWalletKey(104002003),
}
enum class VisaActivationError(
override val errorCode: Int,
) : UniversalError {
BlockedForActivation(104003001),
InvalidActivationState(104003002),
WrongCard(104003003),
WrongRemoteState(104003004),
MissingWallet(104003005),
MissingRootOTP(104003006),
FailedToCreateAddress(104003007),
AddressNotMatched(104003008),
InconsistentRemoteState(104003009),
}
object VisaAuthorizationAPIError : UniversalError {
override val errorCode: Int = 104004000
}

View file

@ -1,10 +0,0 @@
package com.tangem.domain.visa.model
enum class VisaActivationError(val message: String) {
BlockedForActivation("Card is blocked for activation"),
InvalidActivationState("Invalid activation state"),
WrongCard("Wrong card tapped"),
WrongRemoteState("Wrong remote state"),
MissingWallet("Missing wallet"),
MissingRootOTP("Missing root OTP"),
}

View file

@ -27,6 +27,7 @@ dependencies {
implementation(projects.core.decompose) implementation(projects.core.decompose)
implementation(projects.core.navigation) implementation(projects.core.navigation)
implementation(projects.core.datasource) implementation(projects.core.datasource)
implementation(projects.core.error.ext)
/** Common */ /** Common */
implementation(projects.common.ui) implementation(projects.common.ui)

View file

@ -7,6 +7,11 @@ import com.tangem.common.extensions.toHexString
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.error.ext.universalError
import com.tangem.core.ui.utils.showErrorDialog
import com.tangem.domain.visa.error.VisaAPIError
import com.tangem.domain.visa.error.VisaAuthorizationAPIError
import com.tangem.domain.visa.model.VisaCardActivationStatus import com.tangem.domain.visa.model.VisaCardActivationStatus
import com.tangem.domain.visa.model.VisaCardId import com.tangem.domain.visa.model.VisaCardId
import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest
@ -34,6 +39,7 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
@Suppress("UnusedPrivateMember") @Suppress("UnusedPrivateMember")
private val tangemSdkManager: TangemSdkManager, private val tangemSdkManager: TangemSdkManager,
private val visaAuthRepository: VisaAuthRepository, private val visaAuthRepository: VisaAuthRepository,
private val uiMessageSender: UiMessageSender,
) : Model() { ) : Model() {
private val params: OnboardingVisaAccessCodeComponent.Config = paramsContainer.require() private val params: OnboardingVisaAccessCodeComponent.Config = paramsContainer.require()
@ -138,7 +144,7 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
) )
}.getOrElse { }.getOrElse {
loading(false) loading(false)
// TODO show alert uiMessageSender.showErrorDialog(VisaAuthorizationAPIError)
return@launch return@launch
} }
@ -148,17 +154,22 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
authorizationChallenge = challengeToSign, authorizationChallenge = challengeToSign,
), ),
activationInput = activationStatus.activationInput, activationInput = activationStatus.activationInput,
) as? CompletionResult.Success ?: run { )
loading(false)
// TODO show alert val resultData = when (result) {
return@launch is CompletionResult.Failure -> {
loading(false)
uiMessageSender.showErrorDialog(result.error.universalError)
return@launch
}
is CompletionResult.Success -> result.data
} }
runCatching { runCatching {
visaActivationRepository.activateCard(result.data.signedActivationData) visaActivationRepository.activateCard(resultData.signedActivationData)
}.onFailure { }.onFailure {
loading(false) loading(false)
// TODO show alert uiMessageSender.showErrorDialog(VisaAPIError)
return@launch return@launch
} }

View file

@ -6,6 +6,10 @@ import com.tangem.common.extensions.toHexString
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.error.ext.universalError
import com.tangem.core.ui.utils.showErrorDialog
import com.tangem.domain.visa.error.VisaAPIError
import com.tangem.domain.visa.model.VisaCardId import com.tangem.domain.visa.model.VisaCardId
import com.tangem.domain.visa.model.VisaDataForApprove import com.tangem.domain.visa.model.VisaDataForApprove
import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaActivationRepository
@ -27,6 +31,7 @@ internal class OnboardingVisaApproveModel @Inject constructor(
visaActivationRepositoryFactory: VisaActivationRepository.Factory, visaActivationRepositoryFactory: VisaActivationRepository.Factory,
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val tangemSdkManager: TangemSdkManager, private val tangemSdkManager: TangemSdkManager,
private val uiMessageSender: UiMessageSender,
) : Model() { ) : Model() {
private val params = paramsContainer.require<OnboardingVisaApproveComponent.Config>() private val params = paramsContainer.require<OnboardingVisaApproveComponent.Config>()
@ -56,7 +61,7 @@ internal class OnboardingVisaApproveModel @Inject constructor(
visaActivationRepository.getCustomerWalletAcceptanceData(params.preparationDataForApprove.request) visaActivationRepository.getCustomerWalletAcceptanceData(params.preparationDataForApprove.request)
}.getOrElse { }.getOrElse {
loading(false) loading(false)
// TODO show dialog uiMessageSender.showErrorDialog(VisaAPIError)
return@launch return@launch
} }
@ -66,17 +71,22 @@ internal class OnboardingVisaApproveModel @Inject constructor(
targetAddress = params.preparationDataForApprove.customerWalletAddress, targetAddress = params.preparationDataForApprove.customerWalletAddress,
dataToSign = dataToSign, dataToSign = dataToSign,
), ),
) as? CompletionResult.Success ?: run { )
loading(false)
// TODO show dialog val resultData = when (result) {
return@launch is CompletionResult.Failure -> {
loading(false)
uiMessageSender.showErrorDialog(result.error.universalError)
return@launch
}
is CompletionResult.Success -> result.data
} }
runCatching { runCatching {
visaActivationRepository.approveByCustomerWallet(result.data) visaActivationRepository.approveByCustomerWallet(resultData)
}.onFailure { }.onFailure {
loading(false) loading(false)
// TODO show dialog uiMessageSender.showErrorDialog(VisaAPIError)
return@launch return@launch
} }

View file

@ -5,9 +5,13 @@ import com.tangem.common.extensions.toHexString
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.utils.showErrorDialog
import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.datasource.local.visa.VisaAuthTokenStorage
import com.tangem.datasource.local.visa.VisaOTPStorage import com.tangem.datasource.local.visa.VisaOTPStorage
import com.tangem.domain.models.scan.ScanResponse import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.visa.error.VisaActivationError
import com.tangem.domain.visa.error.VisaAuthorizationAPIError
import com.tangem.domain.visa.model.* import com.tangem.domain.visa.model.*
import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaActivationRepository
import com.tangem.domain.visa.repository.VisaAuthRepository import com.tangem.domain.visa.repository.VisaAuthRepository
@ -36,6 +40,7 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
private val otpStorage: VisaOTPStorage, private val otpStorage: VisaOTPStorage,
private val userWalletBuilderFactory: UserWalletBuilder.Factory, private val userWalletBuilderFactory: UserWalletBuilder.Factory,
private val userWalletsListManager: UserWalletsListManager, private val userWalletsListManager: UserWalletsListManager,
private val uiMessageSender: UiMessageSender,
) : Model() { ) : Model() {
private val params = paramsContainer.require<Config>() private val params = paramsContainer.require<Config>()
@ -58,7 +63,7 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
is VisaActivationRemoteState.CardWalletSignatureRequired, is VisaActivationRemoteState.CardWalletSignatureRequired,
VisaActivationRemoteState.BlockedForActivation, VisaActivationRemoteState.BlockedForActivation,
-> { -> {
// TODO show alert inconsistent state uiMessageSender.showErrorDialog(VisaActivationError.InconsistentRemoteState)
return@launch return@launch
} }
is VisaActivationRemoteState.CustomerWalletSignatureRequired, is VisaActivationRemoteState.CustomerWalletSignatureRequired,
@ -92,7 +97,7 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
val newTokens = runCatching { val newTokens = runCatching {
visaAuthRepository.refreshAccessTokens(authTokens.refreshToken) visaAuthRepository.refreshAccessTokens(authTokens.refreshToken)
}.getOrElse { }.getOrElse {
// TODO show alert uiMessageSender.showErrorDialog(VisaAuthorizationAPIError)
return return
} }

View file

@ -5,6 +5,10 @@ import com.tangem.common.extensions.toHexString
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.error.ext.universalError
import com.tangem.core.ui.utils.showErrorDialog
import com.tangem.domain.visa.error.VisaAPIError
import com.tangem.domain.visa.model.VisaCardId import com.tangem.domain.visa.model.VisaCardId
import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest import com.tangem.domain.visa.model.VisaCustomerWalletDataToSignRequest
import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaActivationRepository
@ -28,6 +32,7 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider, override val dispatchers: CoroutineDispatcherProvider,
private val tangemSdkManager: TangemSdkManager, private val tangemSdkManager: TangemSdkManager,
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory, private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
private val uiMessageSender: UiMessageSender,
) : Model() { ) : Model() {
private val params = paramsContainer.require<Config>() private val params = paramsContainer.require<Config>()
@ -68,24 +73,29 @@ internal class OnboardingVisaWelcomeModel @Inject constructor(
visaActivationRepository.getCardWalletAcceptanceData(params.dataToSignRequest) visaActivationRepository.getCardWalletAcceptanceData(params.dataToSignRequest)
}.getOrElse { }.getOrElse {
loading(false) loading(false)
// TODO show alert uiMessageSender.showErrorDialog(VisaAPIError)
return@launch return@launch
} }
val result = tangemSdkManager.activateVisaCard( val result = tangemSdkManager.activateVisaCard(
mode = VisaCardActivationTaskMode.SignOnly(dataToSignByCardWallet = dataToSignByCardWallet), mode = VisaCardActivationTaskMode.SignOnly(dataToSignByCardWallet = dataToSignByCardWallet),
activationInput = params.activationInput, activationInput = params.activationInput,
) as? CompletionResult.Success ?: run { )
loading(false)
// TODO show alert val resultData = when (result) {
return@launch is CompletionResult.Failure -> {
loading(false)
uiMessageSender.showErrorDialog(result.error.universalError)
return@launch
}
is CompletionResult.Success -> result.data
} }
runCatching { runCatching {
visaActivationRepository.activateCard(result.data.signedActivationData) visaActivationRepository.activateCard(resultData.signedActivationData)
}.onFailure { }.onFailure {
loading(false) loading(false)
// TODO show alert uiMessageSender.showErrorDialog(VisaAPIError)
return@launch return@launch
} }

View file

@ -149,6 +149,7 @@ include(":core:deep-links:global")
include(":core:decompose") include(":core:decompose")
include(":core:pagination") include(":core:pagination")
include(":core:error") include(":core:error")
include(":core:error:ext")
// endregion Core modules // endregion Core modules
// region Common modules // region Common modules