Updated on 2026-08-14
This commit is contained in:
parent
3ffffa73ec
commit
616386c345
18 changed files with 310 additions and 125 deletions
|
|
@ -4,9 +4,8 @@ import arrow.core.Either
|
|||
import arrow.core.raise.either
|
||||
import com.tangem.common.CompletionResult
|
||||
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.model.TangemPayInitialCredentials
|
||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import javax.inject.Inject
|
||||
|
|
@ -16,29 +15,14 @@ internal class DefaultTangemPayAuthDataSource @Inject constructor(
|
|||
private val tangemSdkManager: TangemSdkManager,
|
||||
) : TangemPayAuthDataSource {
|
||||
|
||||
override suspend fun generateNewAuthTokens(address: String, cardId: String): Either<Throwable, VisaAuthTokens> =
|
||||
either {
|
||||
val challenge = visaAuthRemoteDataSource
|
||||
.getCustomerWalletAuthChallenge(address)
|
||||
.mapLeft { IllegalStateException("TangemPay challenge failed. Error code: ${it.errorCode}") }
|
||||
.bind()
|
||||
override suspend fun produceInitialCredentials(cardId: String): Either<Throwable, TangemPayInitialCredentials> {
|
||||
val initialCredentials = tangemSdkManager.tangemPayProduceInitialCredentials(cardId = cardId)
|
||||
|
||||
val signed = tangemSdkManager.visaCustomerWalletApprove(
|
||||
VisaDataForApprove(
|
||||
customerWalletCardId = cardId,
|
||||
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()
|
||||
return when (initialCredentials) {
|
||||
is CompletionResult.Failure<*> -> Either.Left(initialCredentials.error)
|
||||
is CompletionResult.Success<TangemPayInitialCredentials> -> Either.Right(initialCredentials.data)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun refreshAuthTokens(refreshToken: String): Either<Throwable, VisaAuthTokens> = either {
|
||||
visaAuthRemoteDataSource.refreshCustomerWalletAuthTokens(
|
||||
|
|
@ -47,9 +31,4 @@ internal class DefaultTangemPayAuthDataSource @Inject constructor(
|
|||
.mapLeft { IllegalStateException("TangemPay token refresh failed. Error code: ${it.errorCode}") }
|
||||
.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 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.models.request.DeeplinkValidityRequest
|
||||
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> {
|
||||
return requestHelper.runWithErrorLogs(TAG) {
|
||||
val result = requestHelper.request {
|
||||
tangemPayApi.validateDeeplink(DeeplinkValidityRequest(link))
|
||||
}.result
|
||||
val result = tangemPayApi.validateDeeplink(DeeplinkValidityRequest(link))
|
||||
.getOrThrow()
|
||||
.result
|
||||
result?.status == VALID_STATUS
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ package com.tangem.data.pay.repository
|
|||
|
||||
import arrow.core.Either
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
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.TangemPayWalletsManager
|
||||
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.domain.pay.datasource.TangemPayAuthDataSource
|
||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||
import com.tangem.domain.visa.model.getAuthHeader
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
|
|
@ -30,11 +28,9 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
private val tangemPayStorage: TangemPayStorage,
|
||||
private val authDataSource: TangemPayAuthDataSource,
|
||||
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 var refreshTokensJob: Deferred<VisaAuthTokens>? = null
|
||||
|
|
@ -81,7 +77,9 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
getTokens: (suspend () -> VisaAuthTokens),
|
||||
refreshTokens: (suspend () -> VisaAuthTokens)? = null,
|
||||
): T = runCatching {
|
||||
requestBlock("Bearer ${getTokens().accessToken}").getOrThrow()
|
||||
val tokens = getTokens()
|
||||
val header = tokens.getAuthHeader()
|
||||
requestBlock(header).getOrThrow()
|
||||
}.getOrElse { error ->
|
||||
val unauthorizedCode = ApiResponseError.HttpException.Code.UNAUTHORIZED
|
||||
if (error is ApiResponseError.HttpException && refreshTokens != null && error.code == unauthorizedCode) {
|
||||
|
|
@ -116,7 +114,18 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
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 {
|
||||
return getAccessTokensIfSaved() ?: fetchTokens()
|
||||
|
|
@ -126,42 +135,33 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
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 {
|
||||
val inputData = fetchAuthInputData()
|
||||
val tokens = authDataSource.generateNewAuthTokens(inputData.address, inputData.cardId)
|
||||
.getOrNull() ?: error("Cannot fetch tokens")
|
||||
tangemPayStorage.storeAuthTokens(inputData.address, tokens)
|
||||
return tokens
|
||||
val wallet = tangemPayWalletsManager.getDefaultWalletForTangemPay()
|
||||
val initialCredentials = authDataSource.produceInitialCredentials(cardId = wallet.cardId)
|
||||
.getOrThrowWithMessage("Can not produce initial data:")
|
||||
tangemPayStorage.storeCustomerWalletAddress(
|
||||
userWalletId = wallet.walletId,
|
||||
customerWalletAddress = initialCredentials.customerWalletAddress,
|
||||
)
|
||||
tangemPayStorage.storeAuthTokens(
|
||||
customerWalletAddress = initialCredentials.customerWalletAddress,
|
||||
tokens = initialCredentials.authTokens,
|
||||
)
|
||||
return initialCredentials.authTokens
|
||||
}
|
||||
|
||||
private suspend fun refreshAuthTokens(): VisaAuthTokens {
|
||||
val customerWalletAddress = getCustomerWalletAddress()
|
||||
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)
|
||||
return tokens
|
||||
}
|
||||
}
|
||||
|
||||
internal data class AuthInputData(
|
||||
val address: String,
|
||||
val cardId: String,
|
||||
)
|
||||
private fun <A : Throwable, B> Either<A, B>.getOrThrowWithMessage(message: String): B {
|
||||
return this.fold(
|
||||
ifLeft = { error -> throw IllegalStateException("$message ${error.message}") },
|
||||
ifRight = { it },
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue