Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-30 10:28:27 +02:00
parent 6a82ee1600
commit 229dc89bcb
28 changed files with 623 additions and 36 deletions

View file

@ -51,18 +51,10 @@ internal interface TangemPayDataModule {
@Singleton
fun bindKycRepository(repository: DefaultKycRepository): KycRepository
@Binds
@Singleton
fun bindOnboardingRepository(repository: DefaultOnboardingRepository): OnboardingRepository
@Binds
@Singleton
fun bindTangemPayTxHistoryRepository(repository: DefaultTangemPayTxHistoryRepository): TangemPayTxHistoryRepository
@Binds
@Singleton
fun bindCardDetailsRepository(repository: DefaultTangemPayCardDetailsRepository): TangemPayCardDetailsRepository
@Binds
@Singleton
fun bindTangemPaySwapRepository(repository: DefaultTangemPayWithdrawRepository): TangemPayWithdrawRepository

View file

@ -0,0 +1,24 @@
package com.tangem.data.pay.di
import com.tangem.data.pay.repository.MockAwareOnboardingRepository
import com.tangem.data.pay.repository.MockAwareTangemPayCardDetailsRepository
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface TangemPayDataMockedModule {
@Binds
@Singleton
fun bindOnboardingRepository(repository: MockAwareOnboardingRepository): OnboardingRepository
@Binds
@Singleton
fun bindCardDetailsRepository(repository: MockAwareTangemPayCardDetailsRepository): TangemPayCardDetailsRepository
}

View file

@ -0,0 +1,110 @@
package com.tangem.data.pay.repository
import arrow.core.Either
import arrow.core.right
import com.tangem.core.error.UniversalError
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.domain.models.pay.TangemPayEligibilityType
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.model.CustomerInfo
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.visa.error.VisaApiError
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
import javax.inject.Singleton
/** In MOCK env skips local-storage / signing enrollment; server calls go to WireMock. */
@Singleton
internal class MockAwareOnboardingRepository @Inject constructor(
private val real: DefaultOnboardingRepository,
private val apiConfigsManager: ApiConfigsManager,
) : OnboardingRepository {
private val mockOrderIds: MutableSet<UserWalletId> = ConcurrentHashMap.newKeySet()
private val isMockMode: Boolean
get() = apiConfigsManager
.getEnvironmentConfig(ApiConfig.ID.TangemPay)
.environment == ApiEnvironment.MOCK
override suspend fun validateDeeplink(link: String): Either<UniversalError, Boolean> {
if (isMockMode) return true.right()
return real.validateDeeplink(link)
}
override suspend fun isTangemPayInitialDataProduced(userWalletId: UserWalletId): Boolean {
if (isMockMode) return true
return real.isTangemPayInitialDataProduced(userWalletId)
}
override suspend fun produceInitialData(userWalletId: UserWalletId) {
if (isMockMode) return
real.produceInitialData(userWalletId)
}
override suspend fun getCustomerInfo(userWalletId: UserWalletId): Either<VisaApiError, CustomerInfo> =
real.getCustomerInfo(userWalletId)
override suspend fun createOrder(userWalletId: UserWalletId): Either<VisaApiError, String> {
if (isMockMode) {
mockOrderIds.add(userWalletId)
return MOCK_ORDER_ID.right()
}
return real.createOrder(userWalletId)
}
override suspend fun clearOrderId(userWalletId: UserWalletId) {
if (isMockMode) {
mockOrderIds.remove(userWalletId)
return
}
real.clearOrderId(userWalletId)
}
override suspend fun getOrderId(userWalletId: UserWalletId): String? {
if (isMockMode) return MOCK_ORDER_ID.takeIf { userWalletId in mockOrderIds }
return real.getOrderId(userWalletId)
}
override suspend fun hasTangemPayInWallet(userWalletId: UserWalletId): Either<VisaApiError, Boolean> =
real.hasTangemPayInWallet(userWalletId)
override suspend fun checkCustomerEligibility(): List<TangemPayEligibilityType> {
if (isMockMode) return listOf(TangemPayEligibilityType.DETAILS)
return real.checkCustomerEligibility()
}
override suspend fun getCustomerEligibility(): List<TangemPayEligibilityType> {
if (isMockMode) return listOf(TangemPayEligibilityType.DETAILS)
return real.getCustomerEligibility()
}
override fun getSavedCustomerInfo(userWalletId: UserWalletId): CustomerInfo? =
real.getSavedCustomerInfo(userWalletId)
override suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean {
if (isMockMode) return false
return real.getHideMainOnboardingBanner(userWalletId)
}
override suspend fun setHideMainOnboardingBanner(userWalletId: UserWalletId) {
if (isMockMode) return
real.setHideMainOnboardingBanner(userWalletId)
}
override suspend fun disableTangemPay(userWalletId: UserWalletId): Either<VisaApiError, Unit> {
if (isMockMode) return Unit.right()
return real.disableTangemPay(userWalletId)
}
override suspend fun isTangemPayDeactivated(userWalletId: UserWalletId): Boolean {
if (isMockMode) return false
return real.isTangemPayDeactivated(userWalletId)
}
private companion object {
const val MOCK_ORDER_ID = "mock-order-id"
}
}

View file

@ -0,0 +1,100 @@
package com.tangem.data.pay.repository
import arrow.core.Either
import arrow.core.right
import com.tangem.core.error.UniversalError
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
import com.tangem.domain.models.account.CardDisplayName
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.model.SetPinResult
import com.tangem.domain.pay.model.TangemPayCardBalance
import com.tangem.domain.pay.model.TangemPayCardDetails
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import com.tangem.domain.visa.model.TangemPayCardFrozenState
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
import javax.inject.Singleton
/** In MOCK env short-circuits RSA-encrypted flows (reveal/getPin/setPin) with hardcoded values. */
@Singleton
internal class MockAwareTangemPayCardDetailsRepository @Inject constructor(
private val real: DefaultTangemPayCardDetailsRepository,
private val apiConfigsManager: ApiConfigsManager,
) : TangemPayCardDetailsRepository {
private val isMockMode: Boolean
get() = apiConfigsManager
.getEnvironmentConfig(ApiConfig.ID.TangemPay)
.environment == ApiEnvironment.MOCK
override suspend fun getCardBalance(userWalletId: UserWalletId): Either<UniversalError, TangemPayCardBalance> =
real.getCardBalance(userWalletId)
override suspend fun revealCardDetails(
userWalletId: UserWalletId,
): Either<UniversalError, TangemPayCardDetails> {
if (isMockMode) {
return TangemPayCardDetails(
pan = MOCK_PAN,
cvv = MOCK_CVV,
expirationYear = MOCK_EXPIRATION_YEAR,
expirationMonth = MOCK_EXPIRATION_MONTH,
).right()
}
return real.revealCardDetails(userWalletId)
}
override suspend fun getPin(userWalletId: UserWalletId, cardId: String): Either<UniversalError, String?> {
if (isMockMode) return MOCK_PIN.right()
return real.getPin(userWalletId, cardId)
}
override suspend fun setPin(userWalletId: UserWalletId, pin: String): Either<UniversalError, SetPinResult> {
if (isMockMode) return SetPinResult.SUCCESS.right()
return real.setPin(userWalletId, pin)
}
override suspend fun isAddToWalletDone(userWalletId: UserWalletId): Either<UniversalError, Boolean> =
real.isAddToWalletDone(userWalletId)
override suspend fun setAddToWalletAsDone(userWalletId: UserWalletId): Either<UniversalError, Unit> =
real.setAddToWalletAsDone(userWalletId)
override suspend fun freezeCard(
userWalletId: UserWalletId,
cardId: String,
): Either<UniversalError, TangemPayCardFrozenState> = real.freezeCard(userWalletId, cardId)
override suspend fun unfreezeCard(
userWalletId: UserWalletId,
cardId: String,
): Either<UniversalError, TangemPayCardFrozenState> = real.unfreezeCard(userWalletId, cardId)
override fun cardFrozenState(cardId: String): Flow<TangemPayCardFrozenState> =
real.cardFrozenState(cardId)
override suspend fun cardFrozenStateSync(cardId: String): TangemPayCardFrozenState? =
real.cardFrozenStateSync(cardId)
override suspend fun updateCardDisplayName(
cardId: String,
userWalletId: UserWalletId,
displayName: CardDisplayName,
): Either<UniversalError, Unit> = real.updateCardDisplayName(cardId, userWalletId, displayName)
override suspend fun updateCardLimit(
cardId: String,
userWalletId: UserWalletId,
limit: String,
): Either<UniversalError, Unit> = real.updateCardLimit(cardId, userWalletId, limit)
private companion object {
const val MOCK_PAN = "4242 4242 4242 4242"
const val MOCK_CVV = "123"
const val MOCK_EXPIRATION_YEAR = "2028"
const val MOCK_EXPIRATION_MONTH = "12"
const val MOCK_PIN = "1234"
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.data.pay.di
import com.tangem.data.pay.repository.DefaultOnboardingRepository
import com.tangem.data.pay.repository.DefaultTangemPayCardDetailsRepository
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface TangemPayDataProductionModule {
@Binds
@Singleton
fun bindOnboardingRepository(repository: DefaultOnboardingRepository): OnboardingRepository
@Binds
@Singleton
fun bindCardDetailsRepository(repository: DefaultTangemPayCardDetailsRepository): TangemPayCardDetailsRepository
}