Updated on 2026-08-14
This commit is contained in:
parent
3ffffa73ec
commit
616386c345
18 changed files with 310 additions and 125 deletions
|
|
@ -2,8 +2,9 @@ package com.tangem.tap.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
import com.tangem.datasource.di.NetworkMoshi
|
||||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||||
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
|
@ -11,7 +12,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
import kotlin.text.encodeToByteArray
|
|
||||||
|
|
||||||
private const val DEFAULT_KEY = "tangem_pay_default_key"
|
private const val DEFAULT_KEY = "tangem_pay_default_key"
|
||||||
private const val ORDER_ID_KEY = "tangem_pay_order_id_key"
|
private const val ORDER_ID_KEY = "tangem_pay_order_id_key"
|
||||||
|
|
@ -19,6 +19,7 @@ private const val ORDER_ID_KEY = "tangem_pay_order_id_key"
|
||||||
@Singleton
|
@Singleton
|
||||||
internal class DefaultTangemPayStorage @Inject constructor(
|
internal class DefaultTangemPayStorage @Inject constructor(
|
||||||
@ApplicationContext applicationContext: Context,
|
@ApplicationContext applicationContext: Context,
|
||||||
|
@NetworkMoshi moshi: Moshi,
|
||||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||||
) : TangemPayStorage {
|
) : TangemPayStorage {
|
||||||
|
|
||||||
|
|
@ -29,14 +30,21 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
||||||
name = "tangem_pay_storage",
|
name = "tangem_pay_storage",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
private val moshi by lazy {
|
|
||||||
Moshi.Builder()
|
|
||||||
.add(KotlinJsonAdapterFactory())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val tokensAdapter by lazy { moshi.adapter(VisaAuthTokens::class.java) }
|
private val tokensAdapter by lazy { moshi.adapter(VisaAuthTokens::class.java) }
|
||||||
|
|
||||||
|
override suspend fun storeCustomerWalletAddress(userWalletId: UserWalletId, customerWalletAddress: String) {
|
||||||
|
withContext(dispatcherProvider.io) {
|
||||||
|
secureStorage.store(key = createCustomerAddressKey(userWalletId), value = customerWalletAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String? {
|
||||||
|
return withContext(dispatcherProvider.io) {
|
||||||
|
secureStorage.getAsString(createCustomerAddressKey(userWalletId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun storeAuthTokens(customerWalletAddress: String, tokens: VisaAuthTokens) =
|
override suspend fun storeAuthTokens(customerWalletAddress: String, tokens: VisaAuthTokens) =
|
||||||
withContext(dispatcherProvider.io) {
|
withContext(dispatcherProvider.io) {
|
||||||
val json = tokensAdapter.toJson(tokens)
|
val json = tokensAdapter.toJson(tokens)
|
||||||
|
|
@ -71,10 +79,14 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
||||||
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clearAll(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
override suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String) =
|
||||||
secureStorage.delete(createKey(customerWalletAddress))
|
withContext(dispatcherProvider.io) {
|
||||||
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
secureStorage.delete(createCustomerAddressKey(userWalletId))
|
||||||
}
|
secureStorage.delete(createKey(customerWalletAddress))
|
||||||
|
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createCustomerAddressKey(userWalletId: UserWalletId): String = userWalletId.stringValue
|
||||||
|
|
||||||
private fun createKey(address: String): String = "${DEFAULT_KEY}_$address"
|
private fun createKey(address: String): String = "${DEFAULT_KEY}_$address"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||||
import com.tangem.sdk.api.TangemSdkManager
|
import com.tangem.sdk.api.TangemSdkManager
|
||||||
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
|
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
|
||||||
import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager
|
import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager
|
||||||
|
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
||||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||||
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -27,6 +28,7 @@ internal class TangemSdkManagerModule {
|
||||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||||
visaCardScanHandler: VisaCardScanHandler,
|
visaCardScanHandler: VisaCardScanHandler,
|
||||||
visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||||
|
tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
||||||
onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||||
): TangemSdkManager {
|
): TangemSdkManager {
|
||||||
return if (BuildConfig.MOCK_DATA_SOURCE) {
|
return if (BuildConfig.MOCK_DATA_SOURCE) {
|
||||||
|
|
@ -37,6 +39,7 @@ internal class TangemSdkManagerModule {
|
||||||
resources = context.resources,
|
resources = context.resources,
|
||||||
visaCardScanHandler = visaCardScanHandler,
|
visaCardScanHandler = visaCardScanHandler,
|
||||||
visaCardActivationTaskFactory = visaCardActivationTaskFactory,
|
visaCardActivationTaskFactory = visaCardActivationTaskFactory,
|
||||||
|
tangemPayChallengeTaskFactory = tangemPayChallengeTaskFactory,
|
||||||
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,17 @@ import com.tangem.core.res.getStringSafe
|
||||||
import com.tangem.crypto.bip39.DefaultMnemonic
|
import com.tangem.crypto.bip39.DefaultMnemonic
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
|
||||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.visa.model.*
|
import com.tangem.domain.visa.model.TangemPayInitialCredentials
|
||||||
|
import com.tangem.domain.visa.model.VisaActivationInput
|
||||||
|
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||||
|
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||||
|
import com.tangem.domain.visa.model.sign
|
||||||
|
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||||
import com.tangem.operations.ScanTask
|
import com.tangem.operations.ScanTask
|
||||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||||
|
|
@ -44,6 +48,7 @@ import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
||||||
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
|
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
|
||||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
||||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||||
|
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
||||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||||
import com.tangem.tap.domain.tasks.visa.VisaCustomerWalletApproveTask
|
import com.tangem.tap.domain.tasks.visa.VisaCustomerWalletApproveTask
|
||||||
import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask
|
import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask
|
||||||
|
|
@ -62,6 +67,7 @@ internal class DefaultTangemSdkManager(
|
||||||
private val resources: Resources,
|
private val resources: Resources,
|
||||||
private val visaCardScanHandler: VisaCardScanHandler,
|
private val visaCardScanHandler: VisaCardScanHandler,
|
||||||
private val visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
private val visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||||
|
private val tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
||||||
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||||
) : TangemSdkManager {
|
) : TangemSdkManager {
|
||||||
|
|
||||||
|
|
@ -511,6 +517,18 @@ internal class DefaultTangemSdkManager(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun tangemPayProduceInitialCredentials(
|
||||||
|
cardId: String,
|
||||||
|
): CompletionResult<TangemPayInitialCredentials> {
|
||||||
|
return coroutineScope {
|
||||||
|
runTaskAsyncReturnOnMain(
|
||||||
|
runnable = tangemPayChallengeTaskFactory.create(coroutineScope = this),
|
||||||
|
cardId = cardId,
|
||||||
|
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -213,5 +213,11 @@ class MockTangemSdkManager(
|
||||||
error("Not implemented")
|
error("Not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun tangemPayProduceInitialCredentials(
|
||||||
|
cardId: String,
|
||||||
|
): CompletionResult<TangemPayInitialCredentials> {
|
||||||
|
error("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
package com.tangem.tap.domain.tasks.visa
|
||||||
|
|
||||||
|
import arrow.core.getOrElse
|
||||||
|
import com.tangem.common.CompletionResult
|
||||||
|
import com.tangem.common.card.CardWallet
|
||||||
|
import com.tangem.common.card.EllipticCurve
|
||||||
|
import com.tangem.common.core.CardSession
|
||||||
|
import com.tangem.common.core.CardSessionRunnable
|
||||||
|
import com.tangem.common.core.CompletionCallback
|
||||||
|
import com.tangem.common.core.TangemSdkError
|
||||||
|
import com.tangem.core.error.ext.tangemError
|
||||||
|
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
|
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||||
|
import com.tangem.domain.visa.datasource.VisaAuthRemoteDataSource
|
||||||
|
import com.tangem.domain.visa.error.VisaActivationError
|
||||||
|
import com.tangem.domain.visa.model.TangemPayInitialCredentials
|
||||||
|
import com.tangem.domain.visa.model.VisaDataToSignByCustomerWallet
|
||||||
|
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||||
|
import com.tangem.domain.visa.model.sign
|
||||||
|
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class TangemPayGenerateAddressAndSignChallengeTask @AssistedInject constructor(
|
||||||
|
@Assisted private val coroutineScope: CoroutineScope,
|
||||||
|
private val dispatchersProvider: CoroutineDispatcherProvider,
|
||||||
|
private val visaAuthRemoteDataSource: VisaAuthRemoteDataSource,
|
||||||
|
) : CardSessionRunnable<TangemPayInitialCredentials> {
|
||||||
|
|
||||||
|
override fun run(session: CardSession, callback: CompletionCallback<TangemPayInitialCredentials>) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
callback(runSuspend(session = session))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun runSuspend(session: CardSession): CompletionResult<TangemPayInitialCredentials> {
|
||||||
|
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
|
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||||
|
?: return CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError)
|
||||||
|
|
||||||
|
val derivationResult = runDerivationTask(session, wallet)
|
||||||
|
val address = when (derivationResult) {
|
||||||
|
is CompletionResult.Failure<*> -> return CompletionResult.Failure(derivationResult.error)
|
||||||
|
is CompletionResult.Success<ExtendedPublicKey> -> generateAddressFromExtendedKey(derivationResult.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
val challenge = withContext(dispatchersProvider.io) {
|
||||||
|
visaAuthRemoteDataSource.getCustomerWalletAuthChallenge(address)
|
||||||
|
}.getOrElse { return CompletionResult.Failure(it.tangemError) }
|
||||||
|
|
||||||
|
val dataToSign = VisaDataToSignByCustomerWallet(hashToSign = challenge.challenge)
|
||||||
|
val approveResult = runVisaCustomerWalletApproveTask(
|
||||||
|
session = session,
|
||||||
|
cardId = card.cardId,
|
||||||
|
targetAddress = address,
|
||||||
|
dataToSign = dataToSign,
|
||||||
|
)
|
||||||
|
val signedData = when (approveResult) {
|
||||||
|
is CompletionResult.Failure<*> -> return CompletionResult.Failure(approveResult.error)
|
||||||
|
is CompletionResult.Success<VisaSignedDataByCustomerWallet> -> approveResult.data
|
||||||
|
}
|
||||||
|
|
||||||
|
val authTokens = withContext(dispatchersProvider.io) {
|
||||||
|
visaAuthRemoteDataSource.getTokenWithCustomerWallet(
|
||||||
|
sessionId = challenge.session.sessionId,
|
||||||
|
signature = signedData.signature,
|
||||||
|
nonce = signedData.dataToSign.hashToSign,
|
||||||
|
)
|
||||||
|
}.getOrNull() ?: return CompletionResult.Failure(VisaActivationError.FailedRemoteState.tangemError)
|
||||||
|
|
||||||
|
return CompletionResult.Success(
|
||||||
|
data = TangemPayInitialCredentials(
|
||||||
|
customerWalletAddress = address,
|
||||||
|
authTokens = authTokens,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun runDerivationTask(
|
||||||
|
session: CardSession,
|
||||||
|
wallet: CardWallet,
|
||||||
|
): CompletionResult<ExtendedPublicKey> {
|
||||||
|
val deferred = CompletableDeferred<CompletionResult<ExtendedPublicKey>>()
|
||||||
|
val derivationTask = DeriveWalletPublicKeyTask(
|
||||||
|
walletPublicKey = wallet.publicKey,
|
||||||
|
derivationPath = VisaUtilities.customDerivationPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
derivationTask.run(session = session, callback = deferred::complete)
|
||||||
|
return deferred.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun runVisaCustomerWalletApproveTask(
|
||||||
|
session: CardSession,
|
||||||
|
cardId: String,
|
||||||
|
targetAddress: String,
|
||||||
|
dataToSign: VisaDataToSignByCustomerWallet,
|
||||||
|
): CompletionResult<VisaSignedDataByCustomerWallet> {
|
||||||
|
val deferred = CompletableDeferred<CompletionResult<VisaSignedDataByCustomerWallet>>()
|
||||||
|
val task = VisaCustomerWalletApproveTask(
|
||||||
|
visaDataForApprove = VisaCustomerWalletApproveTask.Input(
|
||||||
|
cardId = cardId,
|
||||||
|
targetAddress = targetAddress,
|
||||||
|
hashToSign = dataToSign.hashToSign,
|
||||||
|
sign = dataToSign::sign,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
task.run(session = session, callback = deferred::complete)
|
||||||
|
return deferred.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateAddressFromExtendedKey(extendedPublicKey: ExtendedPublicKey): String {
|
||||||
|
val derivationData = VisaUtilities.visaBlockchain.makeAddressesFromExtendedPublicKey(
|
||||||
|
extendedPublicKey = extendedPublicKey,
|
||||||
|
cachedIndex = null,
|
||||||
|
)
|
||||||
|
return derivationData.address
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(coroutineScope: CoroutineScope): TangemPayGenerateAddressAndSignChallengeTask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,6 @@ import com.tangem.common.extensions.toHexString
|
||||||
import com.tangem.core.error.ext.tangemError
|
import com.tangem.core.error.ext.tangemError
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
|
||||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||||
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility
|
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility
|
||||||
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation
|
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation
|
||||||
|
|
@ -59,21 +58,7 @@ class VisaCustomerWalletApproveTask(
|
||||||
session: CardSession,
|
session: CardSession,
|
||||||
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
||||||
) {
|
) {
|
||||||
val cardDTO = CardDTO(card)
|
val derivationPath = VisaUtilities.customDerivationPath
|
||||||
|
|
||||||
val derivationStyle = cardDTO.derivationStyleProvider.getDerivationStyle() ?: run {
|
|
||||||
proceedApproveWithLegacyCard(
|
|
||||||
card = card,
|
|
||||||
session = session,
|
|
||||||
callback = callback,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val derivationPath = VisaUtilities.visaDefaultDerivationPath(derivationStyle) ?: run {
|
|
||||||
callback(CompletionResult.Failure(VisaActivationError.FailedToCreateAddress.tangemError))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
||||||
callback(CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError))
|
callback(CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError))
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
package com.tangem.datasource.local.visa
|
package com.tangem.datasource.local.visa
|
||||||
|
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||||
|
|
||||||
interface TangemPayStorage {
|
interface TangemPayStorage {
|
||||||
|
|
||||||
|
suspend fun storeCustomerWalletAddress(userWalletId: UserWalletId, customerWalletAddress: String)
|
||||||
|
suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String?
|
||||||
|
|
||||||
suspend fun storeAuthTokens(customerWalletAddress: String, tokens: VisaAuthTokens)
|
suspend fun storeAuthTokens(customerWalletAddress: String, tokens: VisaAuthTokens)
|
||||||
|
|
||||||
suspend fun getAuthTokens(customerWalletAddress: String): VisaAuthTokens?
|
suspend fun getAuthTokens(customerWalletAddress: String): VisaAuthTokens?
|
||||||
|
|
@ -14,5 +18,5 @@ interface TangemPayStorage {
|
||||||
|
|
||||||
suspend fun clearOrderId(customerWalletAddress: String)
|
suspend fun clearOrderId(customerWalletAddress: String)
|
||||||
|
|
||||||
suspend fun clearAll(customerWalletAddress: String)
|
suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String)
|
||||||
}
|
}
|
||||||
|
|
@ -4,9 +4,8 @@ import arrow.core.Either
|
||||||
import arrow.core.raise.either
|
import arrow.core.raise.either
|
||||||
import com.tangem.common.CompletionResult
|
import com.tangem.common.CompletionResult
|
||||||
import com.tangem.domain.pay.datasource.TangemPayAuthDataSource
|
import com.tangem.domain.pay.datasource.TangemPayAuthDataSource
|
||||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
|
||||||
import com.tangem.domain.visa.model.VisaDataToSignByCustomerWallet
|
|
||||||
import com.tangem.domain.visa.datasource.VisaAuthRemoteDataSource
|
import com.tangem.domain.visa.datasource.VisaAuthRemoteDataSource
|
||||||
|
import com.tangem.domain.visa.model.TangemPayInitialCredentials
|
||||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||||
import com.tangem.sdk.api.TangemSdkManager
|
import com.tangem.sdk.api.TangemSdkManager
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -16,29 +15,14 @@ internal class DefaultTangemPayAuthDataSource @Inject constructor(
|
||||||
private val tangemSdkManager: TangemSdkManager,
|
private val tangemSdkManager: TangemSdkManager,
|
||||||
) : TangemPayAuthDataSource {
|
) : TangemPayAuthDataSource {
|
||||||
|
|
||||||
override suspend fun generateNewAuthTokens(address: String, cardId: String): Either<Throwable, VisaAuthTokens> =
|
override suspend fun produceInitialCredentials(cardId: String): Either<Throwable, TangemPayInitialCredentials> {
|
||||||
either {
|
val initialCredentials = tangemSdkManager.tangemPayProduceInitialCredentials(cardId = cardId)
|
||||||
val challenge = visaAuthRemoteDataSource
|
|
||||||
.getCustomerWalletAuthChallenge(address)
|
|
||||||
.mapLeft { IllegalStateException("TangemPay challenge failed. Error code: ${it.errorCode}") }
|
|
||||||
.bind()
|
|
||||||
|
|
||||||
val signed = tangemSdkManager.visaCustomerWalletApprove(
|
return when (initialCredentials) {
|
||||||
VisaDataForApprove(
|
is CompletionResult.Failure<*> -> Either.Left(initialCredentials.error)
|
||||||
customerWalletCardId = cardId,
|
is CompletionResult.Success<TangemPayInitialCredentials> -> Either.Right(initialCredentials.data)
|
||||||
targetAddress = address,
|
|
||||||
dataToSign = VisaDataToSignByCustomerWallet(hashToSign = challenge.challenge),
|
|
||||||
),
|
|
||||||
).toEither { IllegalStateException("TangemPay signing failed: $it") }.bind()
|
|
||||||
|
|
||||||
visaAuthRemoteDataSource.getTokenWithCustomerWallet(
|
|
||||||
sessionId = challenge.session.sessionId,
|
|
||||||
signature = signed.signature,
|
|
||||||
nonce = signed.dataToSign.hashToSign,
|
|
||||||
)
|
|
||||||
.mapLeft { IllegalStateException("TangemPay token fetch failed. Error code: ${it.errorCode}") }
|
|
||||||
.bind()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun refreshAuthTokens(refreshToken: String): Either<Throwable, VisaAuthTokens> = either {
|
override suspend fun refreshAuthTokens(refreshToken: String): Either<Throwable, VisaAuthTokens> = either {
|
||||||
visaAuthRemoteDataSource.refreshCustomerWalletAuthTokens(
|
visaAuthRemoteDataSource.refreshCustomerWalletAuthTokens(
|
||||||
|
|
@ -47,9 +31,4 @@ internal class DefaultTangemPayAuthDataSource @Inject constructor(
|
||||||
.mapLeft { IllegalStateException("TangemPay token refresh failed. Error code: ${it.errorCode}") }
|
.mapLeft { IllegalStateException("TangemPay token refresh failed. Error code: ${it.errorCode}") }
|
||||||
.bind()
|
.bind()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> CompletionResult<T>.toEither(map: (Throwable) -> Throwable) = when (this) {
|
|
||||||
is CompletionResult.Success -> Either.Right(data)
|
|
||||||
is CompletionResult.Failure -> Either.Left(map(error))
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.pay.repository
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import com.tangem.core.error.UniversalError
|
import com.tangem.core.error.UniversalError
|
||||||
|
import com.tangem.datasource.api.common.response.getOrThrow
|
||||||
import com.tangem.datasource.api.pay.TangemPayApi
|
import com.tangem.datasource.api.pay.TangemPayApi
|
||||||
import com.tangem.datasource.api.pay.models.request.DeeplinkValidityRequest
|
import com.tangem.datasource.api.pay.models.request.DeeplinkValidityRequest
|
||||||
import com.tangem.datasource.api.pay.models.request.OrderRequest
|
import com.tangem.datasource.api.pay.models.request.OrderRequest
|
||||||
|
|
@ -30,9 +31,9 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
||||||
|
|
||||||
override suspend fun validateDeeplink(link: String): Either<UniversalError, Boolean> {
|
override suspend fun validateDeeplink(link: String): Either<UniversalError, Boolean> {
|
||||||
return requestHelper.runWithErrorLogs(TAG) {
|
return requestHelper.runWithErrorLogs(TAG) {
|
||||||
val result = requestHelper.request {
|
val result = tangemPayApi.validateDeeplink(DeeplinkValidityRequest(link))
|
||||||
tangemPayApi.validateDeeplink(DeeplinkValidityRequest(link))
|
.getOrThrow()
|
||||||
}.result
|
.result
|
||||||
result?.status == VALID_STATUS
|
result?.status == VALID_STATUS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@ package com.tangem.data.pay.repository
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.tangem.blockchain.common.Blockchain
|
|
||||||
import com.tangem.core.error.UniversalError
|
import com.tangem.core.error.UniversalError
|
||||||
import com.tangem.data.common.network.NetworkFactory
|
|
||||||
import com.tangem.data.pay.util.TangemPayErrorConverter
|
import com.tangem.data.pay.util.TangemPayErrorConverter
|
||||||
import com.tangem.data.pay.util.TangemPayWalletsManager
|
import com.tangem.data.pay.util.TangemPayWalletsManager
|
||||||
import com.tangem.datasource.api.common.response.ApiResponse
|
import com.tangem.datasource.api.common.response.ApiResponse
|
||||||
|
|
@ -14,10 +12,10 @@ import com.tangem.datasource.di.NetworkMoshi
|
||||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||||
import com.tangem.domain.pay.datasource.TangemPayAuthDataSource
|
import com.tangem.domain.pay.datasource.TangemPayAuthDataSource
|
||||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.visa.model.getAuthHeader
|
||||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -30,11 +28,9 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
||||||
private val tangemPayStorage: TangemPayStorage,
|
private val tangemPayStorage: TangemPayStorage,
|
||||||
private val authDataSource: TangemPayAuthDataSource,
|
private val authDataSource: TangemPayAuthDataSource,
|
||||||
private val tangemPayWalletsManager: TangemPayWalletsManager,
|
private val tangemPayWalletsManager: TangemPayWalletsManager,
|
||||||
private val walletManagersFacade: WalletManagersFacade,
|
|
||||||
private val networkFactory: NetworkFactory,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var customerWalletAddress: String? = null
|
private val customerWalletAddress = MutableStateFlow<String?>(null)
|
||||||
|
|
||||||
private val refreshTokensMutex = Mutex()
|
private val refreshTokensMutex = Mutex()
|
||||||
private var refreshTokensJob: Deferred<VisaAuthTokens>? = null
|
private var refreshTokensJob: Deferred<VisaAuthTokens>? = null
|
||||||
|
|
@ -81,7 +77,9 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
||||||
getTokens: (suspend () -> VisaAuthTokens),
|
getTokens: (suspend () -> VisaAuthTokens),
|
||||||
refreshTokens: (suspend () -> VisaAuthTokens)? = null,
|
refreshTokens: (suspend () -> VisaAuthTokens)? = null,
|
||||||
): T = runCatching {
|
): T = runCatching {
|
||||||
requestBlock("Bearer ${getTokens().accessToken}").getOrThrow()
|
val tokens = getTokens()
|
||||||
|
val header = tokens.getAuthHeader()
|
||||||
|
requestBlock(header).getOrThrow()
|
||||||
}.getOrElse { error ->
|
}.getOrElse { error ->
|
||||||
val unauthorizedCode = ApiResponseError.HttpException.Code.UNAUTHORIZED
|
val unauthorizedCode = ApiResponseError.HttpException.Code.UNAUTHORIZED
|
||||||
if (error is ApiResponseError.HttpException && refreshTokens != null && error.code == unauthorizedCode) {
|
if (error is ApiResponseError.HttpException && refreshTokens != null && error.code == unauthorizedCode) {
|
||||||
|
|
@ -116,7 +114,18 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCustomerWalletAddress(): String = customerWalletAddress ?: fetchAuthInputData().address
|
suspend fun getCustomerWalletAddress(): String {
|
||||||
|
val existingAddress = customerWalletAddress.value
|
||||||
|
if (existingAddress != null) {
|
||||||
|
return existingAddress
|
||||||
|
}
|
||||||
|
val storedAddress = tangemPayStorage.getCustomerWalletAddress(
|
||||||
|
userWalletId = tangemPayWalletsManager.getDefaultWalletForTangemPay().walletId,
|
||||||
|
) ?: error("Can not find customer address")
|
||||||
|
|
||||||
|
customerWalletAddress.value = storedAddress
|
||||||
|
return storedAddress
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getAccessTokens(): VisaAuthTokens {
|
private suspend fun getAccessTokens(): VisaAuthTokens {
|
||||||
return getAccessTokensIfSaved() ?: fetchTokens()
|
return getAccessTokensIfSaved() ?: fetchTokens()
|
||||||
|
|
@ -126,42 +135,33 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
||||||
return tangemPayStorage.getAuthTokens(getCustomerWalletAddress())
|
return tangemPayStorage.getAuthTokens(getCustomerWalletAddress())
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchAuthInputData(): AuthInputData {
|
|
||||||
val wallet = tangemPayWalletsManager.getDefaultWalletForTangemPay()
|
|
||||||
|
|
||||||
val network = networkFactory.create(
|
|
||||||
blockchain = Blockchain.Polygon,
|
|
||||||
extraDerivationPath = null,
|
|
||||||
derivationStyleProvider = wallet.derivationStyleProvider,
|
|
||||||
canHandleTokens = true,
|
|
||||||
) ?: error("Cannot create network")
|
|
||||||
|
|
||||||
val address = walletManagersFacade.getDefaultAddress(wallet.walletId, network)
|
|
||||||
?: error("Cannot get polygon address")
|
|
||||||
|
|
||||||
customerWalletAddress = address
|
|
||||||
|
|
||||||
return AuthInputData(address, wallet.cardId)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun fetchTokens(): VisaAuthTokens {
|
private suspend fun fetchTokens(): VisaAuthTokens {
|
||||||
val inputData = fetchAuthInputData()
|
val wallet = tangemPayWalletsManager.getDefaultWalletForTangemPay()
|
||||||
val tokens = authDataSource.generateNewAuthTokens(inputData.address, inputData.cardId)
|
val initialCredentials = authDataSource.produceInitialCredentials(cardId = wallet.cardId)
|
||||||
.getOrNull() ?: error("Cannot fetch tokens")
|
.getOrThrowWithMessage("Can not produce initial data:")
|
||||||
tangemPayStorage.storeAuthTokens(inputData.address, tokens)
|
tangemPayStorage.storeCustomerWalletAddress(
|
||||||
return tokens
|
userWalletId = wallet.walletId,
|
||||||
|
customerWalletAddress = initialCredentials.customerWalletAddress,
|
||||||
|
)
|
||||||
|
tangemPayStorage.storeAuthTokens(
|
||||||
|
customerWalletAddress = initialCredentials.customerWalletAddress,
|
||||||
|
tokens = initialCredentials.authTokens,
|
||||||
|
)
|
||||||
|
return initialCredentials.authTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun refreshAuthTokens(): VisaAuthTokens {
|
private suspend fun refreshAuthTokens(): VisaAuthTokens {
|
||||||
val customerWalletAddress = getCustomerWalletAddress()
|
val customerWalletAddress = getCustomerWalletAddress()
|
||||||
val refreshToken = getAccessTokens().refreshToken.value
|
val refreshToken = getAccessTokens().refreshToken.value
|
||||||
val tokens = authDataSource.refreshAuthTokens(refreshToken).getOrNull() ?: error("Cannot refresh tokens")
|
val tokens = authDataSource.refreshAuthTokens(refreshToken).getOrThrowWithMessage("Cannot refresh tokens:")
|
||||||
tangemPayStorage.storeAuthTokens(customerWalletAddress, tokens)
|
tangemPayStorage.storeAuthTokens(customerWalletAddress, tokens)
|
||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal data class AuthInputData(
|
private fun <A : Throwable, B> Either<A, B>.getOrThrowWithMessage(message: String): B {
|
||||||
val address: String,
|
return this.fold(
|
||||||
val cardId: String,
|
ifLeft = { error -> throw IllegalStateException("$message ${error.message}") },
|
||||||
)
|
ifRight = { it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.domain.card.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.FirmwareVersion
|
import com.tangem.common.card.FirmwareVersion
|
||||||
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
|
|
||||||
private const val VISA_BATCH_START = "AE"
|
private const val VISA_BATCH_START = "AE"
|
||||||
|
|
@ -16,6 +17,7 @@ object VisaUtilities {
|
||||||
|
|
||||||
val visaDefaultDerivationPath
|
val visaDefaultDerivationPath
|
||||||
get() = visaBlockchain.derivationPath(DerivationStyle.V3)
|
get() = visaBlockchain.derivationPath(DerivationStyle.V3)
|
||||||
|
val customDerivationPath = DerivationPath("m/44'/60'/999999'/0/0")
|
||||||
|
|
||||||
fun visaDefaultDerivationPath(style: DerivationStyle) = visaBlockchain.derivationPath(style)
|
fun visaDefaultDerivationPath(style: DerivationStyle) = visaBlockchain.derivationPath(style)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package com.tangem.domain.visa.model
|
||||||
|
|
||||||
|
data class TangemPayInitialCredentials(val customerWalletAddress: String, val authTokens: VisaAuthTokens)
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
package com.tangem.domain.pay.datasource
|
package com.tangem.domain.pay.datasource
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
|
import com.tangem.domain.visa.model.TangemPayInitialCredentials
|
||||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||||
|
|
||||||
interface TangemPayAuthDataSource {
|
interface TangemPayAuthDataSource {
|
||||||
|
|
||||||
suspend fun generateNewAuthTokens(address: String, cardId: String): Either<Throwable, VisaAuthTokens>
|
suspend fun produceInitialCredentials(cardId: String): Either<Throwable, TangemPayInitialCredentials>
|
||||||
|
|
||||||
suspend fun refreshAuthTokens(refreshToken: String): Either<Throwable, VisaAuthTokens>
|
suspend fun refreshAuthTokens(refreshToken: String): Either<Throwable, VisaAuthTokens>
|
||||||
}
|
}
|
||||||
|
|
@ -22,12 +22,7 @@ internal class DefaultTangemPayOnboardingComponent @AssistedInject constructor(
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(modifier: Modifier) {
|
override fun Content(modifier: Modifier) {
|
||||||
val state by model.screenState.collectAsStateWithLifecycle()
|
val state by model.screenState.collectAsStateWithLifecycle()
|
||||||
TandemPayOnboardingScreen(
|
TandemPayOnboardingScreen(modifier = modifier, state = state)
|
||||||
modifier = modifier,
|
|
||||||
state = state,
|
|
||||||
onBackClick = model::back,
|
|
||||||
onOpenKycClick = model::openKyc,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import com.tangem.features.tangempay.ui.TangemPayOnboardingScreenState
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
@ -27,7 +28,7 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
||||||
private val params = paramsContainer.require<TangemPayOnboardingComponent.Params>()
|
private val params = paramsContainer.require<TangemPayOnboardingComponent.Params>()
|
||||||
|
|
||||||
val screenState: StateFlow<TangemPayOnboardingScreenState>
|
val screenState: StateFlow<TangemPayOnboardingScreenState>
|
||||||
field = MutableStateFlow(TangemPayOnboardingScreenState())
|
field = MutableStateFlow(getInitialState())
|
||||||
|
|
||||||
init {
|
init {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
|
|
@ -37,21 +38,27 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
||||||
}
|
}
|
||||||
is TangemPayOnboardingComponent.Params.Deeplink -> {
|
is TangemPayOnboardingComponent.Params.Deeplink -> {
|
||||||
repository.validateDeeplink(params.deeplink)
|
repository.validateDeeplink(params.deeplink)
|
||||||
.onRight { isValid -> if (isValid) checkCustomerInfo() }
|
.onRight { isValid -> if (isValid) showOnboarding() }
|
||||||
.onLeft { back() }
|
.onLeft { back() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openKyc() {
|
private fun openKyc() {
|
||||||
router.replaceAll(AppRoute.Wallet, AppRoute.Kyc)
|
router.replaceAll(AppRoute.Wallet, AppRoute.Kyc)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun back() {
|
private fun back() {
|
||||||
router.pop()
|
router.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showOnboarding() {
|
||||||
|
screenState.update {
|
||||||
|
it.copy(fullScreenLoading = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun checkCustomerInfo() {
|
private suspend fun checkCustomerInfo() {
|
||||||
repository.getCustomerInfo()
|
repository.getCustomerInfo()
|
||||||
.onRight { customerInfo ->
|
.onRight { customerInfo ->
|
||||||
|
|
@ -68,4 +75,13 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
||||||
}
|
}
|
||||||
.onLeft { back() }
|
.onLeft { back() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getInitialState(): TangemPayOnboardingScreenState {
|
||||||
|
return TangemPayOnboardingScreenState(
|
||||||
|
fullScreenLoading = true,
|
||||||
|
buttonLoading = false,
|
||||||
|
onGetCardClick = ::openKyc,
|
||||||
|
onBackClick = ::back,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,24 +9,21 @@ import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
|
||||||
import com.tangem.core.ui.res.TangemThemePreview
|
import com.tangem.core.ui.res.TangemThemePreview
|
||||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun TandemPayOnboardingScreen(
|
internal fun TandemPayOnboardingScreen(state: TangemPayOnboardingScreenState, modifier: Modifier = Modifier) {
|
||||||
state: TangemPayOnboardingScreenState,
|
|
||||||
onOpenKycClick: () -> Unit,
|
|
||||||
onBackClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier.systemBarsPadding(),
|
modifier = modifier.systemBarsPadding(),
|
||||||
topBar = {
|
topBar = {
|
||||||
AppBarWithBackButton(
|
AppBarWithBackButton(
|
||||||
modifier = Modifier.statusBarsPadding(),
|
modifier = Modifier.statusBarsPadding(),
|
||||||
onBackClick = onBackClick,
|
onBackClick = state.onBackClick,
|
||||||
iconRes = R.drawable.ic_back_24,
|
iconRes = R.drawable.ic_back_24,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -37,7 +34,7 @@ internal fun TandemPayOnboardingScreen(
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
state = state,
|
state = state,
|
||||||
onButtonClick = onOpenKycClick,
|
onButtonClick = state.onGetCardClick,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -46,8 +43,35 @@ internal fun TandemPayOnboardingScreen(
|
||||||
@Preview(showBackground = true)
|
@Preview(showBackground = true)
|
||||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
@Composable
|
@Composable
|
||||||
private fun PreviewDarkTheme() {
|
private fun PreviewDarkTheme(
|
||||||
|
@PreviewParameter(TangemPayOnboardingScreenStateProvider::class)
|
||||||
|
state: TangemPayOnboardingScreenState,
|
||||||
|
) {
|
||||||
TangemThemePreview {
|
TangemThemePreview {
|
||||||
TandemPayOnboardingScreen(state = TangemPayOnboardingScreenState(), {}, {})
|
TandemPayOnboardingScreen(state = state, modifier = Modifier.fillMaxSize())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class TangemPayOnboardingScreenStateProvider :
|
||||||
|
CollectionPreviewParameterProvider<TangemPayOnboardingScreenState>(
|
||||||
|
listOf(
|
||||||
|
TangemPayOnboardingScreenState(
|
||||||
|
fullScreenLoading = true,
|
||||||
|
buttonLoading = false,
|
||||||
|
onGetCardClick = {},
|
||||||
|
onBackClick = {},
|
||||||
|
),
|
||||||
|
TangemPayOnboardingScreenState(
|
||||||
|
fullScreenLoading = false,
|
||||||
|
buttonLoading = false,
|
||||||
|
onGetCardClick = {},
|
||||||
|
onBackClick = {},
|
||||||
|
),
|
||||||
|
TangemPayOnboardingScreenState(
|
||||||
|
fullScreenLoading = false,
|
||||||
|
buttonLoading = true,
|
||||||
|
onGetCardClick = {},
|
||||||
|
onBackClick = {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
@ -4,6 +4,8 @@ import javax.annotation.concurrent.Immutable
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
internal data class TangemPayOnboardingScreenState(
|
internal data class TangemPayOnboardingScreenState(
|
||||||
val fullScreenLoading: Boolean = true,
|
val fullScreenLoading: Boolean,
|
||||||
val buttonLoading: Boolean = false,
|
val buttonLoading: Boolean,
|
||||||
|
val onGetCardClick: () -> Unit,
|
||||||
|
val onBackClick: () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
@ -16,7 +16,10 @@ import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.visa.model.*
|
import com.tangem.domain.visa.model.TangemPayInitialCredentials
|
||||||
|
import com.tangem.domain.visa.model.VisaActivationInput
|
||||||
|
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||||
|
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||||
import com.tangem.operations.wallet.CreateWalletResponse
|
import com.tangem.operations.wallet.CreateWalletResponse
|
||||||
|
|
@ -156,5 +159,6 @@ interface TangemSdkManager {
|
||||||
visaDataForApprove: VisaDataForApprove,
|
visaDataForApprove: VisaDataForApprove,
|
||||||
): CompletionResult<VisaSignedDataByCustomerWallet>
|
): CompletionResult<VisaSignedDataByCustomerWallet>
|
||||||
|
|
||||||
|
suspend fun tangemPayProduceInitialCredentials(cardId: String): CompletionResult<TangemPayInitialCredentials>
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue