Updated on 2026-08-14
This commit is contained in:
commit
bd0840770b
29 changed files with 515 additions and 222 deletions
|
|
@ -48,5 +48,9 @@ internal class DefaultVisaAuthTokenStorage @Inject constructor(
|
|||
?.let(tokensAdapter::fromJson)
|
||||
}
|
||||
|
||||
override fun remove(cardId: String) {
|
||||
secureStorage.delete(createKey(cardId))
|
||||
}
|
||||
|
||||
private fun createKey(cardId: String): String = "visa_auth_tokens_$cardId"
|
||||
}
|
||||
|
|
@ -26,7 +26,6 @@ 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.VisaCardActivationResponse
|
||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
|
|
@ -41,6 +40,7 @@ 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.tap.derivationsFinder
|
||||
import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
||||
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ 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.VisaCardActivationResponse
|
||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
|
|
@ -28,6 +27,7 @@ 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.tap.domain.sdk.mocks.MockProvider
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package com.tangem.tap.domain.tasks.visa
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
|
|
@ -9,9 +8,13 @@ import com.tangem.common.core.TangemSdkError
|
|||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.common.map
|
||||
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.hasSavedOTP
|
||||
import com.tangem.domain.common.visa.VisaUtilities
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||
import com.tangem.domain.visa.repository.VisaAuthRepository
|
||||
|
|
@ -21,12 +24,15 @@ import com.tangem.operations.pins.SetUserCodeCommand
|
|||
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 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(
|
||||
|
|
@ -42,7 +48,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
|
||||
private class SessionContext(
|
||||
val visaActivationRepository: VisaActivationRepository,
|
||||
val card: Card,
|
||||
val cardId: String,
|
||||
val session: CardSession,
|
||||
)
|
||||
|
||||
|
|
@ -63,34 +69,42 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
|
||||
val context = SessionContext(
|
||||
visaActivationRepository = visaActivationRepository,
|
||||
card = card,
|
||||
cardId = card.cardId,
|
||||
session = session,
|
||||
)
|
||||
|
||||
return if (challengeToSign != null) {
|
||||
context.signAuthorizationChallenge(challengeToSign)
|
||||
} else {
|
||||
val activationOrder = runCatching { visaActivationRepository.getActivationOrderToSign() }
|
||||
.getOrElse {
|
||||
return CompletionResult.Failure(TangemSdkError.Underlying(it.message ?: ""))
|
||||
}
|
||||
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)
|
||||
context.signOrder(activationOrder)
|
||||
}
|
||||
}
|
||||
Timber.i("VisaCardActivationTask all time: ${timedResult.duration}")
|
||||
return timedResult.value
|
||||
}
|
||||
|
||||
private suspend fun SessionContext.signAuthorizationChallenge(
|
||||
challengeToSign: VisaAuthChallenge.Card,
|
||||
): CompletionResult<VisaCardActivationResponse> {
|
||||
val attestationCommand = AttestCardKeyCommand(challenge = challengeToSign.challenge.hexToBytes())
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
attestationCommand.run(session = session) { attestationResponse ->
|
||||
continuation.resume(attestationResponse)
|
||||
val attestationCommand = AttestCardKeyCommand(challenge = CryptoUtils.generateRandomBytes(length = 16))
|
||||
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
attestationCommand.run(session = session) { attestationResponse ->
|
||||
continuation.resume(attestationResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
Timber.i("AttestCardKeyCommand time: ${timedResult.duration}")
|
||||
|
||||
return when (result) {
|
||||
return when (val result = timedResult.value) {
|
||||
is CompletionResult.Success -> {
|
||||
Timber.i("AttestCardKeyCommand success")
|
||||
processSignedAuthorizationChallenge(
|
||||
signedChallenge = challengeToSign.toSignedChallenge(
|
||||
signedChallenge = result.data.cardSignature.toHexString(),
|
||||
|
|
@ -99,6 +113,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e("AttestCardKeyCommand failure ${result.error}")
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
|
|
@ -135,12 +150,12 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
visaAuthTokenStorage.store(card.cardId, tokens)
|
||||
visaAuthTokenStorage.store(cardId, tokens)
|
||||
|
||||
return visaActivationRepository.getActivationOrderToSign()
|
||||
}
|
||||
|
||||
private suspend fun createWallet(session: CardSession): CompletionResult<Unit> {
|
||||
private suspend fun SessionContext.createWallet(session: CardSession): CompletionResult<Unit> {
|
||||
coroutineScope { ensureActive() }
|
||||
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
|
@ -149,45 +164,55 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
createOTP(session)
|
||||
} else {
|
||||
val createWalletTask = CreateWalletTask(VisaUtilities.mandatoryCurve)
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
createWalletTask.run(session) { createWalletResult ->
|
||||
continuation.resume(createWalletResult)
|
||||
|
||||
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
createWalletTask.run(session) { createWalletResult ->
|
||||
continuation.resume(createWalletResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (result) {
|
||||
Timber.i("CreateWalletTask time: ${timedResult.duration}")
|
||||
|
||||
when (val result = timedResult.value) {
|
||||
is CompletionResult.Success -> {
|
||||
Timber.i("CreateWalletTask success")
|
||||
createOTP(session)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e("CreateWalletTask failure ${result.error}")
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createOTP(session: CardSession): CompletionResult<Unit> {
|
||||
private suspend fun SessionContext.createOTP(session: CardSession): CompletionResult<Unit> {
|
||||
coroutineScope { ensureActive() }
|
||||
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val otp = otpStorage.getOTP(card.cardId)
|
||||
return if (otp != null) {
|
||||
return if (otpStorage.hasSavedOTP(cardId)) {
|
||||
CompletionResult.Success(Unit)
|
||||
} else {
|
||||
val otpCommand = GenerateOTPCommand()
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
otpCommand.run(session) { otpResult ->
|
||||
continuation.resume(otpResult)
|
||||
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
otpCommand.run(session) { otpResult ->
|
||||
continuation.resume(otpResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (result) {
|
||||
Timber.i("GenerateOTPCommand time: ${timedResult.duration}")
|
||||
|
||||
when (val result = timedResult.value) {
|
||||
is CompletionResult.Success -> {
|
||||
otpStorage.saveOTP(card.cardId, result.data.rootOTP)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -195,23 +220,38 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private suspend fun SessionContext.signOrder(order: ActivationOrder): CompletionResult<VisaCardActivationResponse> {
|
||||
val card =
|
||||
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
val wallet =
|
||||
card.wallets.firstOrNull() ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
val task = SignHashCommand(order.hash.hexToBytes(), wallet.publicKey)
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
task.run(session) { signResult ->
|
||||
continuation.resume(signResult)
|
||||
card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve }
|
||||
?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val task = SignHashCommand(
|
||||
hash = order.hash.hexToBytes(),
|
||||
walletPublicKey = wallet.publicKey,
|
||||
derivationPath = VisaUtilities.visaDefaultDerivationPath,
|
||||
)
|
||||
|
||||
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
task.run(session) { signResult ->
|
||||
continuation.resume(signResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return when (result) {
|
||||
Timber.i("SignHashCommand time: ${timedResult.duration}")
|
||||
|
||||
return when (val result = timedResult.value) {
|
||||
is CompletionResult.Success -> {
|
||||
Timber.i("SignHashCommand success")
|
||||
handleSignedOrder(
|
||||
activationOrder = order,
|
||||
response = result.data,
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.e("SignHashCommand failure ${result.error}")
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
|
|
@ -226,35 +266,50 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
signature = response.signature.toHexString(),
|
||||
)
|
||||
|
||||
val otp = otpStorage.getOTP(card.cardId) ?: return CompletionResult.Failure(
|
||||
val otp = otpStorage.getOTP(cardId) ?: return CompletionResult.Failure(
|
||||
TangemSdkError.Underlying(VisaActivationError.MissingRootOTP.message),
|
||||
)
|
||||
|
||||
val activationResponse = VisaCardActivationResponse(
|
||||
signedActivationOrder = signedOrder,
|
||||
rootOTP = VisaRootOTP(otp.toHexString()),
|
||||
)
|
||||
return setupAccessCode().map {
|
||||
val card =
|
||||
session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
return setupAccessCode(session).map { activationResponse }
|
||||
VisaCardActivationResponse(
|
||||
signedActivationOrder = signedOrder,
|
||||
rootOTP = VisaRootOTP(otp.toHexString()),
|
||||
newCardDTO = CardDTO(card),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun SessionContext.setupAccessCode(session: CardSession): CompletionResult<Unit> {
|
||||
private suspend fun SessionContext.setupAccessCode(): CompletionResult<Unit> {
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
if (card.isAccessCodeSet) {
|
||||
return CompletionResult.Success(Unit)
|
||||
}
|
||||
|
||||
Timber.i("Setting access code")
|
||||
|
||||
val task = SetUserCodeCommand.changeAccessCode(accessCode)
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
task.run(session) { setAccessCodeResult ->
|
||||
continuation.resume(setAccessCodeResult)
|
||||
|
||||
val timedResult = RealtimeMonotonicTimeSource.measureTimedValue {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
task.run(session) { setAccessCodeResult ->
|
||||
continuation.resume(setAccessCodeResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return when (result) {
|
||||
Timber.i("SetUserCodeCommand time: ${timedResult.duration}")
|
||||
|
||||
return when (val result = timedResult.value) {
|
||||
is CompletionResult.Success -> {
|
||||
Timber.i("SetUserCodeCommand success")
|
||||
CompletionResult.Success(Unit)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
Timber.i("SetUserCodeCommand failure ${result.error}")
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package com.tangem.tap.domain.visa
|
||||
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.core.CardSession
|
||||
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
|
||||
|
|
@ -31,7 +31,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
|
||||
private class SessionContext(
|
||||
val visaActivationRepository: VisaActivationRepository,
|
||||
val card: Card,
|
||||
val cardId: String,
|
||||
val session: CardSession,
|
||||
)
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
|
||||
val context = SessionContext(
|
||||
visaActivationRepository = visaActivationRepository,
|
||||
card = card,
|
||||
cardId = card.cardId,
|
||||
session = session,
|
||||
)
|
||||
|
||||
|
|
@ -103,6 +103,8 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.mandatoryCurve } ?: run {
|
||||
Timber.e("Failed to find extended public key while handling wallet authorization")
|
||||
return CompletionResult.Failure(
|
||||
|
|
@ -118,7 +120,6 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
}
|
||||
|
||||
Timber.i("Requesting challenge for wallet authorization")
|
||||
// Will be changed later after backend implementation
|
||||
val challengeResponse = runCatching {
|
||||
visaAuthRepository.getCustomerWalletAuthChallenge(
|
||||
cardId = card.cardId,
|
||||
|
|
@ -152,8 +153,6 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
private suspend fun SessionContext.handleWalletAuthorizationTokens(
|
||||
signedChallenge: VisaAuthSignedChallenge,
|
||||
): CompletionResult<VisaCardActivationStatus> {
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
|
||||
val authorizationTokensResponse = runCatching {
|
||||
visaAuthRepository.getAccessTokens(signedChallenge = signedChallenge)
|
||||
}.getOrElse {
|
||||
|
|
@ -164,7 +163,7 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
}
|
||||
|
||||
visaAuthTokenStorage.store(
|
||||
cardId = card.cardId,
|
||||
cardId = cardId,
|
||||
tokens = authorizationTokensResponse,
|
||||
)
|
||||
|
||||
|
|
@ -259,40 +258,26 @@ internal class VisaCardScanHandler @Inject constructor(
|
|||
derivationPath: DerivationPath,
|
||||
nonce: String,
|
||||
): CompletionResult<SignHashResponse> {
|
||||
val signHashCommand = SignHashCommand(publicKey, nonce.toByteArray(), derivationPath)
|
||||
val result = suspendCancellableCoroutine {
|
||||
val signHashCommand = SignHashCommand(
|
||||
hash = nonce.hexToBytes(),
|
||||
walletPublicKey = publicKey,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
return suspendCancellableCoroutine {
|
||||
signHashCommand.run(session) { result ->
|
||||
it.resume(result)
|
||||
}
|
||||
}
|
||||
|
||||
return when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
CompletionResult.Success(result.data)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun SessionContext.signChallengeWithCard(
|
||||
challenge: String,
|
||||
): CompletionResult<AttestCardKeyResponse> {
|
||||
val signHashCommand = AttestCardKeyCommand(challenge = challenge.toByteArray())
|
||||
val result = suspendCancellableCoroutine { continuation ->
|
||||
val signHashCommand = AttestCardKeyCommand(challenge = challenge.hexToBytes())
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
signHashCommand.run(session) { result ->
|
||||
continuation.resume(result)
|
||||
}
|
||||
}
|
||||
|
||||
return when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
CompletionResult.Success(result.data)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
CompletionResult.Failure(result.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.common.timemeasure
|
||||
|
||||
import android.os.SystemClock
|
||||
import kotlin.time.AbstractLongTimeSource
|
||||
import kotlin.time.DurationUnit
|
||||
|
||||
object RealtimeMonotonicTimeSource : AbstractLongTimeSource(DurationUnit.NANOSECONDS) {
|
||||
override fun read(): Long = SystemClock.elapsedRealtimeNanos()
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package com.tangem.datasource.api.visa
|
||||
|
||||
import com.tangem.datasource.api.utils.ReadTimeout
|
||||
import com.tangem.datasource.api.visa.models.response.CardActivationRemoteStateResponse
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
interface TangemVisaApi {
|
||||
|
||||
|
|
@ -10,4 +12,10 @@ interface TangemVisaApi {
|
|||
suspend fun getRemoteActivationStatus(
|
||||
@Header("Authorization") authHeader: String,
|
||||
): CardActivationRemoteStateResponse
|
||||
|
||||
@ReadTimeout(duration = 20, TimeUnit.MINUTES)
|
||||
@GET("activation-status")
|
||||
suspend fun getRemoteActivationStatusLongPoll(
|
||||
@Header("Authorization") authHeader: String,
|
||||
): CardActivationRemoteStateResponse
|
||||
}
|
||||
|
|
@ -187,7 +187,7 @@ internal object NetworkModule {
|
|||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
)
|
||||
).applyTimeoutAnnotations()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -208,7 +208,7 @@ internal object NetworkModule {
|
|||
clientBuilder = {
|
||||
addInterceptor(
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
)
|
||||
).applyTimeoutAnnotations()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,4 +7,6 @@ interface VisaAuthTokenStorage {
|
|||
suspend fun store(cardId: String, tokens: VisaAuthTokens)
|
||||
|
||||
suspend fun get(cardId: String): VisaAuthTokens?
|
||||
|
||||
fun remove(cardId: String)
|
||||
}
|
||||
|
|
@ -44,17 +44,13 @@ internal fun OkHttpClient.Builder.applyTimeoutAnnotations(): OkHttpClient.Builde
|
|||
val readTimeout = tag?.method()?.getAnnotation(ReadTimeout::class.java)
|
||||
val writeTimeout = tag?.method()?.getAnnotation(WriteTimeout::class.java)
|
||||
|
||||
chain
|
||||
.apply {
|
||||
connectionTimeout?.let { withConnectTimeout(timeout = it.duration, unit = it.unit) }
|
||||
}
|
||||
.apply {
|
||||
readTimeout?.let { withReadTimeout(timeout = it.duration, unit = it.unit) }
|
||||
}
|
||||
.apply {
|
||||
writeTimeout?.let { withWriteTimeout(timeout = it.duration, unit = it.unit) }
|
||||
}
|
||||
.proceed(request)
|
||||
chain.run {
|
||||
connectionTimeout?.let { withConnectTimeout(timeout = it.duration, unit = it.unit) } ?: this
|
||||
}.run {
|
||||
readTimeout?.let { withReadTimeout(timeout = it.duration, unit = it.unit) } ?: this
|
||||
}.run {
|
||||
writeTimeout?.let { withWriteTimeout(timeout = it.duration, unit = it.unit) } ?: this
|
||||
}.proceed(request)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.data.visa
|
||||
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.data.visa.converter.VisaActivationStatusConverter
|
||||
import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider
|
||||
import com.tangem.datasource.api.visa.TangemVisaApi
|
||||
import com.tangem.domain.visa.model.ActivationOrder
|
||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||
|
|
@ -11,21 +14,32 @@ import dagger.assisted.AssistedFactory
|
|||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
internal class DefaultVisaActivationRepository @AssistedInject constructor(
|
||||
@Assisted private val cardId: String,
|
||||
private val visaApi: TangemVisaApi,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
private val visaActivationStatusConverter: VisaActivationStatusConverter,
|
||||
private val visaAuthProvider: TangemVisaAuthProvider,
|
||||
) : VisaActivationRepository {
|
||||
|
||||
override suspend fun getActivationRemoteState(): VisaActivationRemoteState = withContext(dispatcherProvider.io) {
|
||||
visaActivationStatusConverter.convert(visaApi.getRemoteActivationStatus(cardId))
|
||||
// visaActivationStatusConverter.convert(visaApi.getRemoteActivationStatus(visaAuthProvider.getAuthHeader(cardId)))
|
||||
VisaActivationRemoteState.CardWalletSignatureRequired // mock
|
||||
// TODO implement refreshing access token if it's expired
|
||||
}
|
||||
|
||||
override suspend fun getActivationOrderToSign(): ActivationOrder {
|
||||
return ActivationOrder("TODO implement")
|
||||
override suspend fun getActivationRemoteStateLongPoll(): VisaActivationRemoteState =
|
||||
withContext(dispatcherProvider.io) {
|
||||
// visaActivationStatusConverter.convert(
|
||||
// visaApi.getRemoteActivationStatusLongPoll(visaAuthProvider.getAuthHeader(cardId)),
|
||||
// )
|
||||
|
||||
VisaActivationRemoteState.WaitingPinCode
|
||||
}
|
||||
|
||||
override suspend fun getActivationOrderToSign(): ActivationOrder = withContext(dispatcherProvider.io) {
|
||||
ActivationOrder(CryptoUtils.generateRandomBytes(length = 32).toHexString())
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.data.visa
|
||||
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.crypto.CryptoUtils
|
||||
import com.tangem.datasource.api.visa.TangemVisaAuthApi
|
||||
import com.tangem.domain.visa.model.VisaAuthChallenge
|
||||
import com.tangem.domain.visa.model.VisaAuthSession
|
||||
|
|
@ -10,6 +12,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
internal class DefaultVisaAuthRepository @Inject constructor(
|
||||
private val visaAuthApi: TangemVisaAuthApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -17,14 +20,19 @@ internal class DefaultVisaAuthRepository @Inject constructor(
|
|||
|
||||
override suspend fun getCardAuthChallenge(cardId: String, cardPublicKey: String): VisaAuthChallenge.Card =
|
||||
withContext(dispatchers.io) {
|
||||
val response = visaAuthApi.generateNonceByCard(
|
||||
cardId = cardId,
|
||||
cardPublicKey = cardPublicKey,
|
||||
)
|
||||
// val response = visaAuthApi.generateNonceByCard(
|
||||
// cardId = cardId,
|
||||
// cardPublicKey = cardPublicKey,
|
||||
// )
|
||||
//
|
||||
// VisaAuthChallenge.Card(
|
||||
// challenge = response.nonce,
|
||||
// session = VisaAuthSession(response.sessionId),
|
||||
// )
|
||||
|
||||
VisaAuthChallenge.Card(
|
||||
challenge = response.nonce,
|
||||
session = VisaAuthSession(response.sessionId),
|
||||
challenge = CryptoUtils.generateRandomBytes(length = 16).toHexString(),
|
||||
session = VisaAuthSession("session"),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -32,39 +40,47 @@ internal class DefaultVisaAuthRepository @Inject constructor(
|
|||
cardId: String,
|
||||
walletPublicKey: String,
|
||||
): VisaAuthChallenge.Wallet = withContext(dispatchers.io) {
|
||||
val response = visaAuthApi.generateNonceByWalletAddress(
|
||||
customerId = cardId,
|
||||
customerWalletAddress = walletPublicKey,
|
||||
)
|
||||
|
||||
// val response = visaAuthApi.generateNonceByWalletAddress(
|
||||
// customerId = cardId,
|
||||
// customerWalletAddress = walletPublicKey,
|
||||
// )
|
||||
//
|
||||
// VisaAuthChallenge.Wallet(
|
||||
// challenge = response.nonce,
|
||||
// session = VisaAuthSession(response.sessionId),
|
||||
// )
|
||||
VisaAuthChallenge.Wallet(
|
||||
challenge = response.nonce,
|
||||
session = VisaAuthSession(response.sessionId),
|
||||
challenge = CryptoUtils.generateRandomBytes(length = 32).toHexString(),
|
||||
session = VisaAuthSession("session"),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getAccessTokens(signedChallenge: VisaAuthSignedChallenge): VisaAuthTokens =
|
||||
withContext(dispatchers.io) {
|
||||
val response = when (signedChallenge) {
|
||||
is VisaAuthSignedChallenge.ByCardPublicKey -> {
|
||||
visaAuthApi.getAccessToken(
|
||||
sessionId = signedChallenge.challenge.session.sessionId,
|
||||
signature = signedChallenge.signature,
|
||||
salt = signedChallenge.salt,
|
||||
)
|
||||
}
|
||||
is VisaAuthSignedChallenge.ByWallet -> {
|
||||
visaAuthApi.getAccessToken(
|
||||
sessionId = signedChallenge.challenge.session.sessionId,
|
||||
signature = signedChallenge.signature,
|
||||
salt = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// val response = when (signedChallenge) {
|
||||
// is VisaAuthSignedChallenge.ByCardPublicKey -> {
|
||||
// visaAuthApi.getAccessToken(
|
||||
// sessionId = signedChallenge.challenge.session.sessionId,
|
||||
// signature = signedChallenge.signature,
|
||||
// salt = signedChallenge.salt,
|
||||
// )
|
||||
// }
|
||||
// is VisaAuthSignedChallenge.ByWallet -> {
|
||||
// visaAuthApi.getAccessToken(
|
||||
// sessionId = signedChallenge.challenge.session.sessionId,
|
||||
// signature = signedChallenge.signature,
|
||||
// salt = null,
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// VisaAuthTokens(
|
||||
// accessToken = response.accessToken,
|
||||
// refreshToken = response.refreshToken,
|
||||
// )
|
||||
VisaAuthTokens(
|
||||
accessToken = response.accessToken,
|
||||
refreshToken = response.refreshToken,
|
||||
accessToken = "accessToken",
|
||||
refreshToken = "refreshToken",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,8 @@ object VisaUtilities {
|
|||
|
||||
val visaBlockchain = Blockchain.Polygon
|
||||
|
||||
val visaDefaultDerivationPath = visaBlockchain.derivationPath(DerivationStyle.V3)
|
||||
val visaDefaultDerivationPath
|
||||
get() = visaBlockchain.derivationPath(DerivationStyle.V3)
|
||||
|
||||
fun visaDefaultDerivationPath(style: DerivationStyle) = visaBlockchain.derivationPath(style)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.domain.visa.model
|
||||
|
||||
data class VisaCardActivationResponse(
|
||||
val signedActivationOrder: SignedActivationOrder,
|
||||
val rootOTP: VisaRootOTP,
|
||||
)
|
||||
|
|
@ -7,6 +7,8 @@ interface VisaActivationRepository {
|
|||
|
||||
suspend fun getActivationRemoteState(): VisaActivationRemoteState
|
||||
|
||||
suspend fun getActivationRemoteStateLongPoll(): VisaActivationRemoteState
|
||||
|
||||
suspend fun getActivationOrderToSign(): ActivationOrder
|
||||
|
||||
interface Factory {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import com.tangem.features.onboarding.v2.visa.impl.model.OnboardingVisaModel
|
|||
import com.tangem.features.onboarding.v2.visa.impl.route.ONBOARDING_VISA_STEPS_COUNT
|
||||
import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute
|
||||
import com.tangem.features.onboarding.v2.visa.impl.route.screenTitle
|
||||
import com.tangem.features.onboarding.v2.visa.impl.route.stepNum
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -44,10 +43,6 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
|||
|
||||
private val model: OnboardingVisaModel = getOrCreateModel(params)
|
||||
|
||||
private val innerNavigationState = instanceKeeper.getOrCreateSimple(key = "innerNavigationState") {
|
||||
MutableStateFlow(OnboardingVisaInnerNavigationState(stackSize = model.initialStepNum))
|
||||
}
|
||||
|
||||
private val currentChildBackEventHandle = instanceKeeper.getOrCreateSimple(key = "currentChildBackEventHandle") {
|
||||
MutableSharedFlow<Unit>()
|
||||
}
|
||||
|
|
@ -57,12 +52,10 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
|||
parentBackEvent = currentChildBackEventHandle,
|
||||
)
|
||||
|
||||
private val stackNavigation = StackNavigation<OnboardingVisaRoute>()
|
||||
|
||||
private val childStack: Value<ChildStack<OnboardingVisaRoute, ComposableContentComponent>> =
|
||||
childStack(
|
||||
key = "innerStack",
|
||||
source = stackNavigation,
|
||||
source = model.stackNavigation,
|
||||
serializer = null,
|
||||
initialConfiguration = model.initialRoute,
|
||||
handleBackButton = true,
|
||||
|
|
@ -75,7 +68,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
|||
)
|
||||
|
||||
override val innerNavigation: InnerNavigation = object : InnerNavigation {
|
||||
override val state: StateFlow<InnerNavigationState> = innerNavigationState
|
||||
override val state: StateFlow<InnerNavigationState> = model.innerNavigationState
|
||||
|
||||
override fun pop(onComplete: (Boolean) -> Unit) {
|
||||
if (childStack.value.active.configuration is OnboardingVisaRoute.AccessCode) {
|
||||
|
|
@ -91,7 +84,7 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
|||
childStack.observe(lifecycle) { stack ->
|
||||
val currentRoute = stack.active.configuration
|
||||
params.titleProvider.changeTitle(currentRoute.screenTitle())
|
||||
innerNavigationState.update { it.copy(stackSize = currentRoute.stepNum()) }
|
||||
model.updateStepForNewRoute(currentRoute)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -106,68 +99,52 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
|||
params = OnboardingVisaWelcomeComponent.Params(
|
||||
isWelcomeBack = route.isWelcomeBack,
|
||||
childParams = childParams,
|
||||
onDone = { stackNavigation.push(OnboardingVisaRoute.AccessCode) },
|
||||
onDone = { model.navigateFromWelcome(route) },
|
||||
),
|
||||
)
|
||||
OnboardingVisaRoute.AccessCode -> OnboardingVisaAccessCodeComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = OnboardingVisaAccessCodeComponent.Params(
|
||||
childParams = childParams,
|
||||
onDone = {
|
||||
stackNavigation.push(
|
||||
if (it.walletFound) {
|
||||
OnboardingVisaRoute.TangemWalletApproveOption(
|
||||
visaDataForApprove = it.visaDataForApprove,
|
||||
allowNavigateBack = false,
|
||||
)
|
||||
} else {
|
||||
OnboardingVisaRoute.ChooseWallet(it.visaDataForApprove)
|
||||
},
|
||||
)
|
||||
},
|
||||
onDone = { model.navigateFromAccessCode(it) },
|
||||
),
|
||||
config = OnboardingVisaAccessCodeComponent.Config(
|
||||
scanResponse = params.scanResponse,
|
||||
scanResponse = model.currentScanResponse.value,
|
||||
),
|
||||
)
|
||||
is OnboardingVisaRoute.ChooseWallet -> OnboardingVisaChooseWalletComponent(
|
||||
appComponentContext = factoryContext,
|
||||
params = OnboardingVisaChooseWalletComponent.Params(
|
||||
childParams = childParams,
|
||||
onEvent = { event ->
|
||||
stackNavigation.push(
|
||||
when (event) {
|
||||
OnboardingVisaChooseWalletComponent.Params.Event.TangemWallet ->
|
||||
OnboardingVisaRoute.TangemWalletApproveOption(
|
||||
visaDataForApprove = route.visaDataForApprove,
|
||||
allowNavigateBack = true,
|
||||
)
|
||||
OnboardingVisaChooseWalletComponent.Params.Event.OtherWallet ->
|
||||
OnboardingVisaRoute.OtherWalletApproveOption(route.visaDataForApprove)
|
||||
},
|
||||
)
|
||||
},
|
||||
onEvent = { event -> model.navigateFromChooseWallet(route, event) },
|
||||
),
|
||||
)
|
||||
OnboardingVisaRoute.InProgress -> OnboardingVisaInProgressComponent(
|
||||
appComponentContext = factoryContext,
|
||||
config = OnboardingVisaInProgressComponent.Config(
|
||||
scanResponse = model.currentScanResponse.value,
|
||||
),
|
||||
params = OnboardingVisaInProgressComponent.Params(
|
||||
childParams = childParams,
|
||||
onDone = { stackNavigation.push(OnboardingVisaRoute.PinCode) },
|
||||
onDone = { model.stackNavigation.push(OnboardingVisaRoute.PinCode) },
|
||||
),
|
||||
)
|
||||
is OnboardingVisaRoute.OtherWalletApproveOption -> OnboardingVisaOtherWalletComponent(
|
||||
appComponentContext = factoryContext,
|
||||
config = OnboardingVisaOtherWalletComponent.Config(
|
||||
scanResponse = model.currentScanResponse.value,
|
||||
visaDataForApprove = route.visaDataForApprove,
|
||||
),
|
||||
params = OnboardingVisaOtherWalletComponent.Params(
|
||||
childParams = childParams,
|
||||
onDone = { stackNavigation.push(OnboardingVisaRoute.InProgress) },
|
||||
onDone = { model.stackNavigation.push(OnboardingVisaRoute.PinCode) },
|
||||
),
|
||||
)
|
||||
OnboardingVisaRoute.PinCode -> OnboardingVisaPinCodeComponent(
|
||||
appComponentContext = factoryContext,
|
||||
config = OnboardingVisaPinCodeComponent.Config(
|
||||
scanResponse = model.currentScanResponse.value,
|
||||
),
|
||||
params = OnboardingVisaPinCodeComponent.Params(
|
||||
childParams = childParams,
|
||||
onDone = { params.onDone() },
|
||||
|
|
@ -180,36 +157,16 @@ internal class DefaultOnboardingVisaComponent @AssistedInject constructor(
|
|||
),
|
||||
params = OnboardingVisaApproveComponent.Params(
|
||||
childParams = childParams,
|
||||
onDone = { stackNavigation.push(OnboardingVisaRoute.InProgress) },
|
||||
onDone = { model.stackNavigation.push(OnboardingVisaRoute.InProgress) },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onChildBack() {
|
||||
if (childStack.value.backStack.size == 1) {
|
||||
// TODO show dialog
|
||||
return
|
||||
}
|
||||
|
||||
when (val route = childStack.value.active.configuration) {
|
||||
OnboardingVisaRoute.AccessCode,
|
||||
is OnboardingVisaRoute.OtherWalletApproveOption,
|
||||
-> stackNavigation.pop()
|
||||
is OnboardingVisaRoute.TangemWalletApproveOption -> {
|
||||
if (route.allowNavigateBack) {
|
||||
stackNavigation.pop()
|
||||
}
|
||||
}
|
||||
|
||||
is OnboardingVisaRoute.Welcome,
|
||||
is OnboardingVisaRoute.ChooseWallet,
|
||||
OnboardingVisaRoute.InProgress,
|
||||
OnboardingVisaRoute.PinCode,
|
||||
-> {
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun onChildBack() = model.onChildBack(
|
||||
currentRoute = childStack.value.active.configuration,
|
||||
lastRoute = childStack.value.backStack.size == 1,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||
import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent
|
||||
|
|
@ -44,7 +43,7 @@ internal class OnboardingVisaAccessCodeComponent(
|
|||
|
||||
BackHandler(onBack = model::onBack)
|
||||
|
||||
DisableScreenshotsDisposableEffect()
|
||||
// DisableScreenshotsDisposableEffect()
|
||||
|
||||
OnboardingVisaAccessCode(state, modifier)
|
||||
}
|
||||
|
|
@ -61,5 +60,6 @@ internal class OnboardingVisaAccessCodeComponent(
|
|||
data class DoneEvent(
|
||||
val visaDataForApprove: VisaDataForApprove,
|
||||
val walletFound: Boolean,
|
||||
val newScanResponse: ScanResponse,
|
||||
)
|
||||
}
|
||||
|
|
@ -14,7 +14,10 @@ import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.OnboardingVi
|
|||
import com.tangem.features.onboarding.v2.visa.impl.child.accesscode.ui.state.OnboardingVisaAccessCodeUM
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -39,6 +42,8 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
|||
val onDone = MutableSharedFlow<OnboardingVisaAccessCodeComponent.DoneEvent>()
|
||||
|
||||
fun onBack() {
|
||||
if (uiState.value.buttonLoading) return
|
||||
|
||||
when (uiState.value.step) {
|
||||
OnboardingVisaAccessCodeUM.Step.Enter -> modelScope.launch { onBack.emit(Unit) }
|
||||
OnboardingVisaAccessCodeUM.Step.ReEnter ->
|
||||
|
|
@ -141,9 +146,12 @@ internal class OnboardingVisaAccessCodeModel @Inject constructor(
|
|||
OnboardingVisaAccessCodeComponent.DoneEvent(
|
||||
visaDataForApprove = VisaDataForApprove(
|
||||
targetAddress = "x9F65354e595284956599F2892fA4A4a87653D6E6",
|
||||
approveHash = "approve hash",
|
||||
approveHash = "48b55c482123a10ad9022f9f4c5dd95c",
|
||||
),
|
||||
walletFound = false, // TODO
|
||||
newScanResponse = params.scanResponse.copy(
|
||||
card = result.data.newCardDTO,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import androidx.compose.ui.Modifier
|
|||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
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.ui.OnboardingVisaInProgress
|
||||
|
|
@ -15,10 +16,11 @@ import kotlinx.coroutines.flow.onEach
|
|||
|
||||
internal class OnboardingVisaInProgressComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
config: Config,
|
||||
private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OnboardingVisaInProgressModel = getOrCreateModel()
|
||||
private val model: OnboardingVisaInProgressModel = getOrCreateModel(config)
|
||||
|
||||
init {
|
||||
model.onDone
|
||||
|
|
@ -35,6 +37,10 @@ internal class OnboardingVisaInProgressComponent(
|
|||
)
|
||||
}
|
||||
|
||||
data class Config(
|
||||
val scanResponse: ScanResponse,
|
||||
)
|
||||
|
||||
data class Params(
|
||||
val childParams: DefaultOnboardingVisaComponent.ChildParams,
|
||||
val onDone: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ package com.tangem.features.onboarding.v2.visa.impl.child.inprogress.model
|
|||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.inprogress.OnboardingVisaInProgressComponent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
|
@ -12,16 +16,29 @@ import javax.inject.Inject
|
|||
@Stable
|
||||
@ComponentScoped
|
||||
internal class OnboardingVisaInProgressModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<OnboardingVisaInProgressComponent.Config>()
|
||||
private val visaActivationRepository = visaActivationRepositoryFactory.create(params.scanResponse.card.cardId)
|
||||
val onDone = MutableSharedFlow<Unit>()
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
// TODO check state
|
||||
delay(timeMillis = 2000)
|
||||
onDone.emit(Unit)
|
||||
while (true) {
|
||||
val result = runCatching {
|
||||
visaActivationRepository.getActivationRemoteStateLongPoll()
|
||||
}.getOrNull()
|
||||
|
||||
if (result == VisaActivationRemoteState.WaitingPinCode) {
|
||||
onDone.emit(Unit)
|
||||
break
|
||||
}
|
||||
|
||||
delay(timeMillis = 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||
import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.model.OnboardingVisaOtherWalletModel
|
||||
|
|
@ -43,6 +44,7 @@ internal class OnboardingVisaOtherWalletComponent(
|
|||
}
|
||||
|
||||
data class Config(
|
||||
val scanResponse: ScanResponse,
|
||||
val visaDataForApprove: VisaDataForApprove,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,18 @@ import androidx.compose.runtime.Stable
|
|||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.navigation.share.ShareManager
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||
import com.tangem.domain.visa.repository.VisaActivationRepository
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.OnboardingVisaOtherWalletComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.otherwallet.ui.state.OnboardingVisaOtherWalletUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
|
|
@ -17,15 +23,36 @@ import javax.inject.Inject
|
|||
internal class OnboardingVisaOtherWalletModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val shareManager: ShareManager,
|
||||
private val visaActivationRepositoryFactory: VisaActivationRepository.Factory,
|
||||
) : Model() {
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private val config = paramsContainer.require<OnboardingVisaOtherWalletComponent.Config>()
|
||||
private val visaActivationRepository = visaActivationRepositoryFactory.create(config.scanResponse.card.cardId)
|
||||
private val _uiState = MutableStateFlow(getInitialState())
|
||||
|
||||
val uiState = _uiState.asStateFlow()
|
||||
val onDone = MutableSharedFlow<Unit>()
|
||||
|
||||
init {
|
||||
modelScope.launch {
|
||||
while (true) {
|
||||
val result = runCatching {
|
||||
visaActivationRepository.getActivationRemoteStateLongPoll()
|
||||
}.getOrNull()
|
||||
|
||||
if (result == VisaActivationRemoteState.WaitingPinCode) {
|
||||
onDone.emit(Unit)
|
||||
break
|
||||
}
|
||||
|
||||
delay(timeMillis = 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialState(): OnboardingVisaOtherWalletUM {
|
||||
return OnboardingVisaOtherWalletUM(
|
||||
onShareClick = ::onShareClicked,
|
||||
|
|
@ -34,10 +61,10 @@ internal class OnboardingVisaOtherWalletModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onShareClicked() {
|
||||
// TODO
|
||||
shareManager.shareText("https://tangem.com/${config.visaDataForApprove.approveHash}")
|
||||
}
|
||||
|
||||
private fun onOpenInBrowserClicked() {
|
||||
// TODO
|
||||
urlOpener.openUrl("https://tangem.com/${config.visaDataForApprove.approveHash}")
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.core.decompose.context.AppComponentContext
|
|||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.core.ui.security.DisableScreenshotsDisposableEffect
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.features.onboarding.v2.visa.impl.DefaultOnboardingVisaComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.model.OnboardingVisaPinCodeModel
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.OnboardingVisaPinCode
|
||||
|
|
@ -17,10 +18,11 @@ import kotlinx.coroutines.launch
|
|||
|
||||
internal class OnboardingVisaPinCodeComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
config: Config,
|
||||
private val params: Params,
|
||||
) : ComposableContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OnboardingVisaPinCodeModel = getOrCreateModel()
|
||||
private val model: OnboardingVisaPinCodeModel = getOrCreateModel(config)
|
||||
|
||||
init {
|
||||
componentScope.launch {
|
||||
|
|
@ -42,6 +44,10 @@ internal class OnboardingVisaPinCodeComponent(
|
|||
)
|
||||
}
|
||||
|
||||
data class Config(
|
||||
val scanResponse: ScanResponse,
|
||||
)
|
||||
|
||||
data class Params(
|
||||
val childParams: DefaultOnboardingVisaComponent.ChildParams,
|
||||
val onDone: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,15 @@ package com.tangem.features.onboarding.v2.visa.impl.child.pincode.model
|
|||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.datasource.local.visa.VisaAuthTokenStorage
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
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.pincode.OnboardingVisaPinCodeComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state.OnboardingVisaPinCodeUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
|
@ -10,14 +19,21 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
@ComponentScoped
|
||||
internal class OnboardingVisaPinCodeModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
|
||||
private val userWalletsListManager: UserWalletsListManager,
|
||||
private val authTokenStorage: VisaAuthTokenStorage,
|
||||
private val otpStorage: VisaAuthTokenStorage,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<OnboardingVisaPinCodeComponent.Config>()
|
||||
private val _uiState = MutableStateFlow(getInitialState())
|
||||
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
|
@ -31,21 +47,61 @@ internal class OnboardingVisaPinCodeModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onPinCodeChange(pin: String) {
|
||||
if (pin.all { it.isDigit() }) _uiState.update { it.copy(pinCode = pin) }
|
||||
if (pin.all { it.isDigit() }) {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
pinCode = pin,
|
||||
submitButtonEnabled = checkPinCode(pin),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSubmitClick() {
|
||||
if (checkPinCode(_uiState.value.pinCode).not()) return
|
||||
|
||||
// TODO
|
||||
modelScope.launch {
|
||||
loading(true)
|
||||
|
||||
modelScope.launch { onDone.emit(Unit) }
|
||||
// TODO
|
||||
// make backend call
|
||||
saveWallet()
|
||||
|
||||
loading(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPinCode(pin: String): Boolean {
|
||||
return pin.length == PIN_CODE_LENGTH
|
||||
}
|
||||
|
||||
private fun loading(state: Boolean) {
|
||||
_uiState.update { it.copy(submitButtonLoading = state) }
|
||||
}
|
||||
|
||||
private suspend fun saveWallet() {
|
||||
val userWallet = createUserWallet(params.scanResponse)
|
||||
userWalletsListManager.save(userWallet)
|
||||
authTokenStorage.remove(params.scanResponse.card.cardId)
|
||||
otpStorage.remove(params.scanResponse.card.cardId)
|
||||
onDone.emit(Unit)
|
||||
}
|
||||
|
||||
private suspend fun createUserWallet(scanResponse: ScanResponse): UserWallet = withContext(dispatchers.io) {
|
||||
val newActivationStatus = VisaCardActivationStatus.Activated(
|
||||
visaAuthTokens = authTokenStorage.get(scanResponse.card.cardId)
|
||||
?: error("Impossible state. Wrong feature implementation"),
|
||||
)
|
||||
|
||||
requireNotNull(
|
||||
value = UserWalletBuilder(
|
||||
scanResponse.copy(visaCardActivationStatus = newActivationStatus),
|
||||
generateWalletNameUseCase,
|
||||
).build(),
|
||||
lazyMessage = { "User wallet not created" },
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val PIN_CODE_LENGTH = 4
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Color.Companion.Transparent
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
|
|
@ -28,9 +27,11 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.PrimaryButton
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.common.ui.navigationButtons.NavigationPrimaryButton
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerH16
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state.OnboardingVisaPinCodeUM
|
||||
|
|
@ -72,13 +73,17 @@ internal fun OnboardingVisaPinCode(state: OnboardingVisaPinCodeUM, modifier: Mod
|
|||
PinCodeSection(state)
|
||||
}
|
||||
|
||||
PrimaryButton(
|
||||
NavigationPrimaryButton(
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
text = "Submit",
|
||||
onClick = state.onSubmitClick,
|
||||
primaryButton = NavigationButton(
|
||||
textReference = TextReference.Str("Submit"),
|
||||
onClick = state.onSubmitClick,
|
||||
showProgress = state.submitButtonLoading,
|
||||
isEnabled = state.submitButtonEnabled,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -154,7 +159,7 @@ private fun PinCode(
|
|||
Box(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = Color(color = 0xFFF5F5F5),
|
||||
color = TangemTheme.colors.field.primary,
|
||||
shape = TangemTheme.shapes.roundedCornersXMedium,
|
||||
)
|
||||
.padding(
|
||||
|
|
|
|||
|
|
@ -3,5 +3,7 @@ package com.tangem.features.onboarding.v2.visa.impl.child.pincode.ui.state
|
|||
internal data class OnboardingVisaPinCodeUM(
|
||||
val pinCode: String = "",
|
||||
val onPinCodeChange: (String) -> Unit = {},
|
||||
val submitButtonLoading: Boolean = false,
|
||||
val submitButtonEnabled: Boolean = true,
|
||||
val onSubmitClick: () -> Unit = {},
|
||||
)
|
||||
|
|
@ -1,13 +1,23 @@
|
|||
package com.tangem.features.onboarding.v2.visa.impl.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||
import com.arkivanov.decompose.router.stack.pop
|
||||
import com.arkivanov.decompose.router.stack.push
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.visa.model.VisaActivationRemoteState
|
||||
import com.tangem.domain.visa.model.VisaCardActivationStatus
|
||||
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.child.accesscode.OnboardingVisaAccessCodeComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.child.choosewallet.OnboardingVisaChooseWalletComponent
|
||||
import com.tangem.features.onboarding.v2.visa.impl.route.OnboardingVisaRoute
|
||||
import com.tangem.features.onboarding.v2.visa.impl.route.stepNum
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
|
|
@ -20,7 +30,107 @@ internal class OnboardingVisaModel @Inject constructor(
|
|||
@Suppress("UnusedPrivateMember")
|
||||
private val params = paramsContainer.require<OnboardingVisaComponent.Params>()
|
||||
|
||||
val initialRoute = OnboardingVisaRoute.Welcome(isWelcomeBack = false)
|
||||
private val _currentScanResponse = MutableStateFlow(params.scanResponse)
|
||||
|
||||
val initialStepNum = initialRoute.stepNum()
|
||||
val initialRoute = initializeRoute()
|
||||
|
||||
val stackNavigation = StackNavigation<OnboardingVisaRoute>()
|
||||
|
||||
private val _innerNavigationState =
|
||||
MutableStateFlow(OnboardingVisaInnerNavigationState(stackSize = initialRoute.stepNum()))
|
||||
|
||||
val currentScanResponse = _currentScanResponse.asStateFlow()
|
||||
|
||||
val innerNavigationState = _innerNavigationState.asStateFlow()
|
||||
|
||||
fun updateStepForNewRoute(route: OnboardingVisaRoute) {
|
||||
_innerNavigationState.value = innerNavigationState.value.copy(
|
||||
stackSize = route.stepNum(),
|
||||
)
|
||||
}
|
||||
|
||||
fun navigateFromWelcome(route: OnboardingVisaRoute.Welcome) {
|
||||
if (route.isWelcomeBack) {
|
||||
val scanResponse = _currentScanResponse.value
|
||||
val activationStatus = scanResponse.visaCardActivationStatus as? VisaCardActivationStatus
|
||||
.ActivationStarted ?: error("Activation status is not correct for welcome back route")
|
||||
|
||||
when (activationStatus.remoteState) {
|
||||
VisaActivationRemoteState.CardWalletSignatureRequired,
|
||||
VisaActivationRemoteState.CustomerWalletSignatureRequired,
|
||||
-> OnboardingVisaRoute.AccessCode
|
||||
VisaActivationRemoteState.WaitingPinCode -> OnboardingVisaRoute.PinCode
|
||||
VisaActivationRemoteState.WaitingForActivationFinishing -> OnboardingVisaRoute.InProgress
|
||||
VisaActivationRemoteState.PaymentAccountDeploying -> OnboardingVisaRoute.InProgress
|
||||
else -> error("Remote state is not correct for welcome back route")
|
||||
}
|
||||
} else {
|
||||
stackNavigation.push(OnboardingVisaRoute.AccessCode)
|
||||
}
|
||||
}
|
||||
|
||||
fun navigateFromAccessCode(result: OnboardingVisaAccessCodeComponent.DoneEvent) {
|
||||
_currentScanResponse.value = result.newScanResponse
|
||||
stackNavigation.push(
|
||||
if (result.walletFound) {
|
||||
OnboardingVisaRoute.TangemWalletApproveOption(
|
||||
visaDataForApprove = result.visaDataForApprove,
|
||||
allowNavigateBack = false,
|
||||
)
|
||||
} else {
|
||||
OnboardingVisaRoute.ChooseWallet(result.visaDataForApprove)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun navigateFromChooseWallet(
|
||||
route: OnboardingVisaRoute.ChooseWallet,
|
||||
event: OnboardingVisaChooseWalletComponent.Params.Event,
|
||||
) {
|
||||
stackNavigation.push(
|
||||
when (event) {
|
||||
OnboardingVisaChooseWalletComponent.Params.Event.TangemWallet ->
|
||||
OnboardingVisaRoute.TangemWalletApproveOption(
|
||||
visaDataForApprove = route.visaDataForApprove,
|
||||
allowNavigateBack = true,
|
||||
)
|
||||
OnboardingVisaChooseWalletComponent.Params.Event.OtherWallet ->
|
||||
OnboardingVisaRoute.OtherWalletApproveOption(route.visaDataForApprove)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun onChildBack(currentRoute: OnboardingVisaRoute, lastRoute: Boolean) {
|
||||
if (lastRoute) {
|
||||
// TODO show dialog
|
||||
return
|
||||
}
|
||||
|
||||
when (currentRoute) {
|
||||
OnboardingVisaRoute.AccessCode,
|
||||
is OnboardingVisaRoute.OtherWalletApproveOption,
|
||||
-> stackNavigation.pop()
|
||||
is OnboardingVisaRoute.TangemWalletApproveOption -> {
|
||||
if (currentRoute.allowNavigateBack) {
|
||||
stackNavigation.pop()
|
||||
}
|
||||
}
|
||||
is OnboardingVisaRoute.Welcome,
|
||||
is OnboardingVisaRoute.ChooseWallet,
|
||||
OnboardingVisaRoute.InProgress,
|
||||
OnboardingVisaRoute.PinCode,
|
||||
-> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeRoute(): OnboardingVisaRoute {
|
||||
val activationStatus = params.scanResponse.visaCardActivationStatus
|
||||
|
||||
return when (activationStatus) {
|
||||
is VisaCardActivationStatus.ActivationStarted -> OnboardingVisaRoute.Welcome(isWelcomeBack = true)
|
||||
is VisaCardActivationStatus.NotStartedActivation -> OnboardingVisaRoute.Welcome(isWelcomeBack = false)
|
||||
else -> error("Visa activation status is not correct for onboarding flow")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,13 +17,13 @@ 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.VisaCardActivationResponse
|
||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||
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.visa.VisaCardActivationResponse
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface TangemSdkManager {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.sdk.api.visa
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.visa.model.SignedActivationOrder
|
||||
import com.tangem.domain.visa.model.VisaRootOTP
|
||||
|
||||
data class VisaCardActivationResponse(
|
||||
val signedActivationOrder: SignedActivationOrder,
|
||||
val rootOTP: VisaRootOTP,
|
||||
val newCardDTO: CardDTO,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue