From 3d9e937ee90ac1a8a2a532185e677d1f88e76bf6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 17 Feb 2025 21:44:49 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../network/auth/DefaultVisaAuthProvider.kt | 19 --- .../tangem/tap/network/auth/di/AuthModule.kt | 13 -- .../api/common/visa/TangemVisaAuthProvider.kt | 6 - .../datasource/api/visa/TangemVisaApi.kt | 19 ++- .../datasource/api/visa/TangemVisaAuthApi.kt | 3 +- .../visa/models/response/VisaCustomerInfo.kt | 15 +++ .../models/response}/VisaTxHistoryResponse.kt | 2 +- .../data/visa/di/ImplementedVisaDataModule.kt | 25 ---- .../visa/DefaultVisaActivationRepository.kt | 6 + .../tangem/data/visa/DefaultVisaRepository.kt | 123 ++++++------------ .../data/visa/MockVisaActivationRepository.kt | 4 + ...isaRepository.kt => MockVisaRepository.kt} | 3 +- .../com/tangem/data/visa/config/VisaConfig.kt | 2 + .../tangem/data/visa/config/VisaLibLoader.kt | 32 +---- .../data/visa/di/ImplementedVisaRepository.kt | 6 - .../di/ImplementedVisaRepositoryModule.kt | 16 --- .../com/tangem/data/visa/di/VisaDataModule.kt | 27 ++-- .../tangem/data/visa/utils/CurrencyUtils.kt | 0 .../data/visa/utils/VisaApiRequestMaker.kt | 107 +++++++++++++++ .../tangem/data/visa/utils/VisaConstants.kt | 10 +- .../data/visa/utils/VisaCurrencyFactory.kt | 0 .../data/visa/utils/VisaTxDetailsFactory.kt | 2 +- .../visa/utils/VisaTxHistoryItemFactory.kt | 2 +- .../visa/utils/VisaTxHistoryPagingSource.kt | 6 +- .../domain/visa/SetVisaPinCodeUseCase.kt | 5 +- .../repository/VisaActivationRepository.kt | 2 + .../visa/DefaultVisaContractInfoProvider.kt | 48 ++++--- .../lib/visa/VisaContractInfoProvider.kt | 9 +- .../kotlin/com/tangem/lib/visa/api/VisaApi.kt | 18 --- .../com/tangem/lib/visa/api/VisaApiBuilder.kt | 72 ---------- 30 files changed, 258 insertions(+), 344 deletions(-) delete mode 100644 app/src/main/java/com/tangem/tap/network/auth/DefaultVisaAuthProvider.kt delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/common/visa/TangemVisaAuthProvider.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaCustomerInfo.kt rename {libs/visa/src/main/kotlin/com/tangem/lib/visa/model => core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response}/VisaTxHistoryResponse.kt (98%) delete mode 100644 data/visa/src/debug/kotlin/com/tangem/data/visa/di/ImplementedVisaDataModule.kt rename data/visa/src/{debug => main}/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt (65%) rename data/visa/src/main/kotlin/com/tangem/data/visa/{DummyVisaRepository.kt => MockVisaRepository.kt} (89%) rename data/visa/src/{debug => main}/kotlin/com/tangem/data/visa/config/VisaConfig.kt (92%) rename data/visa/src/{debug => main}/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt (68%) delete mode 100644 data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepository.kt delete mode 100644 data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepositoryModule.kt rename data/visa/src/{debug => main}/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt (100%) create mode 100644 data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt rename data/visa/src/{debug => main}/kotlin/com/tangem/data/visa/utils/VisaConstants.kt (86%) rename data/visa/src/{debug => main}/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt (100%) rename data/visa/src/{debug => main}/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt (97%) rename data/visa/src/{debug => main}/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt (89%) rename data/visa/src/{debug => main}/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt (92%) delete mode 100644 libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt delete mode 100644 libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApiBuilder.kt diff --git a/app/src/main/java/com/tangem/tap/network/auth/DefaultVisaAuthProvider.kt b/app/src/main/java/com/tangem/tap/network/auth/DefaultVisaAuthProvider.kt deleted file mode 100644 index 0a793b0222..0000000000 --- a/app/src/main/java/com/tangem/tap/network/auth/DefaultVisaAuthProvider.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.tangem.tap.network.auth - -import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider -import com.tangem.domain.visa.model.VisaCardActivationStatus -import com.tangem.domain.visa.model.getAuthHeader -import com.tangem.domain.wallets.legacy.UserWalletsListManager -import javax.inject.Inject - -internal class DefaultVisaAuthProvider @Inject constructor( - private val userWalletsListManager: UserWalletsListManager, -) : TangemVisaAuthProvider { - - override suspend fun getAuthHeader(cardId: String): String { - val card = userWalletsListManager.userWalletsSync.firstOrNull { it.cardId == cardId } - val status = card?.scanResponse?.visaCardActivationStatus as? VisaCardActivationStatus.Activated - ?: return "Error in the app!" - return status.visaAuthTokens.getAuthHeader() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt index 829f08d9cc..c72a9c145c 100644 --- a/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt +++ b/app/src/main/java/com/tangem/tap/network/auth/di/AuthModule.kt @@ -1,21 +1,17 @@ package com.tangem.tap.network.auth.di import com.tangem.datasource.api.common.AuthProvider -import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.lib.auth.StakeKitAuthProvider -import com.tangem.tap.network.auth.* import com.tangem.tap.network.auth.DefaultAppVersionProvider import com.tangem.tap.network.auth.DefaultAuthProvider import com.tangem.tap.network.auth.DefaultExpressAuthProvider import com.tangem.tap.network.auth.DefaultStakeKitAuthProvider -import com.tangem.tap.network.auth.DefaultVisaAuthProvider import com.tangem.utils.version.AppVersionProvider -import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -55,13 +51,4 @@ internal class AuthModule { fun provideAppVersionProvider(): AppVersionProvider { return DefaultAppVersionProvider() } -} - -@Module -@InstallIn(SingletonComponent::class) -internal interface AuthBindModule { - - @Binds - @Singleton - fun bindVisaAuthProvider(authStorage: DefaultVisaAuthProvider): TangemVisaAuthProvider } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/visa/TangemVisaAuthProvider.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/visa/TangemVisaAuthProvider.kt deleted file mode 100644 index 1f164d8411..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/visa/TangemVisaAuthProvider.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.datasource.api.common.visa - -interface TangemVisaAuthProvider { - - suspend fun getAuthHeader(cardId: String): String -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt index 802738fbd7..a0d08a74a1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaApi.kt @@ -5,9 +5,7 @@ import com.tangem.datasource.api.utils.ReadTimeout import com.tangem.datasource.api.visa.models.request.ActivationByCardWalletRequest import com.tangem.datasource.api.visa.models.request.ActivationByCustomerWalletRequest import com.tangem.datasource.api.visa.models.request.SetPinCodeRequest -import com.tangem.datasource.api.visa.models.response.CardActivationRemoteStateResponse -import com.tangem.datasource.api.visa.models.response.CardWalletDataToSignResponse -import com.tangem.datasource.api.visa.models.response.CustomerWalletDataToSignResponse +import com.tangem.datasource.api.visa.models.response.* import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header @@ -71,4 +69,19 @@ interface TangemVisaApi { @Header("Authorization") authHeader: String, @Body body: SetPinCodeRequest, ): ApiResponse + + @GET("customer/info") + suspend fun getCustomerInfo( + @Header("Authorization") authHeader: String, + @Query("card_id") cardId: String, + ): ApiResponse + + @GET("product_instance/transactions") + suspend fun getTxHistory( + @Header("Authorization") authHeader: String, + @Query("customer_id") customerId: String, + @Query("product_instance_id") productInstanceId: String, + @Query("limit") limit: Int, + @Query("offset") offset: Int, + ): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt index 1e200c63bd..b2ed8d19be 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/TangemVisaAuthApi.kt @@ -1,5 +1,6 @@ package com.tangem.datasource.api.visa +import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.visa.models.response.GenerateNonceResponse import com.tangem.datasource.api.visa.models.response.JWTResponse import retrofit2.http.Field @@ -27,5 +28,5 @@ interface TangemVisaAuthApi { ): JWTResponse @POST("auth/refresh_token") - suspend fun refreshAccessToken(@Field("refresh_token") refreshToken: String): JWTResponse + suspend fun refreshAccessToken(@Field("refresh_token") refreshToken: String): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaCustomerInfo.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaCustomerInfo.kt new file mode 100644 index 0000000000..70d5f6ac94 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaCustomerInfo.kt @@ -0,0 +1,15 @@ +package com.tangem.datasource.api.visa.models.response + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class VisaCustomerInfo( + @Json(name = "payment_accounts") val paymentAccounts: List, +) { + data class PaymentAccount( + @Json(name = "id") val id: String, + @Json(name = "customer_wallet_address") val customerWalletAddress: String, + @Json(name = "payment_account_address") val paymentAccountAddress: String, + ) +} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaTxHistoryResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaTxHistoryResponse.kt similarity index 98% rename from libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaTxHistoryResponse.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaTxHistoryResponse.kt index bf722ca7bf..7c4d7d0a22 100644 --- a/libs/visa/src/main/kotlin/com/tangem/lib/visa/model/VisaTxHistoryResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/visa/models/response/VisaTxHistoryResponse.kt @@ -1,4 +1,4 @@ -package com.tangem.lib.visa.model +package com.tangem.datasource.api.visa.models.response import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/di/ImplementedVisaDataModule.kt b/data/visa/src/debug/kotlin/com/tangem/data/visa/di/ImplementedVisaDataModule.kt deleted file mode 100644 index efb081bdc6..0000000000 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/di/ImplementedVisaDataModule.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.data.visa.di - -import com.tangem.data.common.cache.CacheRegistry -import com.tangem.data.visa.DefaultVisaRepository -import com.tangem.data.visa.config.VisaLibLoader -import com.tangem.datasource.api.tangemTech.TangemTechApi -import com.tangem.datasource.local.userwallet.UserWalletsStore -import com.tangem.domain.visa.repository.VisaRepository -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import dagger.Binds -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal interface ImplementedVisaDataModule { - - @Binds - @Singleton - @ImplementedVisaRepository - fun provideVisaRepository(impl: DefaultVisaRepository): VisaRepository -} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt index 4bb73eee50..b4bb976ce9 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaActivationRepository.kt @@ -1,5 +1,6 @@ package com.tangem.data.visa +import com.tangem.data.visa.config.VisaLibLoader import com.tangem.data.visa.converter.AccessCodeDataConverter import com.tangem.data.visa.converter.VisaActivationStatusConverter import com.tangem.datasource.api.common.response.ApiResponseError @@ -28,6 +29,7 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( private val visaAuthTokenStorage: VisaAuthTokenStorage, private val accessCodeDataConverter: AccessCodeDataConverter, private val visaAuthRepository: VisaAuthRepository, + private val visaLibLoader: VisaLibLoader, ) : VisaActivationRepository { override suspend fun getActivationRemoteState(): VisaActivationRemoteState = withContext(dispatcherProvider.io) { @@ -192,6 +194,10 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor( } } + override suspend fun getPinCodeRsaEncryptionPublicKey(): String { + return visaLibLoader.getOrCreateConfig().rsaPublicKey + } + private suspend fun request(requestBlock: suspend () -> T): T { return runCatching { requestBlock() diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt similarity index 65% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt index b7ce28f265..6a78abbf06 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/DefaultVisaRepository.kt @@ -12,21 +12,18 @@ import com.tangem.common.extensions.toHexString import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.visa.config.VisaLibLoader import com.tangem.data.visa.utils.* -import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.datasource.api.common.response.getOrThrow -import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.visa.TangemVisaApi +import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse import com.tangem.datasource.local.userwallet.UserWalletsStore import com.tangem.domain.common.util.cardTypesResolver -import com.tangem.domain.common.visa.VisaUtilities -import com.tangem.domain.visa.exception.RefreshTokenExpiredException -import com.tangem.domain.visa.model.* -import com.tangem.domain.visa.repository.VisaAuthRepository +import com.tangem.domain.visa.model.VisaCurrency +import com.tangem.domain.visa.model.VisaTxDetails +import com.tangem.domain.visa.model.VisaTxHistoryItem import com.tangem.domain.visa.repository.VisaRepository import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.lib.visa.api.VisaApi -import com.tangem.lib.visa.model.VisaTxHistoryResponse import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -35,8 +32,8 @@ import kotlinx.coroutines.withContext import java.math.BigDecimal import javax.inject.Inject import javax.inject.Singleton -import kotlin.jvm.Throws +@Suppress("LongParameterList") @Singleton internal class DefaultVisaRepository @Inject constructor( private val visaLibLoader: VisaLibLoader, @@ -44,8 +41,8 @@ internal class DefaultVisaRepository @Inject constructor( private val cacheRegistry: CacheRegistry, private val userWalletsStore: UserWalletsStore, private val dispatchers: CoroutineDispatcherProvider, - private val visaAuthProvider: TangemVisaAuthProvider, - private val visaAuthRepository: VisaAuthRepository, + private val visaApiRequestMaker: VisaApiRequestMaker, + private val visaApi: TangemVisaApi, ) : VisaRepository { private val currencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) { @@ -65,27 +62,32 @@ internal class DefaultVisaRepository @Inject constructor( override suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean): VisaCurrency { val address = makeAddress(userWalletId) - fetchVisaCurrencyIfExpired(address, isRefresh) + fetchVisaCurrencyIfExpired(userWalletId, address, isRefresh) return requireNotNull(fetchedCurrencies.value[address]) { "Unable to find VISA currency for $address" } } - private suspend fun fetchVisaCurrencyIfExpired(address: String, isRefresh: Boolean) { + private suspend fun fetchVisaCurrencyIfExpired(userWalletId: UserWalletId, address: String, isRefresh: Boolean) { cacheRegistry.invokeOnExpire( key = getVisaCurrencyKey(address), skipCache = isRefresh, - block = { fetchVisaCurrency(address) }, + block = { fetchVisaCurrency(userWalletId, address) }, ) } - private suspend fun fetchVisaCurrency(address: String) { + private suspend fun fetchVisaCurrency(userWalletId: UserWalletId, address: String) { val contractInfoProvider = visaLibLoader.getOrCreateProvider() parZip( dispatchers.io, - { contractInfoProvider.getContractInfo(address) }, + { + contractInfoProvider.getContractInfo( + walletAddress = address, + paymentAccountAddress = getPaymentAccountAddress(userWalletId), + ) + }, { getFiatRate() }, { contractInfo, fiatRate -> fetchedCurrencies.update { value -> @@ -104,7 +106,7 @@ internal class DefaultVisaRepository @Inject constructor( ): Flow> { val userWallet = findVisaUserWallet(userWalletId) val cardPubKey = getCardPubKey(userWallet) - val api = visaLibLoader.getOrCreateApi() + val pager = Pager( config = PagingConfig( pageSize = pageSize, @@ -121,7 +123,7 @@ internal class DefaultVisaRepository @Inject constructor( cacheRegistry = cacheRegistry, fetchedItems = fetchedHistoryItems, dispatchers = dispatchers, - requestTxHistory = { offset, pageSize -> getTxHistory(api, userWalletId, offset, pageSize) }, + requestTxHistory = { offset, pageSize -> getTxHistory(userWalletId, offset, pageSize) }, ) }, ) @@ -145,22 +147,31 @@ internal class DefaultVisaRepository @Inject constructor( } } - private suspend fun getTxHistory( - api: VisaApi, - userWalletId: UserWalletId, - offset: Int, - pageSize: Int, - ): VisaTxHistoryResponse = withContext(dispatchers.io) { + private suspend fun getPaymentAccountAddress(userWalletId: UserWalletId): String? = runCatching { val userWallet = findVisaUserWallet(userWalletId) - val cardPubKey = getCardPubKey(userWallet) - request(userWalletId = userWalletId) { - api.getTxHistory( - authorizationHeader = visaAuthProvider.getAuthHeader(userWallet.cardId), - cardPublicKey = cardPubKey, + val customerInfo = visaApiRequestMaker.request(userWalletId) { authHeader, _ -> + visaApi.getCustomerInfo( + authHeader = authHeader, + cardId = userWallet.scanResponse.card.cardId, + ) + } + + // TODO select correct account when multiple accounts are available (will be implemented when backend is ready) + customerInfo.paymentAccounts.firstOrNull()?.paymentAccountAddress + }.getOrNull() + + private suspend fun getTxHistory(userWalletId: UserWalletId, offset: Int, pageSize: Int): VisaTxHistoryResponse { + return visaApiRequestMaker.request( + userWalletId = userWalletId, + ) { authHeader, accessCodeData -> + visaApi.getTxHistory( + authHeader = authHeader, + customerId = accessCodeData.customerId, + productInstanceId = accessCodeData.productInstanceId, limit = pageSize, offset = offset, - ).getOrThrow() + ) } } @@ -217,56 +228,4 @@ internal class DefaultVisaRepository @Inject constructor( private fun getVisaCurrencyKey(address: String): String { return "visa_currency_$address" } - - private suspend fun request( - userWalletId: UserWalletId, - requestBlock: suspend () -> T, - ): T { - return runCatching { - requestBlock() - }.getOrElse { responseError -> - if (responseError !is ApiResponseError.HttpException || - responseError.code != ApiResponseError.HttpException.Code.UNAUTHORIZED - ) { - throw responseError - } - - val authTokens = getAuthTokens(userWalletId) - val newTokens = runCatching { - visaAuthRepository.refreshAccessTokens(authTokens.refreshToken) - }.getOrElse { - if (it is ApiResponseError.HttpException && - it.code == ApiResponseError.HttpException.Code.UNAUTHORIZED - ) { - userWalletsStore.update(userWalletId) { userWallet -> - userWallet.copy( - scanResponse = userWallet.scanResponse.copy( - visaCardActivationStatus = VisaCardActivationStatus.RefreshTokenExpired - ) - ) - } - } - throw RefreshTokenExpiredException() - } - - userWalletsStore.update(userWalletId) { userWallet -> - userWallet.copy( - scanResponse = userWallet.scanResponse.copy( - visaCardActivationStatus = VisaCardActivationStatus.Activated( - visaAuthTokens = newTokens - ) - ) - ) - } - - requestBlock() - } - } - - @Throws - private suspend fun getAuthTokens(userWalletId: UserWalletId): VisaAuthTokens { - val userWallet = findVisaUserWallet(userWalletId) - val status = userWallet.scanResponse.visaCardActivationStatus ?: error("Visa card activation status not found") - return (status as? VisaCardActivationStatus.Activated)?.visaAuthTokens ?: error("Visa card is not activated") - } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt index 8ebb4ae1b4..ed49e8187b 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaActivationRepository.kt @@ -45,6 +45,10 @@ class MockVisaActivationRepository @AssistedInject constructor( override suspend fun sendPinCode(pinCode: VisaEncryptedPinCode) {} + override suspend fun getPinCodeRsaEncryptionPublicKey(): String { + return CryptoUtils.generateRandomBytes(length = 32).toHexString() + } + @AssistedFactory interface Factory : VisaActivationRepository.Factory { override fun create(cardId: VisaCardId): MockVisaActivationRepository diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/DummyVisaRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaRepository.kt similarity index 89% rename from data/visa/src/main/kotlin/com/tangem/data/visa/DummyVisaRepository.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaRepository.kt index e965d71e93..87402d0554 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/DummyVisaRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/MockVisaRepository.kt @@ -7,8 +7,9 @@ import com.tangem.domain.visa.model.VisaTxHistoryItem import com.tangem.domain.visa.repository.VisaRepository import com.tangem.domain.wallets.models.UserWalletId import kotlinx.coroutines.flow.Flow +import javax.inject.Inject -internal class DummyVisaRepository : VisaRepository { +internal class MockVisaRepository @Inject constructor() : VisaRepository { override suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean): VisaCurrency { TODO("Not implemented for this build type") diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaConfig.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/config/VisaConfig.kt similarity index 92% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaConfig.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/config/VisaConfig.kt index 187f4ca9e6..2dd3b5566c 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaConfig.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/config/VisaConfig.kt @@ -11,6 +11,8 @@ internal data class VisaConfig( val mainnet: Addresses, @Json(name = "txHistoryAPIAdditionalHeaders") val header: Header, + @Json(name = "rsaPublicKey") + val rsaPublicKey: String, ) { @JsonClass(generateAdapter = true) diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt similarity index 68% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt index e5062cf662..471f56ea4e 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/config/VisaLibLoader.kt @@ -1,13 +1,9 @@ package com.tangem.data.visa.config -import com.squareup.moshi.Moshi import com.tangem.data.visa.BuildConfig import com.tangem.data.visa.utils.VisaConstants import com.tangem.datasource.asset.loader.AssetLoader -import com.tangem.datasource.di.NetworkMoshi import com.tangem.lib.visa.VisaContractInfoProvider -import com.tangem.lib.visa.api.VisaApi -import com.tangem.lib.visa.api.VisaApiBuilder import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -15,21 +11,19 @@ import javax.inject.Inject internal class VisaLibLoader @Inject constructor( private val assetLoader: AssetLoader, - @NetworkMoshi private val moshi: Moshi, private val dispatchers: CoroutineDispatcherProvider, ) { - private val createMutex = Mutex() + private val createMutex2 = Mutex() private var config: VisaConfig? = null private var provider: VisaContractInfoProvider? = null - private var api: VisaApi? = null + + suspend fun getOrCreateConfig(): VisaConfig = config ?: getOrLoadConfig() suspend fun getOrCreateProvider(): VisaContractInfoProvider = provider ?: createProvider() - suspend fun getOrCreateApi(): VisaApi = api ?: createApi() - private suspend fun createProvider(): VisaContractInfoProvider = createMutex.withLock { val config = getOrLoadConfig() @@ -54,24 +48,7 @@ internal class VisaLibLoader @Inject constructor( } } - private suspend fun createApi(): VisaApi = createMutex.withLock { - val config = getOrLoadConfig() - - api = VisaApiBuilder( - useDevApi = VisaConstants.USE_TEST_ENV, - isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED, - moshi = moshi, - headers = mapOf( - X_ASN_HEADER_NAME to config.header.xAsn, - ), - ).build() - - return requireNotNull(api) { - "Visa API is not created" - } - } - - private suspend fun getOrLoadConfig(): VisaConfig { + private suspend fun getOrLoadConfig(): VisaConfig = createMutex2.withLock { config = assetLoader.load(VISA_CONFIG_FILE_NAME) return requireNotNull(config) { @@ -81,6 +58,5 @@ internal class VisaLibLoader @Inject constructor( companion object { private const val VISA_CONFIG_FILE_NAME = "tangem-app-config/visa_config" - private const val X_ASN_HEADER_NAME = "x-asn" } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepository.kt deleted file mode 100644 index 074af6e72d..0000000000 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepository.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.data.visa.di - -import javax.inject.Qualifier - -@Qualifier -internal annotation class ImplementedVisaRepository \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepositoryModule.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepositoryModule.kt deleted file mode 100644 index 96de38914e..0000000000 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/di/ImplementedVisaRepositoryModule.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.data.visa.di - -import com.tangem.domain.visa.repository.VisaRepository -import dagger.BindsOptionalOf -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent - -@Module -@InstallIn(SingletonComponent::class) -internal interface ImplementedVisaRepositoryModule { - - @BindsOptionalOf - @ImplementedVisaRepository - fun bindImplementedVisaRepository(): VisaRepository -} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt index 5cfa0296bb..e1cc459371 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/di/VisaDataModule.kt @@ -1,36 +1,20 @@ package com.tangem.data.visa.di import com.tangem.data.visa.DefaultVisaAuthRepository -import com.tangem.data.visa.DummyVisaRepository +import com.tangem.data.visa.MockVisaRepository import com.tangem.data.visa.MockVisaActivationRepository import com.tangem.domain.visa.repository.VisaActivationRepository import com.tangem.domain.visa.repository.VisaAuthRepository import com.tangem.domain.visa.repository.VisaRepository import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import java.util.Optional import javax.inject.Singleton -import kotlin.jvm.optionals.getOrNull @Module @InstallIn(SingletonComponent::class) -internal object VisaDataModule { - - @Provides - @Singleton - fun provideVisaRepository( - @ImplementedVisaRepository implementedVisaRepository: Optional, - ): VisaRepository { - return implementedVisaRepository.getOrNull() ?: DummyVisaRepository() - } -} - -@Module -@InstallIn(SingletonComponent::class) -internal interface VisaDataBindsModule { +internal interface VisaDataModule { @Binds @Singleton @@ -48,4 +32,11 @@ internal interface VisaDataBindsModule { fun bindVisaActivationRepositoryFactory( repository: MockVisaActivationRepository.Factory, ): VisaActivationRepository.Factory + + // @Binds + // fun bindVisaRepository(repository: DefaultVisaRepository): VisaRepository + + // Mocked + @Binds + fun bindVisaRepository(repository: MockVisaRepository): VisaRepository } \ No newline at end of file diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt similarity index 100% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/utils/CurrencyUtils.kt diff --git a/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt new file mode 100644 index 0000000000..d06d1fb0de --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaApiRequestMaker.kt @@ -0,0 +1,107 @@ +package com.tangem.data.visa.utils + +import com.tangem.data.visa.converter.AccessCodeDataConverter +import com.tangem.data.visa.model.AccessCodeData +import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.common.response.ApiResponseError +import com.tangem.datasource.api.common.response.getOrThrow +import com.tangem.datasource.api.visa.TangemVisaAuthApi +import com.tangem.datasource.local.userwallet.UserWalletsStore +import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.visa.exception.RefreshTokenExpiredException +import com.tangem.domain.visa.model.VisaAuthTokens +import com.tangem.domain.visa.model.VisaCardActivationStatus +import com.tangem.domain.visa.model.getAuthHeader +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext +import javax.inject.Inject +import kotlin.jvm.Throws + +typealias VisaAuthorizationHeader = String + +internal class VisaApiRequestMaker @Inject constructor( + private val userWalletsStore: UserWalletsStore, + private val visaAuthApi: TangemVisaAuthApi, + private val accessCodeDataConverter: AccessCodeDataConverter, + private val dispatcherProvider: CoroutineDispatcherProvider, +) { + suspend fun request( + userWalletId: UserWalletId, + requestBlock: suspend (header: VisaAuthorizationHeader, accessCodeData: AccessCodeData) -> ApiResponse, + ): T = withContext(dispatcherProvider.io) { + val authTokens = getAuthTokens(userWalletId) + val authHeader = authTokens.getAuthHeader() + val accessCodeData = accessCodeDataConverter.convert(authTokens) + + runCatching { + requestBlock(authHeader, accessCodeData).getOrThrow() + }.getOrElse { responseError -> + if (responseError !is ApiResponseError.HttpException || + responseError.code != ApiResponseError.HttpException.Code.UNAUTHORIZED + ) { + throw responseError + } + + val newTokens = runCatching { + refreshAccessTokens(authTokens.refreshToken) + }.getOrElse { + if (it is ApiResponseError.HttpException && + it.code == ApiResponseError.HttpException.Code.UNAUTHORIZED + ) { + userWalletsStore.update(userWalletId) { userWallet -> + userWallet.copy( + scanResponse = userWallet.scanResponse.copy( + visaCardActivationStatus = VisaCardActivationStatus.RefreshTokenExpired, + ), + ) + } + } + throw RefreshTokenExpiredException() + } + + userWalletsStore.update(userWalletId) { userWallet -> + userWallet.copy( + scanResponse = userWallet.scanResponse.copy( + visaCardActivationStatus = VisaCardActivationStatus.Activated( + visaAuthTokens = newTokens, + ), + ), + ) + } + + val newAuthHeader = newTokens.getAuthHeader() + val newAccessCodeData = accessCodeDataConverter.convert(newTokens) + + requestBlock(newAuthHeader, newAccessCodeData).getOrThrow() + } + } + + private suspend fun refreshAccessTokens(refreshToken: VisaAuthTokens.RefreshToken): VisaAuthTokens { + val result = visaAuthApi.refreshAccessToken(refreshToken.value).getOrThrow() + + return VisaAuthTokens( + accessToken = result.accessToken, + refreshToken = VisaAuthTokens.RefreshToken(result.refreshToken), + ) + } + + @Throws + private suspend fun getAuthTokens(userWalletId: UserWalletId): VisaAuthTokens { + val userWallet = findVisaUserWallet(userWalletId) + val status = userWallet.scanResponse.visaCardActivationStatus ?: error("Visa card activation status not found") + return (status as? VisaCardActivationStatus.Activated)?.visaAuthTokens ?: error("Visa card is not activated") + } + + private suspend fun findVisaUserWallet(userWalletId: UserWalletId): UserWallet { + val userWallet = requireNotNull(userWalletsStore.getSyncOrNull(userWalletId)) { + "No user wallet found: $userWalletId" + } + if (!userWallet.scanResponse.cardTypesResolver.isVisaWallet()) { + error("VISA wallet required: $userWalletId") + } + + return userWallet + } +} \ No newline at end of file diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaConstants.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaConstants.kt similarity index 86% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaConstants.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaConstants.kt index 27cd336588..1a6f0a142a 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaConstants.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaConstants.kt @@ -15,9 +15,9 @@ internal object VisaConstants { ) /* - * Must be `false` in production - * Don't forget to change CardTypesResolver.isVisaWallet - * */ + * Must be `false` in production + * Don't forget to change CardTypesResolver.isVisaWallet + * */ const val IS_DEMO_MODE_ENABLED = false const val USE_TEST_ENV = true @@ -40,5 +40,7 @@ internal fun getDemoAddress(): String { internal fun getDemoPublicKey(): String { return if (VisaConstants.USE_TEST_ENV) { VisaConstants.DEMO_TESTNET_PUBLIC_KEY - } else VisaConstants.DEMO_MAINNET_PUBLIC_KEY + } else { + VisaConstants.DEMO_MAINNET_PUBLIC_KEY + } } \ No newline at end of file diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt similarity index 100% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaCurrencyFactory.kt diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt similarity index 97% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt index 5108e0387e..c69adf5664 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxDetailsFactory.kt @@ -2,8 +2,8 @@ package com.tangem.data.visa.utils import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.externallinkprovider.TxExploreState +import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse import com.tangem.domain.visa.model.VisaTxDetails -import com.tangem.lib.visa.model.VisaTxHistoryResponse internal class VisaTxDetailsFactory { diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt similarity index 89% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt index 86d1439068..75a8c4eefe 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryItemFactory.kt @@ -1,7 +1,7 @@ package com.tangem.data.visa.utils +import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse import com.tangem.domain.visa.model.VisaTxHistoryItem -import com.tangem.lib.visa.model.VisaTxHistoryResponse internal class VisaTxHistoryItemFactory { diff --git a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt similarity index 92% rename from data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt rename to data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt index e233435e55..4393d4f372 100644 --- a/data/visa/src/debug/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/visa/utils/VisaTxHistoryPagingSource.kt @@ -3,13 +3,9 @@ package com.tangem.data.visa.utils import androidx.paging.PagingSource import androidx.paging.PagingState import com.tangem.data.common.cache.CacheRegistry -import com.tangem.datasource.api.common.response.ApiResponseError -import com.tangem.datasource.api.common.response.getOrThrow -import com.tangem.datasource.api.common.visa.TangemVisaAuthProvider +import com.tangem.datasource.api.visa.models.response.VisaTxHistoryResponse import com.tangem.domain.visa.model.VisaTxHistoryItem import com.tangem.domain.wallets.models.UserWallet -import com.tangem.lib.visa.api.VisaApi -import com.tangem.lib.visa.model.VisaTxHistoryResponse import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt index 7b05cf26b8..6fda7af7df 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/SetVisaPinCodeUseCase.kt @@ -18,8 +18,6 @@ import javax.crypto.spec.SecretKeySpec private const val KEY_SIZE = 256 -private const val RSA_PUB_KEY = "TODO" // TODO add RSA public key [REDACTED_TASK_KEY] - class SetVisaPinCodeUseCase( private val visaActivationRepositoryFactory: VisaActivationRepository.Factory, ) { @@ -30,9 +28,10 @@ class SetVisaPinCodeUseCase( pinCode: String, ): Either = Either.catch { val visaActivationRepository = visaActivationRepositoryFactory.create(visaCardId) + val rsaPublicKey = visaActivationRepository.getPinCodeRsaEncryptionPublicKey() val sessionKey = generateSessionKey() - val sessionId = getSessionId(RSA_PUB_KEY, sessionKey) + val sessionId = getSessionId(rsaPublicKey, sessionKey) val secureRandom = SecureRandom() val iv = ByteArray(size = 16) diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt index 5f7959f423..f6d451fb7b 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/visa/repository/VisaActivationRepository.kt @@ -20,6 +20,8 @@ interface VisaActivationRepository { suspend fun sendPinCode(pinCode: VisaEncryptedPinCode) + suspend fun getPinCodeRsaEncryptionPublicKey(): String + interface Factory { fun create(cardId: VisaCardId): VisaActivationRepository } diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/DefaultVisaContractInfoProvider.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/DefaultVisaContractInfoProvider.kt index 2265f6258f..395904554e 100644 --- a/libs/visa/src/main/kotlin/com/tangem/lib/visa/DefaultVisaContractInfoProvider.kt +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/DefaultVisaContractInfoProvider.kt @@ -20,27 +20,38 @@ internal class DefaultVisaContractInfoProvider( private val dispatchers: CoroutineDispatcherProvider, ) : VisaContractInfoProvider { - override suspend fun getContractInfo(walletAddress: String): VisaContractInfo { + override suspend fun getContractInfo(walletAddress: String, paymentAccountAddress: String?): VisaContractInfo { return parZip( dispatchers.io, - { loadPaymentAccount(walletAddress) }, + { loadPaymentAccount(walletAddress = walletAddress, paymentAccountAddress = paymentAccountAddress) }, { loadPaymentTokenInfo() }, { paymentAccount, paymentToken -> - fetchBalancesAndLimits(paymentAccount, paymentToken) + fetchBalancesAndLimits( + paymentAccount = paymentAccount, + paymentToken = paymentToken, + walletAddress = walletAddress, + ) }, ) } - private fun loadPaymentAccount(walletAddress: String): TangemPaymentAccount { + private fun loadPaymentAccount(walletAddress: String, paymentAccountAddress: String?): TangemPaymentAccount { + return TangemPaymentAccount.load( + /* contractAddress = */ paymentAccountAddress ?: getPaymentAccountAddressFromRegistry(walletAddress), + /* web3j = */ web3j, + /* transactionManager = */ transactionManager, + /* contractGasProvider = */ gasProvider, + ) + } + + private fun getPaymentAccountAddressFromRegistry(walletAddress: String): String { val paymentAccountRegistry = TangemPaymentAccountRegistry.load( /* contractAddress = */ paymentAccountRegistryAddress, /* web3j = */ web3j, /* transactionManager = */ transactionManager, /* contractGasProvider = */ gasProvider, ) - val paymentAccountAddress = paymentAccountRegistry.paymentAccountByCard(walletAddress).send() - - return TangemPaymentAccount.load(paymentAccountAddress, web3j, transactionManager, gasProvider) + return paymentAccountRegistry.paymentAccountByCard(walletAddress).send() } private fun loadPaymentTokenInfo(): PaymentTokenInfo { @@ -63,11 +74,12 @@ internal class DefaultVisaContractInfoProvider( private suspend fun fetchBalancesAndLimits( paymentAccount: TangemPaymentAccount, paymentToken: PaymentTokenInfo, + walletAddress: String, ): VisaContractInfo = parZip( dispatchers.io, { fetchToken(paymentAccount) }, { fetchBalances(paymentAccount, paymentToken) }, - { fetchLimits(paymentAccount, paymentToken) }, + { fetchLimits(paymentAccount, paymentToken, walletAddress) }, { token, balances, (oldLimit, newLimit, changeDate) -> VisaContractInfo(token, balances, oldLimit, newLimit, changeDate) }, @@ -123,19 +135,15 @@ internal class DefaultVisaContractInfoProvider( private fun fetchLimits( paymentAccount: TangemPaymentAccount, paymentToken: PaymentTokenInfo, + walletAddress: String, ): Triple { - TODO() // TODO: [REDACTED_TASK_KEY] - // val ( - // oldLimit, - // newLimit, - // changeDateSeconds, - // ) = paymentAccount.cards("").send().component5() - // - // return Triple( - // first = getLimits(oldLimit, paymentToken), - // second = getLimits(newLimit, paymentToken), - // third = changeDateSeconds.toInstant(), - // ) + val limits = paymentAccount.cards(walletAddress).send().component5() + + return Triple( + first = getLimits(limits.oldValue, paymentToken), + second = getLimits(limits.newValue, paymentToken), + third = limits.changeTimestamp.toInstant(), + ) } @Suppress("UnusedPrivateMember") diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/VisaContractInfoProvider.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/VisaContractInfoProvider.kt index 4696b698fb..4587bfabf1 100644 --- a/libs/visa/src/main/kotlin/com/tangem/lib/visa/VisaContractInfoProvider.kt +++ b/libs/visa/src/main/kotlin/com/tangem/lib/visa/VisaContractInfoProvider.kt @@ -21,7 +21,14 @@ import java.util.concurrent.TimeUnit interface VisaContractInfoProvider { - suspend fun getContractInfo(walletAddress: String): VisaContractInfo + /** + * Fetches Visa contract info for the given wallet address. + * + * @param walletAddress Wallet address to fetch contract info for. + * @param paymentAccountAddress Payment account address to fetch data from. If null, + * it will be fetched from the registry. + */ + suspend fun getContractInfo(walletAddress: String, paymentAccountAddress: String?): VisaContractInfo class Builder( private val useTestnetRpc: Boolean, diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt deleted file mode 100644 index 106ced7a60..0000000000 --- a/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.tangem.lib.visa.api - -import com.tangem.datasource.api.common.response.ApiResponse -import com.tangem.lib.visa.model.VisaTxHistoryResponse -import retrofit2.http.GET -import retrofit2.http.Header -import retrofit2.http.Query - -interface VisaApi { - - @GET("transaction") - suspend fun getTxHistory( - @Header("Authorization") authorizationHeader: String, - @Query("card_public_key") cardPublicKey: String, - @Query("limit") limit: Int, - @Query("offset") offset: Int, - ): ApiResponse -} \ No newline at end of file diff --git a/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApiBuilder.kt b/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApiBuilder.kt deleted file mode 100644 index ef1a99dd5f..0000000000 --- a/libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApiBuilder.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.tangem.lib.visa.api - -import android.util.Log -import com.ihsanbal.logging.Level -import com.ihsanbal.logging.LoggingInterceptor -import com.squareup.moshi.Moshi -import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory -import com.tangem.lib.visa.utils.Constants -import com.tangem.lib.visa.utils.Constants.NETWORK_LOGS_TAG -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory -import java.util.concurrent.TimeUnit - -class VisaApiBuilder( - private val useDevApi: Boolean, - private val isNetworkLoggingEnabled: Boolean, - private val moshi: Moshi, - private val headers: Map, - private val networkTimeoutSeconds: Long = Constants.NETWORK_TIMEOUT_SECONDS, -) { - - fun build(): VisaApi { - val okHttpClient = createOkHttpClient() - val retrofit = createRetrofit(okHttpClient) - return retrofit.create(VisaApi::class.java) - } - - private fun createOkHttpClient(): OkHttpClient { - val builder = OkHttpClient.Builder().apply { - connectTimeout(networkTimeoutSeconds, TimeUnit.SECONDS) - readTimeout(networkTimeoutSeconds, TimeUnit.SECONDS) - writeTimeout(networkTimeoutSeconds, TimeUnit.SECONDS) - - if (isNetworkLoggingEnabled) { - addInterceptor(createNetworkLoggingInterceptor()) - } - - if (headers.isNotEmpty()) { - addInterceptor { chain -> - val request = chain.request().newBuilder().apply { - headers.forEach { (key, value) -> addHeader(key, value) } - }.build() - - chain.proceed(request) - } - } - } - - return builder.build() - } - - private fun createRetrofit(okHttpClient: OkHttpClient): Retrofit { - val baseUrl = if (useDevApi) Constants.VISA_API_DEV_URL else Constants.VISA_API_PROD_URL - - return Retrofit.Builder() - .addConverterFactory(MoshiConverterFactory.create(moshi)) - .addCallAdapterFactory(ApiResponseCallAdapterFactory.create()) - .baseUrl(baseUrl) - .client(okHttpClient) - .build() - } -} - -private fun createNetworkLoggingInterceptor(): Interceptor { - return LoggingInterceptor.Builder() - .setLevel(Level.BODY) - .log(Log.VERBOSE) - .tag(NETWORK_LOGS_TAG) - .build() -} \ No newline at end of file