Updated on 2026-08-14
This commit is contained in:
parent
19fb8f2bcd
commit
ecdb72e55c
58 changed files with 1082 additions and 346 deletions
|
|
@ -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_"
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue