Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-19 14:21:03 +05:00
parent 8f2c28715a
commit 0741b83490
13 changed files with 107 additions and 98 deletions

View file

@ -40,8 +40,8 @@ sealed class ApiConfig {
is TangemTech -> ID.TangemTech
is StakeKit -> ID.StakeKit
is P2PEthPool -> ID.P2PEthPool
is TangemPay -> ID.TangemPay
is TangemPayAuth -> ID.TangemPayAuth
is TangemPay.Bff -> ID.TangemPay
is TangemPay.Auth -> ID.TangemPayAuth
is BlockAid -> ID.BlockAid
is YieldSupply -> ID.YieldSupply
is MoonPay -> ID.MoonPay

View file

@ -1,11 +1,13 @@
package com.tangem.datasource.api.common.config
import com.tangem.datasource.BuildConfig
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.utils.ProviderSuspend
import com.tangem.utils.version.AppVersionProvider
internal class TangemPay(
internal sealed class TangemPay(
private val appVersionProvider: AppVersionProvider,
private val environmentConfigStorage: EnvironmentConfigStorage,
) : ApiConfig() {
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
@ -16,6 +18,8 @@ internal class TangemPay(
createProdEnvironment(),
)
protected abstract fun getBaseUrl(apiEnvironment: ApiEnvironment): String
private fun getInitialEnvironment(): ApiEnvironment {
return when (BuildConfig.BUILD_TYPE) {
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
@ -31,24 +35,75 @@ internal class TangemPay(
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.DEV,
baseUrl = "https://api.dev.us.paera.com/bff-v2/",
headers = createHeaders(),
baseUrl = getBaseUrl(ApiEnvironment.DEV),
headers = createHeaders(ApiEnvironment.DEV),
)
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.MOCK,
baseUrl = "[REDACTED_ENV_URL]",
headers = createHeaders(),
baseUrl = getBaseUrl(ApiEnvironment.MOCK),
headers = createHeaders(ApiEnvironment.MOCK),
)
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.PROD,
baseUrl = "https://api.us.paera.com/bff-v2/",
headers = createHeaders(),
baseUrl = getBaseUrl(ApiEnvironment.PROD),
headers = createHeaders(ApiEnvironment.PROD),
)
private fun createHeaders() = mapOf(
private fun createHeaders(apiEnvironment: ApiEnvironment) = mapOf(
"version" to ProviderSuspend { appVersionProvider.versionName },
"platform" to ProviderSuspend { "Android" },
"X-API-KEY" to ProviderSuspend { getBffStaticToken(apiEnvironment) },
)
private fun getBffStaticToken(apiEnvironment: ApiEnvironment): String {
return when (apiEnvironment) {
ApiEnvironment.MOCK,
ApiEnvironment.DEV,
-> environmentConfigStorage.getConfigSync().bffStaticTokenDev
ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().bffStaticToken
ApiEnvironment.STAGE,
ApiEnvironment.STAGE_2,
ApiEnvironment.DEV_2,
ApiEnvironment.DEV_3,
-> null
} ?: error("BffStaticToken is not provided for $apiEnvironment")
}
class Bff(
appVersionProvider: AppVersionProvider,
environmentConfigStorage: EnvironmentConfigStorage,
) : TangemPay(appVersionProvider, environmentConfigStorage) {
override fun getBaseUrl(apiEnvironment: ApiEnvironment): String {
return when (apiEnvironment) {
ApiEnvironment.DEV -> "https://api.dev.us.paera.com/bff-v2/"
ApiEnvironment.MOCK -> "[REDACTED_ENV_URL]"
ApiEnvironment.PROD -> "https://api.us.paera.com/bff-v2/"
ApiEnvironment.DEV_2,
ApiEnvironment.DEV_3,
ApiEnvironment.STAGE,
ApiEnvironment.STAGE_2,
-> error("Unknown environment: $apiEnvironment")
}
}
}
class Auth(
appVersionProvider: AppVersionProvider,
environmentConfigStorage: EnvironmentConfigStorage,
) : TangemPay(appVersionProvider, environmentConfigStorage) {
override fun getBaseUrl(apiEnvironment: ApiEnvironment): String {
return when (apiEnvironment) {
ApiEnvironment.DEV -> "https://api.dev.us.paera.com/"
ApiEnvironment.MOCK -> "[REDACTED_ENV_URL]"
ApiEnvironment.PROD -> "https://api.us.paera.com/"
ApiEnvironment.DEV_2,
ApiEnvironment.DEV_3,
ApiEnvironment.STAGE,
ApiEnvironment.STAGE_2,
-> error("Unknown environment: $apiEnvironment")
}
}
}
}

View file

@ -1,53 +0,0 @@
package com.tangem.datasource.api.common.config
import com.tangem.datasource.BuildConfig
import com.tangem.utils.ProviderSuspend
import com.tangem.utils.version.AppVersionProvider
internal class TangemPayAuth(
private val appVersionProvider: AppVersionProvider,
) : ApiConfig() {
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
override val environmentConfigs = listOf(
createDevEnvironment(),
createMockedEnvironment(),
createProdEnvironment(),
)
private fun getInitialEnvironment(): ApiEnvironment {
return when (BuildConfig.BUILD_TYPE) {
MOCKED_BUILD_TYPE -> ApiEnvironment.MOCK
DEBUG_BUILD_TYPE,
INTERNAL_BUILD_TYPE,
-> ApiEnvironment.DEV
EXTERNAL_BUILD_TYPE,
RELEASE_BUILD_TYPE,
-> ApiEnvironment.PROD
else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]")
}
}
private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.DEV,
baseUrl = "https://api.dev.us.paera.com/",
headers = createHeaders(),
)
private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.MOCK,
baseUrl = "[REDACTED_ENV_URL]",
headers = createHeaders(),
)
private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig(
environment = ApiEnvironment.PROD,
baseUrl = "https://api.us.paera.com/",
headers = createHeaders(),
)
private fun createHeaders() = mapOf(
"version" to ProviderSuspend { appVersionProvider.versionName },
"platform" to ProviderSuspend { "Android" },
)
}

View file

@ -25,7 +25,6 @@ interface TangemPayApi {
@GET("v1/customer/wallets/{customer_wallet_id}")
suspend fun checkCustomerWalletId(
@Header("X-API-KEY") authHeader: String,
@Path("customer_wallet_id") customerWalletId: String,
): ApiResponse<CheckCustomerWalletResponse>

View file

@ -86,13 +86,17 @@ internal object ApiConfigsModule {
@Provides
@IntoSet
fun provideTangemVisaConfig(appVersionProvider: AppVersionProvider): ApiConfig = TangemPay(appVersionProvider)
fun provideTangemPayBffConfig(
appVersionProvider: AppVersionProvider,
environmentConfigStorage: EnvironmentConfigStorage,
): ApiConfig = TangemPay.Bff(appVersionProvider, environmentConfigStorage)
@Provides
@IntoSet
fun provideTangemPayAuthConfig(appVersionProvider: AppVersionProvider): ApiConfig = TangemPayAuth(
appVersionProvider,
)
fun provideTangemPayAuthConfig(
appVersionProvider: AppVersionProvider,
environmentConfigStorage: EnvironmentConfigStorage,
): ApiConfig = TangemPay.Auth(appVersionProvider, environmentConfigStorage)
@Provides
@IntoSet

View file

@ -25,6 +25,7 @@ data class EnvironmentConfig(
val yieldModuleApiKey: String? = null,
val yieldModuleApiKeyDev: String? = null,
val bffStaticToken: String? = null,
val bffStaticTokenDev: String? = null,
val gaslessTxApiKeyDev: String? = null,
val gaslessTxApiKey: String? = null,
)

View file

@ -33,6 +33,7 @@ internal object EnvironmentConfigConverter : Converter<EnvironmentConfigModel, E
yieldModuleApiKey = value.yieldModuleApiKey,
yieldModuleApiKeyDev = value.yieldModuleApiKeyDev,
bffStaticToken = value.bffStaticToken,
bffStaticTokenDev = value.bffStaticTokenDev,
gaslessTxApiKeyDev = value.gaslessTxApiKeyDev,
gaslessTxApiKey = value.gaslessTxApiKey,
)

View file

@ -52,6 +52,7 @@ class EnvironmentConfigModel(
@Json(name = "blinkApiKey") val blinkApiKey: String?,
@Json(name = "tatumApiKey") val tatumApiKey: String?,
@Json(name = "bffStaticToken") val bffStaticToken: String?,
@Json(name = "bffStaticTokenDev") val bffStaticTokenDev: String?,
@Json(name = "gaslessTxApiKeyDev") val gaslessTxApiKeyDev: String?,
@Json(name = "gaslessTxApiKey") val gaslessTxApiKey: String?,
)

View file

@ -69,7 +69,14 @@ class ApiConfigTest {
)
}
ApiConfig.ID.StakeKit -> StakeKit(stakeKitAuthProvider = mockk())
ApiConfig.ID.TangemPay -> TangemPay(appVersionProvider = mockk())
ApiConfig.ID.TangemPay -> TangemPay.Bff(
appVersionProvider = mockk(),
environmentConfigStorage = mockk()
)
ApiConfig.ID.TangemPayAuth -> TangemPay.Auth(
appVersionProvider = mockk(),
environmentConfigStorage = mockk()
)
ApiConfig.ID.BlockAid -> BlockAid(configStorage = mockk())
ApiConfig.ID.MoonPay -> MoonPay()
ApiConfig.ID.P2PEthPool -> P2PEthPool(p2pAuthProvider = mockk())
@ -78,7 +85,6 @@ class ApiConfigTest {
authProvider = appAuthProvider,
appInfoProvider = mockk(),
)
ApiConfig.ID.TangemPayAuth -> TangemPayAuth(appVersionProvider = mockk())
ApiConfig.ID.GaslessTxService -> GaslessTxService(
authProvider = appAuthProvider,
appVersionProvider = mockk(),

View file

@ -18,6 +18,8 @@ internal class MockEnvironmentConfigStorage : EnvironmentConfigStorage {
blockAidApiKey = BLOCK_AID_API_KEY,
tangemApiKey = TANGEM_API_KEY,
tangemApiKeyDev = TANGEM_API_KEY_DEV,
bffStaticToken = TANGEM_PAY_BFF_KEY,
bffStaticTokenDev = TANGEM_PAY_BFF_KEY_DEV,
tangemApiKeyStage = TANGEM_API_KEY_STAGE,
yieldModuleApiKey = YIELD_MODULE_KEY,
yieldModuleApiKeyDev = YIELD_MODULE_KEY_DEV,
@ -33,6 +35,8 @@ internal class MockEnvironmentConfigStorage : EnvironmentConfigStorage {
const val BLOCK_AID_API_KEY = "block_aid_api_key"
const val TANGEM_API_KEY = "tangem_api_key"
const val TANGEM_API_KEY_DEV = "tangem_api_key_dev"
const val TANGEM_PAY_BFF_KEY = "tangem_pay_bff_key"
const val TANGEM_PAY_BFF_KEY_DEV = "tangem_pay_bff_key_dev"
const val TANGEM_GASLESS_API_KEY = "tangem_gasless_api_key"
const val TANGEM_API_KEY_STAGE = "tangem_api_key_stage"
const val YIELD_MODULE_KEY = "yield_module_api_key"

View file

@ -13,6 +13,7 @@ import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.BLOCK_AID_API_KEY
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.TANGEM_API_KEY
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.TANGEM_GASLESS_API_KEY
import com.tangem.datasource.api.common.config.managers.MockEnvironmentConfigStorage.Companion.TANGEM_PAY_BFF_KEY_DEV
import com.tangem.domain.staking.model.ethpool.P2PEthPoolStakingConfig
import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.P2PEthPoolAuthProvider
@ -115,7 +116,14 @@ internal class ProdApiConfigsManagerTest {
)
}
ApiConfig.ID.StakeKit -> StakeKit(stakeKitAuthProvider = stakeKitAuthProvider)
ApiConfig.ID.TangemPay -> TangemPay(appVersionProvider = appVersionProvider)
ApiConfig.ID.TangemPay -> TangemPay.Bff(
appVersionProvider = appVersionProvider,
environmentConfigStorage = environmentConfigStorage,
)
ApiConfig.ID.TangemPayAuth -> TangemPay.Auth(
appVersionProvider = appVersionProvider,
environmentConfigStorage = environmentConfigStorage,
)
ApiConfig.ID.BlockAid -> BlockAid(configStorage = environmentConfigStorage)
ApiConfig.ID.MoonPay -> MoonPay()
ApiConfig.ID.P2PEthPool -> P2PEthPool(p2pAuthProvider = p2pEthPoolAuthProvider)
@ -124,7 +132,6 @@ internal class ProdApiConfigsManagerTest {
authProvider = appAuthProvider,
appInfoProvider = appInfoProvider,
)
ApiConfig.ID.TangemPayAuth -> TangemPayAuth(appVersionProvider = appVersionProvider)
ApiConfig.ID.GaslessTxService -> GaslessTxService(
authProvider = appAuthProvider,
appVersionProvider = appVersionProvider,
@ -141,11 +148,11 @@ internal class ProdApiConfigsManagerTest {
ApiConfig.ID.TangemTech -> createTangemTechModel()
ApiConfig.ID.StakeKit -> createStakeKitModel()
ApiConfig.ID.TangemPay -> createTangemPayModel()
ApiConfig.ID.TangemPayAuth -> createTangemPayAuthModel()
ApiConfig.ID.BlockAid -> createBlockAidSdkModel()
ApiConfig.ID.MoonPay -> createMoonPayModel()
ApiConfig.ID.P2PEthPool -> createP2PModel()
ApiConfig.ID.News -> createNewsModel()
ApiConfig.ID.TangemPayAuth -> createTangemPayAuthModel()
ApiConfig.ID.GaslessTxService -> createGaslessTxServiceModel()
}
}
@ -269,6 +276,7 @@ internal class ProdApiConfigsManagerTest {
headers = mapOf(
"version" to ProviderSuspend { VERSION_NAME },
"platform" to ProviderSuspend { "Android" },
"X-API-KEY" to ProviderSuspend { TANGEM_PAY_BFF_KEY_DEV },
),
),
)
@ -283,6 +291,7 @@ internal class ProdApiConfigsManagerTest {
headers = mapOf(
"version" to ProviderSuspend { VERSION_NAME },
"platform" to ProviderSuspend { "Android" },
"X-API-KEY" to ProviderSuspend { TANGEM_PAY_BFF_KEY_DEV },
),
),
)

View file

@ -185,11 +185,8 @@ internal class DefaultOnboardingRepository @Inject constructor(
return Either.Right(hasTangemPay)
}
return requestHelper.performWithStaticToken { staticToken ->
tangemPayApi.checkCustomerWalletId(
authHeader = staticToken,
customerWalletId = userWalletId.stringValue,
)
return requestHelper.performWithStaticToken {
tangemPayApi.checkCustomerWalletId(customerWalletId = userWalletId.stringValue)
}.map { response ->
val id = response.result?.id
val isTangemPayEnabled = response.result?.isTangemPayEnabled == true
@ -205,7 +202,7 @@ internal class DefaultOnboardingRepository @Inject constructor(
}
override suspend fun checkCustomerEligibility(): Boolean {
val response = requestHelper.performWithoutToken {
val response = requestHelper.performWithStaticToken {
tangemPayApi.checkCustomerEligibility()
}.getOrNull()

View file

@ -11,7 +11,6 @@ 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.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.local.visa.TangemPayStorage
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.visa.error.VisaApiError
@ -31,7 +30,6 @@ private const val TAG = "TangemPayRequestPerformer"
@Singleton
internal class TangemPayRequestPerformer @Inject constructor(
private val errorConverter: TangemPayErrorConverter,
private val environmentConfigStorage: EnvironmentConfigStorage,
private val dispatchers: CoroutineDispatcherProvider,
private val tangemPayAuthApi: TangemPayAuthApi,
private val tangemPayStorage: TangemPayStorage,
@ -40,23 +38,10 @@ internal class TangemPayRequestPerformer @Inject constructor(
private val customerWalletAddresses = ConcurrentHashMap<UserWalletId, String>()
private val tokensMutex = Mutex()
suspend fun <T : Any> performWithStaticToken(
requestBlock: suspend (header: String) -> ApiResponse<T>,
): Either<VisaApiError, T> = withContext(dispatchers.io) {
catch(
block = {
val staticToken =
environmentConfigStorage.getConfigSync().bffStaticToken ?: error("BFF static token is null")
when (val apiResponse = requestBlock(staticToken)) {
is ApiResponse.Error -> errorConverter.convert(apiResponse.cause).left()
is ApiResponse.Success<T> -> apiResponse.data.right()
}
},
catch = { errorConverter.convert(it).left() },
)
}
suspend fun <T : Any> performWithoutToken(requestBlock: suspend () -> ApiResponse<T>): Either<VisaApiError, T> =
/**
* Static token added in headers [com.tangem.datasource.api.common.config.TangemPay]
*/
suspend fun <T : Any> performWithStaticToken(requestBlock: suspend () -> ApiResponse<T>): Either<VisaApiError, T> =
withContext(dispatchers.io) {
catch(
block = {