diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/DefaultTangemPayAuthDataSource.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/DefaultTangemPayAuthDataSource.kt index f347f480b2..9eea6d7429 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/DefaultTangemPayAuthDataSource.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/datasource/DefaultTangemPayAuthDataSource.kt @@ -2,35 +2,26 @@ package com.tangem.data.pay.datasource import arrow.core.Either import arrow.core.left -import arrow.core.raise.either +import arrow.core.right import com.tangem.common.CompletionResult import com.tangem.common.core.TangemSdkError import com.tangem.domain.pay.datasource.TangemPayAuthDataSource import com.tangem.domain.pay.model.WithdrawalSignatureResult -import com.tangem.domain.visa.datasource.VisaAuthRemoteDataSource -import com.tangem.domain.visa.model.TangemPayAuthTokens import com.tangem.domain.visa.model.TangemPayInitialCredentials import com.tangem.sdk.api.TangemSdkManager import javax.inject.Inject internal class DefaultTangemPayAuthDataSource @Inject constructor( - private val visaAuthRemoteDataSource: VisaAuthRemoteDataSource, private val tangemSdkManager: TangemSdkManager, ) : TangemPayAuthDataSource { override suspend fun produceInitialCredentials(cardId: String): Either { return when (val initialCredentials = tangemSdkManager.tangemPayProduceInitialCredentials(cardId = cardId)) { - is CompletionResult.Failure<*> -> Either.Left(initialCredentials.error) - is CompletionResult.Success -> Either.Right(initialCredentials.data) + is CompletionResult.Failure<*> -> initialCredentials.error.left() + is CompletionResult.Success -> initialCredentials.data.right() } } - override suspend fun refreshAuthTokens(refreshToken: String): Either = either { - visaAuthRemoteDataSource.refreshCustomerWalletAuthTokens(refreshToken = refreshToken) - .mapLeft { IllegalStateException("TangemPay token refresh failed. Error code: ${it.errorCode}") } - .bind() - } - override suspend fun getWithdrawalSignature( cardId: String, hash: String, @@ -38,13 +29,13 @@ internal class DefaultTangemPayAuthDataSource @Inject constructor( return when (val signResult = tangemSdkManager.getWithdrawalSignature(cardId, hash)) { is CompletionResult.Failure<*> -> { if (signResult.error is TangemSdkError.UserCancelled) { - Either.Right(WithdrawalSignatureResult.Cancelled) + WithdrawalSignatureResult.Cancelled.right() } else { signResult.error.left() } } is CompletionResult.Success -> { - Either.Right(WithdrawalSignatureResult.Success(signResult.data)) + WithdrawalSignatureResult.Success(signResult.data).right() } } } diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt index 51c702ce61..21077cd428 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt @@ -139,7 +139,7 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor( cardId: String, ): Either { cardFrozenStateStore.store(cardId, TangemPayCardFrozenState.Pending) - return requestHelper.makeSafeRequest(userWalletId) { + return requestHelper.performRequest(userWalletId) { tangemPayApi.freezeCard(authHeader = it, body = FreezeUnfreezeCardRequest(cardId = cardId)) }.onLeft { cardFrozenStateStore.store(cardId, TangemPayCardFrozenState.Unfrozen) @@ -172,7 +172,7 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor( cardId: String, ): Either { cardFrozenStateStore.store(cardId, TangemPayCardFrozenState.Pending) - return requestHelper.makeSafeRequest(userWalletId) { + return requestHelper.performRequest(userWalletId) { tangemPayApi.unfreezeCard(authHeader = it, body = FreezeUnfreezeCardRequest(cardId = cardId)) }.onLeft { cardFrozenStateStore.store(cardId, TangemPayCardFrozenState.Frozen) @@ -215,7 +215,7 @@ internal class DefaultTangemPayCardDetailsRepository @Inject constructor( while (isActive && pollingJobs.containsKey(orderId)) { delay(duration = 5.seconds) - val orderStatus = requestHelper.makeSafeRequest(userWalletId) { authHeader -> + val orderStatus = requestHelper.performRequest(userWalletId) { authHeader -> tangemPayApi.getOrder(authHeader, orderId) } diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt index 78dec8de97..d1d72250d3 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt @@ -44,7 +44,7 @@ internal class DefaultTangemPaySwapRepository @Inject constructor( ): Either { val amountInCents = getAmountInCents(cryptoAmount, cryptoCurrencyId) if (amountInCents.isNullOrEmpty()) return Either.Left(VisaApiError.WithdrawalDataError) - return requestHelper.makeSafeRequest(userWalletId) { authHeader -> + return requestHelper.performRequest(userWalletId) { authHeader -> val request = WithdrawDataRequest(amountInCents = amountInCents, recipientAddress = receiverAddress) tangemPayApi.getWithdrawData(authHeader = authHeader, body = request) }.map { data -> @@ -60,7 +60,7 @@ internal class DefaultTangemPaySwapRepository @Inject constructor( Either.Right(WithdrawalResult.Cancelled) } is WithdrawalSignatureResult.Success -> { - requestHelper.makeSafeRequest(userWalletId) { authHeader -> + requestHelper.performRequest(userWalletId) { authHeader -> val request = WithdrawRequest( amountInCents = amountInCents, recipientAddress = receiverAddress, diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt index db011f878c..f3aacb104e 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt @@ -1,6 +1,7 @@ package com.tangem.data.pay.repository import arrow.core.Either +import arrow.core.getOrElse import arrow.core.left import arrow.core.raise.catch import arrow.core.right @@ -8,30 +9,35 @@ import com.squareup.moshi.Moshi import com.squareup.wire.Instant import com.tangem.data.pay.util.TangemPayErrorConverter import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.pay.TangemPayAuthApi +import com.tangem.datasource.api.pay.models.request.RefreshCustomerWalletAccessTokenRequest +import com.tangem.datasource.api.pay.models.response.TangemPayGetTokensResponse import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.pay.datasource.TangemPayAuthDataSource import com.tangem.domain.visa.error.VisaApiError import com.tangem.domain.visa.model.TangemPayAuthTokens import com.tangem.domain.visa.model.getAuthHeader import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.* +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton +private const val TAG = "TangemPayRequestPerformer" + @Singleton internal class TangemPayRequestPerformer @Inject constructor( @NetworkMoshi moshi: Moshi, private val environmentConfigStorage: EnvironmentConfigStorage, private val dispatchers: CoroutineDispatcherProvider, + private val tangemPayAuthApi: TangemPayAuthApi, private val tangemPayStorage: TangemPayStorage, - private val authDataSource: TangemPayAuthDataSource, ) { private val customerWalletAddress = MutableStateFlow(null) @@ -56,13 +62,6 @@ internal class TangemPayRequestPerformer @Inject constructor( } } - suspend fun makeSafeRequest( - userWalletId: UserWalletId, - requestBlock: suspend (header: String) -> ApiResponse, - ): Either { - return performRequest(userWalletId, requestBlock) - } - @Deprecated("Use perform request instead", replaceWith = ReplaceWith("performRequest")) suspend fun request( userWalletId: UserWalletId, @@ -99,7 +98,7 @@ internal class TangemPayRequestPerformer @Inject constructor( ): Either = withContext(dispatchers.io) { catch( block = { - val tokens = getAccessTokens(userWalletId) ?: return@catch VisaApiError.RefreshTokenExpired.left() + val tokens = getAccessTokens(userWalletId).getOrElse { return@catch it.left() } when (val apiResponse: ApiResponse = requestBlock(tokens.getAuthHeader())) { is ApiResponse.Error -> errorConverter.convert(apiResponse.cause).left() @@ -107,7 +106,7 @@ internal class TangemPayRequestPerformer @Inject constructor( } }, catch = { errorConverter.convert(it).left() }, - ).onLeft { visaApiError -> Timber.tag("TangemPayRequestPerformer").e(visaApiError.toString()) } + ).onLeft { visaApiError -> Timber.tag(TAG).e(visaApiError.toString()) } } suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String { @@ -123,7 +122,7 @@ internal class TangemPayRequestPerformer @Inject constructor( return storedAddress } - private suspend fun getAccessTokens(userWalletId: UserWalletId): TangemPayAuthTokens? { + private suspend fun getAccessTokens(userWalletId: UserWalletId): Either { return tokensMutex.withLock { val walletAddress = getCustomerWalletAddress(userWalletId) val tokens = tangemPayStorage.getAuthTokens(walletAddress) ?: error("Auth tokens are not stored") @@ -132,24 +131,41 @@ internal class TangemPayRequestPerformer @Inject constructor( val refreshExpiresAt = Instant.ofEpochSecond(tokens.refreshExpiresAt) if (accessExpiresAt.isAfter(now)) { - tokens + tokens.right() } else if (accessExpiresAt.isBefore(now) && refreshExpiresAt.isAfter(now)) { - val newTokens = refreshAuthTokens(userWalletId = userWalletId, refreshToken = tokens.refreshToken) - newTokens + refreshAuthTokens(userWalletId = userWalletId, refreshToken = tokens.refreshToken) } else { - null + VisaApiError.RefreshTokenExpired.left() } } } - private suspend fun refreshAuthTokens(userWalletId: UserWalletId, refreshToken: String): TangemPayAuthTokens { + private suspend fun refreshAuthTokens( + userWalletId: UserWalletId, + refreshToken: String, + ): Either { val customerWalletAddress = getCustomerWalletAddress(userWalletId) - val tokens = authDataSource.refreshAuthTokens(refreshToken) - .fold( - ifLeft = { error -> error("Cannot refresh tokens: ${error.message}") }, - ifRight = { it }, + val apiResponse = tangemPayAuthApi.refreshCustomerWalletAccessToken( + request = RefreshCustomerWalletAccessTokenRequest( + authType = "customer_wallet", + refreshToken = refreshToken, + ), + ) + val responseEither = when (apiResponse) { + is ApiResponse.Error -> errorConverter.convert(apiResponse.cause).left() + is ApiResponse.Success -> apiResponse.data.right() + } + return responseEither.map { response -> + TangemPayAuthTokens( + accessToken = response.accessToken, + expiresAt = response.expiresAt, + refreshToken = response.refreshToken, + refreshExpiresAt = response.refreshExpiresAt, ) - tangemPayStorage.storeAuthTokens(customerWalletAddress, tokens) - return tokens + }.onRight { tokens -> + tangemPayStorage.storeAuthTokens(customerWalletAddress = customerWalletAddress, tokens = tokens) + }.onLeft { + Timber.tag(TAG).e("Can not refresh auth tokens: $it") + } } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayErrorConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayErrorConverter.kt index d5fa97ece5..145269f0f5 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayErrorConverter.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/util/TangemPayErrorConverter.kt @@ -13,6 +13,7 @@ class TangemPayErrorConverter(moshi: Moshi) : Converter override fun convert(value: Throwable): VisaApiError { return if (value is ApiResponseError.HttpException) { if (value.code == ApiResponseError.HttpException.Code.NOT_FOUND) return VisaApiError.NotPaeraCustomer + if (value.code == ApiResponseError.HttpException.Code.UNAUTHORIZED) return VisaApiError.RefreshTokenExpired val errorBody = value.errorBody ?: return VisaApiError.UnknownWithoutCode return runCatching { diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRemoteDataSource.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRemoteDataSource.kt index 78d5cc47eb..ded2ed2d27 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRemoteDataSource.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaAuthRemoteDataSource.kt @@ -106,26 +106,6 @@ internal class DefaultVisaAuthRemoteDataSource @Inject constructor( } } - override suspend fun refreshCustomerWalletAuthTokens( - refreshToken: String, - ): Either = withContext(dispatchers.io) { - request { - tangemPayAuthApi.refreshCustomerWalletAccessToken( - request = RefreshCustomerWalletAccessTokenRequest( - authType = "customer_wallet", - refreshToken = refreshToken, - ), - ).getOrThrow() - }.map { response -> - TangemPayAuthTokens( - accessToken = response.accessToken, - expiresAt = response.expiresAt, - refreshToken = response.refreshToken, - refreshExpiresAt = response.refreshExpiresAt, - ) - } - } - override suspend fun getAccessTokens( signedChallenge: VisaAuthSignedChallenge, ): Either = withContext(dispatchers.io) { diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/datasource/TangemPayAuthDataSource.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/datasource/TangemPayAuthDataSource.kt index 0ff292096a..66a037381f 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/datasource/TangemPayAuthDataSource.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/datasource/TangemPayAuthDataSource.kt @@ -2,14 +2,11 @@ package com.tangem.domain.pay.datasource import arrow.core.Either import com.tangem.domain.pay.model.WithdrawalSignatureResult -import com.tangem.domain.visa.model.TangemPayAuthTokens import com.tangem.domain.visa.model.TangemPayInitialCredentials interface TangemPayAuthDataSource { suspend fun produceInitialCredentials(cardId: String): Either - suspend fun refreshAuthTokens(refreshToken: String): Either - suspend fun getWithdrawalSignature(cardId: String, hash: String): Either } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/datasource/VisaAuthRemoteDataSource.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/datasource/VisaAuthRemoteDataSource.kt index 835c2b07a4..c5cbbee4b9 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/visa/datasource/VisaAuthRemoteDataSource.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/datasource/VisaAuthRemoteDataSource.kt @@ -30,8 +30,6 @@ interface VisaAuthRemoteDataSource { nonce: String, ): Either - suspend fun refreshCustomerWalletAuthTokens(refreshToken: String): Either - suspend fun getAccessTokens(signedChallenge: VisaAuthSignedChallenge): Either suspend fun refreshAccessTokens(refreshToken: VisaAuthTokens.RefreshToken): Either