Updated on 2026-08-14
This commit is contained in:
parent
6f973aed83
commit
9812f9f253
15 changed files with 184 additions and 67 deletions
|
|
@ -5,6 +5,7 @@ import arrow.core.getOrElse
|
||||||
import arrow.core.raise.catch
|
import arrow.core.raise.catch
|
||||||
import arrow.core.raise.either
|
import arrow.core.raise.either
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
|
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
|
||||||
|
|
@ -173,10 +174,10 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
|
|
||||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
|
|
||||||
return if (card.wallets.any { it.curve == VisaUtilities.mandatoryCurve }) {
|
return if (card.wallets.any { it.curve == EllipticCurve.Secp256k1 }) {
|
||||||
createOTP()
|
createOTP()
|
||||||
} else {
|
} else {
|
||||||
val createWalletTask = CreateWalletTask(VisaUtilities.mandatoryCurve)
|
val createWalletTask = CreateWalletTask(EllipticCurve.Secp256k1)
|
||||||
|
|
||||||
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||||
suspendCancellableCoroutine { continuation ->
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
|
@ -241,13 +242,13 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
val card =
|
val card =
|
||||||
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
val wallet =
|
val wallet =
|
||||||
card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve }
|
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||||
?: 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(TangemSdkError.Underlying(VisaActivationError.MissingWallet.message))
|
||||||
|
|
||||||
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnVisaCurve(derivedPublicKey.publicKey)
|
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(derivedPublicKey.publicKey)
|
||||||
.getOrElse { return CompletionResult.Failure(TangemSdkError.Underlying(it.message)) }
|
.getOrElse { return CompletionResult.Failure(TangemSdkError.Underlying(it.message)) }
|
||||||
.value
|
.value
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import arrow.core.getOrElse
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
import com.tangem.common.card.Card
|
import com.tangem.common.card.Card
|
||||||
import com.tangem.common.card.CardWallet
|
import com.tangem.common.card.CardWallet
|
||||||
|
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
|
||||||
|
|
@ -81,7 +82,7 @@ class VisaCustomerWalletApproveTask(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run {
|
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
||||||
callback(CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.MissingWallet.message)))
|
callback(CompletionResult.Failure(TangemSdkError.Underlying(VisaActivationError.MissingWallet.message)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.domain.visa
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
import com.tangem.common.card.CardWallet
|
import com.tangem.common.card.CardWallet
|
||||||
|
import com.tangem.common.card.EllipticCurve
|
||||||
import com.tangem.common.core.CardSession
|
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
|
||||||
|
|
@ -56,7 +57,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
session = session,
|
session = session,
|
||||||
)
|
)
|
||||||
|
|
||||||
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run {
|
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
||||||
val activationInput =
|
val activationInput =
|
||||||
VisaActivationInput(card.cardId, card.cardPublicKey.toHexString(), card.isAccessCodeSet)
|
VisaActivationInput(card.cardId, card.cardPublicKey.toHexString(), card.isAccessCodeSet)
|
||||||
val activationStatus = VisaCardActivationStatus.NotStartedActivation(activationInput)
|
val activationStatus = VisaCardActivationStatus.NotStartedActivation(activationInput)
|
||||||
|
|
@ -110,7 +111,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
|
|
||||||
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 == VisaUtilities.mandatoryCurve } ?: 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(
|
||||||
TangemSdkError.Underlying(VisaCardScanHandlerError.FailedToFindDerivedWalletKey.errorDescription),
|
TangemSdkError.Underlying(VisaCardScanHandlerError.FailedToFindDerivedWalletKey.errorDescription),
|
||||||
|
|
@ -124,7 +125,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnVisaCurve(extendedPublicKey.publicKey)
|
val walletAddress = VisaWalletPublicKeyUtility.generateAddressOnSecp256k1(extendedPublicKey.publicKey)
|
||||||
.getOrElse {
|
.getOrElse {
|
||||||
return CompletionResult.Failure(
|
return CompletionResult.Failure(
|
||||||
TangemSdkError.Underlying("Cannot generate address on Visa curve"),
|
TangemSdkError.Underlying("Cannot generate address on Visa curve"),
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import androidx.paging.PagingData
|
||||||
import arrow.fx.coroutines.parZip
|
import arrow.fx.coroutines.parZip
|
||||||
import com.tangem.blockchain.common.address.Address
|
import com.tangem.blockchain.common.address.Address
|
||||||
import com.tangem.blockchain.common.address.AddressType
|
import com.tangem.blockchain.common.address.AddressType
|
||||||
|
import com.tangem.common.card.EllipticCurve
|
||||||
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.data.common.cache.CacheRegistry
|
import com.tangem.data.common.cache.CacheRegistry
|
||||||
|
|
@ -195,7 +196,7 @@ internal class DefaultVisaRepository @Inject constructor(
|
||||||
if (VisaConstants.IS_DEMO_MODE_ENABLED) return getDemoPublicKey()
|
if (VisaConstants.IS_DEMO_MODE_ENABLED) return getDemoPublicKey()
|
||||||
|
|
||||||
val cardWallet = userWallet.scanResponse.card.wallets.firstOrNull {
|
val cardWallet = userWallet.scanResponse.card.wallets.firstOrNull {
|
||||||
it.curve == VisaUtilities.mandatoryCurve
|
it.curve == EllipticCurve.Secp256k1
|
||||||
}
|
}
|
||||||
requireNotNull(cardWallet) { "Visa card wallet not found" }
|
requireNotNull(cardWallet) { "Visa card wallet not found" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.tangem.domain.common.visa
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Blockchain
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchain.common.derivation.DerivationStyle
|
import com.tangem.blockchain.common.derivation.DerivationStyle
|
||||||
import com.tangem.common.card.EllipticCurve
|
|
||||||
import com.tangem.common.card.FirmwareVersion
|
import com.tangem.common.card.FirmwareVersion
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
|
|
||||||
|
|
@ -10,8 +9,6 @@ private const val VISA_BATCH_START = "AE"
|
||||||
|
|
||||||
object VisaUtilities {
|
object VisaUtilities {
|
||||||
|
|
||||||
val mandatoryCurve = EllipticCurve.Secp256k1
|
|
||||||
|
|
||||||
const val tokenId = "tether"
|
const val tokenId = "tether"
|
||||||
|
|
||||||
val visaBlockchain = Blockchain.Polygon
|
val visaBlockchain = Blockchain.Polygon
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import arrow.core.raise.catch
|
||||||
import arrow.core.raise.either
|
import arrow.core.raise.either
|
||||||
import com.tangem.blockchain.common.address.Address
|
import com.tangem.blockchain.common.address.Address
|
||||||
import com.tangem.blockchain.common.address.AddressType
|
import com.tangem.blockchain.common.address.AddressType
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ object VisaWalletPublicKeyUtility {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findKeyWithoutDerivation(targetAddress: String, card: CardDTO): Either<Error, ByteArray> = either {
|
fun findKeyWithoutDerivation(targetAddress: String, card: CardDTO): Either<Error, ByteArray> = either {
|
||||||
val wallet = findWalletOnVisaCurve(card).bind()
|
val wallet = findWalletOnSecp256k1(card).bind()
|
||||||
|
|
||||||
validatePublicKey(
|
validatePublicKey(
|
||||||
targetAddress = targetAddress,
|
targetAddress = targetAddress,
|
||||||
|
|
@ -29,13 +30,13 @@ object VisaWalletPublicKeyUtility {
|
||||||
wallet.publicKey
|
wallet.publicKey
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generateAddressOnVisaCurve(walletPublicKey: ByteArray): Either<Error, Address> = either {
|
fun generateAddressOnSecp256k1(walletPublicKey: ByteArray): Either<Error, Address> = either {
|
||||||
val addresses = catch(
|
val addresses = catch(
|
||||||
block = {
|
block = {
|
||||||
VisaUtilities.visaBlockchain.makeAddresses(
|
VisaUtilities.visaBlockchain.makeAddresses(
|
||||||
walletPublicKey = walletPublicKey,
|
walletPublicKey = walletPublicKey,
|
||||||
pairPublicKey = null,
|
pairPublicKey = null,
|
||||||
curve = VisaUtilities.mandatoryCurve,
|
curve = EllipticCurve.Secp256k1,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
catch = { raise(Error.FailedToCreateAddress) },
|
catch = { raise(Error.FailedToCreateAddress) },
|
||||||
|
|
@ -44,12 +45,12 @@ object VisaWalletPublicKeyUtility {
|
||||||
addresses.firstOrNull { it.type == AddressType.Default } ?: raise(Error.FailedToCreateAddress)
|
addresses.firstOrNull { it.type == AddressType.Default } ?: raise(Error.FailedToCreateAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findWalletOnVisaCurve(card: CardDTO): Either<Error, CardDTO.Wallet> = either {
|
private fun findWalletOnSecp256k1(card: CardDTO): Either<Error, CardDTO.Wallet> = either {
|
||||||
card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: raise(Error.MissingWalletOnTargetCurve)
|
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: raise(Error.MissingWalletOnTargetCurve)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validatePublicKey(targetAddress: String, publicKey: ByteArray): Either<Error, Unit> = either {
|
private fun validatePublicKey(targetAddress: String, publicKey: ByteArray): Either<Error, Unit> = either {
|
||||||
val address = generateAddressOnVisaCurve(publicKey).bind()
|
val address = generateAddressOnSecp256k1(publicKey).bind()
|
||||||
|
|
||||||
if (address.value != targetAddress) {
|
if (address.value != targetAddress) {
|
||||||
raise(Error.AddressNotMatched)
|
raise(Error.AddressNotMatched)
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,10 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
componentScope.launch {
|
||||||
|
model.onDone.collect { params.onDone() }
|
||||||
|
}
|
||||||
|
|
||||||
// sets title and stepper value
|
// sets title and stepper value
|
||||||
childStack.observe(lifecycle) { stack ->
|
childStack.observe(lifecycle) { stack ->
|
||||||
val currentRoute = stack.active.configuration
|
val currentRoute = stack.active.configuration
|
||||||
|
|
@ -138,20 +142,14 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
||||||
onEvent = { event -> model.navigateFromChooseWallet(route, event) },
|
onEvent = { event -> model.navigateFromChooseWallet(route, event) },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
OnboardingVisaRoute.InProgress -> OnboardingVisaInProgressComponent(
|
is OnboardingVisaRoute.InProgress -> OnboardingVisaInProgressComponent(
|
||||||
appComponentContext = factoryContext,
|
appComponentContext = factoryContext,
|
||||||
config = OnboardingVisaInProgressComponent.Config(
|
config = OnboardingVisaInProgressComponent.Config(
|
||||||
scanResponse = model.currentScanResponse.value,
|
scanResponse = model.currentScanResponse.value,
|
||||||
),
|
),
|
||||||
params = OnboardingVisaInProgressComponent.Params(
|
params = OnboardingVisaInProgressComponent.Params(
|
||||||
childParams = childParams,
|
childParams = childParams,
|
||||||
onDone = {
|
onDone = { model.navigateFromInProgress(it) },
|
||||||
model.stackNavigation.push(
|
|
||||||
OnboardingVisaRoute.PinCode(
|
|
||||||
activationOrderInfo = TODO(), // TODO [REDACTED_TASK_KEY] [Visa] In progress screen after pin code
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
is OnboardingVisaRoute.OtherWalletApproveOption -> OnboardingVisaOtherWalletComponent(
|
is OnboardingVisaRoute.OtherWalletApproveOption -> OnboardingVisaOtherWalletComponent(
|
||||||
|
|
@ -163,11 +161,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
||||||
params = OnboardingVisaOtherWalletComponent.Params(
|
params = OnboardingVisaOtherWalletComponent.Params(
|
||||||
childParams = childParams,
|
childParams = childParams,
|
||||||
onDone = {
|
onDone = {
|
||||||
model.stackNavigation.push(
|
model.stackNavigation.push(OnboardingVisaRoute.PinCode(activationOrderInfo = it))
|
||||||
OnboardingVisaRoute.PinCode(
|
|
||||||
activationOrderInfo = TODO(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -179,7 +173,11 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
||||||
),
|
),
|
||||||
params = OnboardingVisaPinCodeComponent.Params(
|
params = OnboardingVisaPinCodeComponent.Params(
|
||||||
childParams = childParams,
|
childParams = childParams,
|
||||||
onDone = { params.onDone() },
|
onDone = {
|
||||||
|
model.stackNavigation.push(
|
||||||
|
OnboardingVisaRoute.InProgress(from = OnboardingVisaRoute.InProgress.From.PinCode),
|
||||||
|
)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
is OnboardingVisaRoute.TangemWalletApproveOption -> OnboardingVisaApproveComponent(
|
is OnboardingVisaRoute.TangemWalletApproveOption -> OnboardingVisaApproveComponent(
|
||||||
|
|
@ -191,7 +189,11 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
||||||
),
|
),
|
||||||
params = OnboardingVisaApproveComponent.Params(
|
params = OnboardingVisaApproveComponent.Params(
|
||||||
childParams = childParams,
|
childParams = childParams,
|
||||||
onDone = { model.stackNavigation.push(OnboardingVisaRoute.InProgress) },
|
onDone = {
|
||||||
|
model.stackNavigation.push(
|
||||||
|
OnboardingVisaRoute.InProgress(from = OnboardingVisaRoute.InProgress.From.Approve),
|
||||||
|
)
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent
|
import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model.OnboardingVisaInProgressModel
|
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model.OnboardingVisaInProgressModel
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.ui.OnboardingVisaInProgress
|
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.ui.OnboardingVisaInProgress
|
||||||
|
import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ internal class OnboardingVisaInProgressComponent(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
model.onDone
|
model.onDone
|
||||||
.onEach { params.onDone() }
|
.onEach { params.onDone(it) }
|
||||||
.launchIn(componentScope)
|
.launchIn(componentScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,6 +44,12 @@ internal class OnboardingVisaInProgressComponent(
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
val childParams: DefaultOnboardingVisaComponent.ChildParams,
|
val childParams: DefaultOnboardingVisaComponent.ChildParams,
|
||||||
val onDone: () -> Unit,
|
val onDone: (DoneEvent) -> Unit,
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
sealed class DoneEvent {
|
||||||
|
data class NavigateTo(val route: OnboardingVisaRoute) : DoneEvent()
|
||||||
|
data object Activated : DoneEvent()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,47 +5,116 @@ import com.tangem.common.extensions.toHexString
|
||||||
import com.tangem.core.decompose.di.ComponentScoped
|
import com.tangem.core.decompose.di.ComponentScoped
|
||||||
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.domain.visa.model.VisaActivationRemoteState
|
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
||||||
import com.tangem.domain.visa.model.VisaCardId
|
import com.tangem.datasource.local.visa.VisaOTPStorage
|
||||||
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
import com.tangem.domain.visa.model.*
|
||||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent
|
import com.tangem.domain.visa.repository.VisaAuthRepository
|
||||||
|
import com.tangem.domain.wallets.builder.UserWalletBuilder
|
||||||
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
|
import com.tangem.domain.wallets.models.UserWallet
|
||||||
|
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
|
||||||
|
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent.Config
|
||||||
|
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent.Params
|
||||||
|
import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
@Stable
|
@Stable
|
||||||
@ComponentScoped
|
@ComponentScoped
|
||||||
internal class OnboardingVisaInProgressModel @Inject constructor(
|
internal class OnboardingVisaInProgressModel @Inject constructor(
|
||||||
paramsContainer: ParamsContainer,
|
paramsContainer: ParamsContainer,
|
||||||
visaActivationRepositoryFactory: VisaActivationRepository.Factory,
|
visaActivationRepositoryFactory: VisaActivationRepository.Factory,
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
private val visaAuthRepository: VisaAuthRepository,
|
||||||
|
private val visaAuthTokenStorage: VisaAuthTokenStorage,
|
||||||
|
private val otpStorage: VisaOTPStorage,
|
||||||
|
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||||
|
private val userWalletsListManager: UserWalletsListManager,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val params = paramsContainer.require<OnboardingVisaInProgressComponent.Config>()
|
private val params = paramsContainer.require<Config>()
|
||||||
private val visaActivationRepository = visaActivationRepositoryFactory.create(
|
private val visaActivationRepository = visaActivationRepositoryFactory.create(
|
||||||
VisaCardId(
|
VisaCardId(
|
||||||
cardId = params.scanResponse.card.cardId,
|
cardId = params.scanResponse.card.cardId,
|
||||||
cardPublicKey = params.scanResponse.card.cardPublicKey.toHexString(),
|
cardPublicKey = params.scanResponse.card.cardPublicKey.toHexString(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val onDone = MutableSharedFlow<Unit>()
|
val onDone = MutableSharedFlow<Params.DoneEvent>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
while (true) {
|
while (true) {
|
||||||
val result = runCatching {
|
val result = runCatching {
|
||||||
visaActivationRepository.getActivationRemoteStateLongPoll()
|
visaActivationRepository.getActivationRemoteStateLongPoll()
|
||||||
}.getOrNull()
|
}.getOrNull() ?: continue
|
||||||
|
|
||||||
if (result is VisaActivationRemoteState.WaitingPinCode) {
|
when (result) {
|
||||||
onDone.emit(Unit)
|
is VisaActivationRemoteState.CardWalletSignatureRequired,
|
||||||
break
|
VisaActivationRemoteState.BlockedForActivation,
|
||||||
|
-> {
|
||||||
|
// TODO show alert inconsistent state
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
is VisaActivationRemoteState.CustomerWalletSignatureRequired,
|
||||||
|
VisaActivationRemoteState.PaymentAccountDeploying,
|
||||||
|
VisaActivationRemoteState.WaitingForActivationFinishing,
|
||||||
|
-> {
|
||||||
|
}
|
||||||
|
is VisaActivationRemoteState.WaitingPinCode -> {
|
||||||
|
navigateToPinCode(result.activationOrderInfo)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
VisaActivationRemoteState.Activated -> {
|
||||||
|
finishActivation()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delay(timeMillis = 1000)
|
delay(timeMillis = 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun navigateToPinCode(activationOrderInfo: VisaActivationOrderInfo) {
|
||||||
|
onDone.emit(Params.DoneEvent.NavigateTo(OnboardingVisaRoute.PinCode(activationOrderInfo)))
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun finishActivation() {
|
||||||
|
val authTokens = visaAuthTokenStorage.get(params.scanResponse.card.cardId)
|
||||||
|
?: error("Auth tokens are not found. This should not happen.")
|
||||||
|
|
||||||
|
val newTokens = runCatching {
|
||||||
|
visaAuthRepository.refreshAccessTokens(authTokens.refreshToken)
|
||||||
|
}.getOrElse {
|
||||||
|
// TODO show alert
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val userWallet = createUserWallet(params.scanResponse, newTokens)
|
||||||
|
userWalletsListManager.save(userWallet)
|
||||||
|
visaAuthTokenStorage.remove(params.scanResponse.card.cardId)
|
||||||
|
otpStorage.removeOTP(params.scanResponse.card.cardId)
|
||||||
|
|
||||||
|
onDone.emit(Params.DoneEvent.Activated)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun createUserWallet(scanResponse: ScanResponse, authTokens: VisaAuthTokens): UserWallet =
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
val newActivationStatus = VisaCardActivationStatus.Activated(visaAuthTokens = authTokens)
|
||||||
|
|
||||||
|
requireNotNull(
|
||||||
|
value = UserWalletBuilder(
|
||||||
|
scanResponse.copy(visaCardActivationStatus = newActivationStatus),
|
||||||
|
generateWalletNameUseCase,
|
||||||
|
).build(),
|
||||||
|
lazyMessage = { "User wallet not created" },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import com.tangem.core.decompose.context.AppComponentContext
|
||||||
import com.tangem.core.decompose.model.getOrCreateModel
|
import com.tangem.core.decompose.model.getOrCreateModel
|
||||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
import com.tangem.domain.visa.model.VisaActivationOrderInfo
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent
|
import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model.OnboardingVisaOtherWalletModel
|
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model.OnboardingVisaOtherWalletModel
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.OnboardingVisaOtherWallet
|
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.OnboardingVisaOtherWallet
|
||||||
|
|
@ -27,7 +28,7 @@ internal class OnboardingVisaOtherWalletComponent(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
model.onDone
|
model.onDone
|
||||||
.onEach { params.onDone() }
|
.onEach { params.onDone(it) }
|
||||||
.launchIn(componentScope)
|
.launchIn(componentScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,6 +51,6 @@ internal class OnboardingVisaOtherWalletComponent(
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
val childParams: DefaultOnboardingVisaComponent.ChildParams,
|
val childParams: DefaultOnboardingVisaComponent.ChildParams,
|
||||||
val onDone: () -> Unit,
|
val onDone: (VisaActivationOrderInfo) -> Unit,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ 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.navigation.share.ShareManager
|
import com.tangem.core.navigation.share.ShareManager
|
||||||
import com.tangem.core.navigation.url.UrlOpener
|
import com.tangem.core.navigation.url.UrlOpener
|
||||||
|
import com.tangem.domain.visa.model.VisaActivationOrderInfo
|
||||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||||
import com.tangem.domain.visa.model.VisaCardId
|
import com.tangem.domain.visa.model.VisaCardId
|
||||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||||
|
|
@ -40,7 +41,7 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor(
|
||||||
private val _uiState = MutableStateFlow(getInitialState())
|
private val _uiState = MutableStateFlow(getInitialState())
|
||||||
|
|
||||||
val uiState = _uiState.asStateFlow()
|
val uiState = _uiState.asStateFlow()
|
||||||
val onDone = MutableSharedFlow<Unit>()
|
val onDone = MutableSharedFlow<VisaActivationOrderInfo>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
|
|
@ -50,7 +51,7 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor(
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|
||||||
if (result is VisaActivationRemoteState.WaitingPinCode) {
|
if (result is VisaActivationRemoteState.WaitingPinCode) {
|
||||||
onDone.emit(Unit)
|
onDone.emit(result.activationOrderInfo)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
package com.tangem.features.onboarding.v2.visa.impl.model
|
package com.tangem.features.onboarding.v2.visa.impl.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import com.arkivanov.decompose.ExperimentalDecomposeApi
|
||||||
import com.arkivanov.decompose.router.stack.StackNavigation
|
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||||
import com.arkivanov.decompose.router.stack.pop
|
import com.arkivanov.decompose.router.stack.pop
|
||||||
import com.arkivanov.decompose.router.stack.push
|
import com.arkivanov.decompose.router.stack.push
|
||||||
|
import com.arkivanov.decompose.router.stack.pushNew
|
||||||
import com.tangem.core.decompose.di.ComponentScoped
|
import com.tangem.core.decompose.di.ComponentScoped
|
||||||
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
|
||||||
|
|
@ -16,13 +18,16 @@ import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||||
import com.tangem.features.onboarding.v2.visa.api.OnboardingVisaComponent
|
import com.tangem.features.onboarding.v2.visa.api.OnboardingVisaComponent
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.OnboardingVisaInnerNavigationState
|
import com.tangem.features.onboarding.v2.visa.impl.OnboardingVisaInnerNavigationState
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.OnboardingVisaChooseWalletComponent
|
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.OnboardingVisaChooseWalletComponent
|
||||||
|
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent
|
import com.tangem.features.onboarding.v2.visa.impl.common.ActivationReadyEvent
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.common.PreparationDataForApprove
|
import com.tangem.features.onboarding.v2.visa.impl.common.PreparationDataForApprove
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute
|
import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute
|
||||||
import com.tangem.features.onboarding.v2.visa.impl.route.stepNum
|
import com.tangem.features.onboarding.v2.visa.impl.route.stepNum
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|
@ -49,6 +54,8 @@ internal class OnboardingVisaModel @Inject constructor(
|
||||||
|
|
||||||
val innerNavigationState = _innerNavigationState.asStateFlow()
|
val innerNavigationState = _innerNavigationState.asStateFlow()
|
||||||
|
|
||||||
|
val onDone = MutableSharedFlow<Unit>()
|
||||||
|
|
||||||
fun updateStepForNewRoute(route: OnboardingVisaRoute) {
|
fun updateStepForNewRoute(route: OnboardingVisaRoute) {
|
||||||
_innerNavigationState.value = innerNavigationState.value.copy(
|
_innerNavigationState.value = innerNavigationState.value.copy(
|
||||||
stackSize = route.stepNum(),
|
stackSize = route.stepNum(),
|
||||||
|
|
@ -77,6 +84,18 @@ internal class OnboardingVisaModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalDecomposeApi::class)
|
||||||
|
fun navigateFromInProgress(event: OnboardingVisaInProgressComponent.Params.DoneEvent) {
|
||||||
|
when (event) {
|
||||||
|
OnboardingVisaInProgressComponent.Params.DoneEvent.Activated -> {
|
||||||
|
modelScope.launch { onDone.emit(Unit) }
|
||||||
|
}
|
||||||
|
is OnboardingVisaInProgressComponent.Params.DoneEvent.NavigateTo -> {
|
||||||
|
stackNavigation.pushNew(event.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun navigateFromChooseWallet(
|
fun navigateFromChooseWallet(
|
||||||
route: OnboardingVisaRoute.ChooseWallet,
|
route: OnboardingVisaRoute.ChooseWallet,
|
||||||
event: OnboardingVisaChooseWalletComponent.Params.Event,
|
event: OnboardingVisaChooseWalletComponent.Params.Event,
|
||||||
|
|
@ -115,7 +134,7 @@ internal class OnboardingVisaModel @Inject constructor(
|
||||||
is OnboardingVisaRoute.WelcomeBack,
|
is OnboardingVisaRoute.WelcomeBack,
|
||||||
is OnboardingVisaRoute.Welcome,
|
is OnboardingVisaRoute.Welcome,
|
||||||
is OnboardingVisaRoute.ChooseWallet,
|
is OnboardingVisaRoute.ChooseWallet,
|
||||||
OnboardingVisaRoute.InProgress,
|
is OnboardingVisaRoute.InProgress,
|
||||||
is OnboardingVisaRoute.PinCode,
|
is OnboardingVisaRoute.PinCode,
|
||||||
-> {
|
-> {
|
||||||
}
|
}
|
||||||
|
|
@ -123,22 +142,27 @@ internal class OnboardingVisaModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeRoute(): OnboardingVisaRoute {
|
private fun initializeRoute(): OnboardingVisaRoute {
|
||||||
val activationStatus = params.scanResponse.visaCardActivationStatus
|
return when (val activationStatus = params.scanResponse.visaCardActivationStatus) {
|
||||||
|
|
||||||
return when (activationStatus) {
|
|
||||||
is VisaCardActivationStatus.ActivationStarted -> {
|
is VisaCardActivationStatus.ActivationStarted -> {
|
||||||
when (val remoteState = activationStatus.remoteState) {
|
when (val remoteState = activationStatus.remoteState) {
|
||||||
is VisaActivationRemoteState.CardWalletSignatureRequired -> OnboardingVisaRoute.WelcomeBack(
|
is VisaActivationRemoteState.CardWalletSignatureRequired -> {
|
||||||
activationInput = activationStatus.activationInput,
|
OnboardingVisaRoute.WelcomeBack(
|
||||||
dataToSignByCardWalletRequest = remoteState.request,
|
activationInput = activationStatus.activationInput,
|
||||||
)
|
dataToSignByCardWalletRequest = remoteState.request,
|
||||||
is VisaActivationRemoteState.CustomerWalletSignatureRequired ->
|
)
|
||||||
|
}
|
||||||
|
is VisaActivationRemoteState.CustomerWalletSignatureRequired -> {
|
||||||
remoteState.getRoute(activationStatus)
|
remoteState.getRoute(activationStatus)
|
||||||
VisaActivationRemoteState.PaymentAccountDeploying -> OnboardingVisaRoute.InProgress // TODO
|
}
|
||||||
VisaActivationRemoteState.WaitingForActivationFinishing -> OnboardingVisaRoute.InProgress // TODO
|
VisaActivationRemoteState.PaymentAccountDeploying -> {
|
||||||
is VisaActivationRemoteState.WaitingPinCode -> OnboardingVisaRoute.PinCode(
|
OnboardingVisaRoute.InProgress(from = OnboardingVisaRoute.InProgress.From.Approve)
|
||||||
activationOrderInfo = remoteState.activationOrderInfo,
|
}
|
||||||
)
|
VisaActivationRemoteState.WaitingForActivationFinishing -> {
|
||||||
|
OnboardingVisaRoute.InProgress(from = OnboardingVisaRoute.InProgress.From.PinCode)
|
||||||
|
}
|
||||||
|
is VisaActivationRemoteState.WaitingPinCode -> {
|
||||||
|
OnboardingVisaRoute.PinCode(activationOrderInfo = remoteState.activationOrderInfo)
|
||||||
|
}
|
||||||
VisaActivationRemoteState.Activated,
|
VisaActivationRemoteState.Activated,
|
||||||
VisaActivationRemoteState.BlockedForActivation,
|
VisaActivationRemoteState.BlockedForActivation,
|
||||||
-> error("Activation status is not correct for onboarding flow")
|
-> error("Activation status is not correct for onboarding flow")
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,13 @@ internal sealed class OnboardingVisaRoute {
|
||||||
) : OnboardingVisaRoute()
|
) : OnboardingVisaRoute()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object InProgress : OnboardingVisaRoute()
|
data class InProgress(
|
||||||
|
val from: From,
|
||||||
|
) : OnboardingVisaRoute() {
|
||||||
|
enum class From {
|
||||||
|
Approve, PinCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PinCode(val activationOrderInfo: VisaActivationOrderInfo) : OnboardingVisaRoute()
|
data class PinCode(val activationOrderInfo: VisaActivationOrderInfo) : OnboardingVisaRoute()
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,13 @@ internal fun OnboardingVisaRoute.stepNum() = when (this) {
|
||||||
is OnboardingVisaRoute.ChooseWallet -> 3
|
is OnboardingVisaRoute.ChooseWallet -> 3
|
||||||
is OnboardingVisaRoute.OtherWalletApproveOption -> 4
|
is OnboardingVisaRoute.OtherWalletApproveOption -> 4
|
||||||
is OnboardingVisaRoute.TangemWalletApproveOption -> 4
|
is OnboardingVisaRoute.TangemWalletApproveOption -> 4
|
||||||
OnboardingVisaRoute.InProgress -> 5
|
|
||||||
is OnboardingVisaRoute.PinCode -> 6
|
is OnboardingVisaRoute.PinCode -> 6
|
||||||
|
is OnboardingVisaRoute.InProgress -> {
|
||||||
|
when (this.from) {
|
||||||
|
OnboardingVisaRoute.InProgress.From.Approve -> 5
|
||||||
|
OnboardingVisaRoute.InProgress.From.PinCode -> 7
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal const val ONBOARDING_VISA_STEPS_COUNT = 7
|
internal const val ONBOARDING_VISA_STEPS_COUNT = 8
|
||||||
|
|
@ -6,7 +6,7 @@ import com.tangem.core.ui.extensions.TextReference
|
||||||
internal fun OnboardingVisaRoute.screenTitle(): TextReference = when (this) {
|
internal fun OnboardingVisaRoute.screenTitle(): TextReference = when (this) {
|
||||||
OnboardingVisaRoute.AccessCode -> TextReference.Str("Access code")
|
OnboardingVisaRoute.AccessCode -> TextReference.Str("Access code")
|
||||||
is OnboardingVisaRoute.ChooseWallet -> TextReference.Str("Account activation")
|
is OnboardingVisaRoute.ChooseWallet -> TextReference.Str("Account activation")
|
||||||
OnboardingVisaRoute.InProgress -> TextReference.Str("In progress")
|
is OnboardingVisaRoute.InProgress -> TextReference.Str("In progress")
|
||||||
is OnboardingVisaRoute.OtherWalletApproveOption -> TextReference.Str("Wallet connection")
|
is OnboardingVisaRoute.OtherWalletApproveOption -> TextReference.Str("Wallet connection")
|
||||||
is OnboardingVisaRoute.PinCode -> TextReference.Str("PIN code")
|
is OnboardingVisaRoute.PinCode -> TextReference.Str("PIN code")
|
||||||
is OnboardingVisaRoute.TangemWalletApproveOption -> TextReference.Str("Wallet connection")
|
is OnboardingVisaRoute.TangemWalletApproveOption -> TextReference.Str("Wallet connection")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue