Updated on 2026-08-14
This commit is contained in:
parent
b83b9d4ef6
commit
9db2ebaef4
21 changed files with 279 additions and 20 deletions
|
|
@ -293,5 +293,16 @@ internal interface TangemPayDataModule {
|
|||
appCoroutineScope = appCoroutineScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideCreateVirtualAccountOrderUseCase(
|
||||
onboardingRepository: OnboardingRepository,
|
||||
pollingUseCase: StartTangemPayOrderPollingUseCase,
|
||||
): CreateVirtualAccountOrderUseCase {
|
||||
return CreateVirtualAccountOrderUseCase(
|
||||
onboardingRepository = onboardingRepository,
|
||||
pollingUseCase = pollingUseCase,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -338,7 +338,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
fiatRate: BigDecimal?,
|
||||
): PaymentAccountStatusValue {
|
||||
val cardsById = cards.associateBy { it.cardId }
|
||||
val tangemPayCards = productInstances.mapNotNull { productInstance ->
|
||||
val tangemPayCards = cardProductInstances.mapNotNull { productInstance ->
|
||||
val cardInfo = cardsById[productInstance.cardId] ?: return@mapNotNull null
|
||||
val cardId = productInstance.cardId
|
||||
val cardFrozenState = cardDetailsRepository.cardFrozenStateSync(cardId)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ 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
|
||||
import com.tangem.datasource.api.pay.models.request.SetTangemPayEnabledRequest
|
||||
import com.tangem.datasource.api.pay.models.request.VirtualAccountOrderRequest
|
||||
import com.tangem.datasource.api.pay.models.response.CustomerMeResponse
|
||||
import com.tangem.datasource.api.pay.models.response.OrderResponse
|
||||
import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore
|
||||
|
|
@ -38,7 +39,7 @@ import javax.inject.Inject
|
|||
|
||||
private const val VALID_STATUS = "valid"
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "TooManyFunctions")
|
||||
internal class DefaultOnboardingRepository @Inject constructor(
|
||||
private val analytics: AnalyticsEventHandler,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
|
|
@ -113,7 +114,10 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
): Either<VisaApiError, BankCredentials> {
|
||||
return requestHelper.performRequest(userWalletId) { authHeader ->
|
||||
tangemPayApi.getBankCredentials(authHeader = authHeader, productInstanceId = productInstanceId)
|
||||
}.map { response -> BankCredentialsConverter.convert(response) }
|
||||
}.flatMap { response ->
|
||||
val result = response.result ?: return@flatMap VisaApiError.UnknownWithoutCode.left()
|
||||
BankCredentialsConverter.convert(result).right()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isTangemPayDeactivated(userWalletId: UserWalletId): Boolean {
|
||||
|
|
@ -167,6 +171,34 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun createVirtualAccountOrder(
|
||||
userWalletId: UserWalletId,
|
||||
paymentAccountAddress: String,
|
||||
): Either<VisaApiError, String> = withContext(dispatcherProvider.io) {
|
||||
requestHelper.performRequest(userWalletId) { authHeader ->
|
||||
tangemPayApi.createVirtualAccountOrder(
|
||||
authHeader = authHeader,
|
||||
body = VirtualAccountOrderRequest(
|
||||
data = VirtualAccountOrderRequest.Data(depositAddress = paymentAccountAddress),
|
||||
idempotencyKey = UUID.randomUUID().toString(),
|
||||
),
|
||||
)
|
||||
}.map { response -> requireNotNull(response.result).id }
|
||||
}
|
||||
|
||||
override suspend fun getVirtualAccountOrderId(userWalletId: UserWalletId): String? =
|
||||
withContext(dispatcherProvider.io) {
|
||||
val customerWalletAddress = requestHelper.getCustomerWalletAddress(userWalletId)
|
||||
tangemPayStorage.getVirtualAccountOrderId(customerWalletAddress)
|
||||
}
|
||||
|
||||
override suspend fun storeVirtualAccountOrderId(userWalletId: UserWalletId, vaOrderId: String) {
|
||||
withContext(dispatcherProvider.io) {
|
||||
val customerWalletAddress = requestHelper.getCustomerWalletAddress(userWalletId)
|
||||
tangemPayStorage.storeVirtualAccountOrderId(customerWalletAddress, vaOrderId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUserWallet(userWalletId: UserWalletId): UserWallet {
|
||||
return userWalletsListRepository.userWallets.value?.firstOrNull { it.walletId == userWalletId }
|
||||
?: error("no userWallet found")
|
||||
|
|
@ -181,7 +213,7 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
sendKycAnalytics(customerInfo.kycStatus)
|
||||
|
||||
// Keep the per-card frozen state up to date for every card.
|
||||
customerInfo.productInstances.forEach { instance ->
|
||||
customerInfo.cardProductInstances.forEach { instance ->
|
||||
cardFrozenStateStore.store(key = instance.cardId, value = instance.frozenState)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import com.tangem.datasource.api.pay.models.response.BankCredentialsResponse
|
|||
import com.tangem.domain.models.account.BankCredentials
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object BankCredentialsConverter : Converter<BankCredentialsResponse, BankCredentials> {
|
||||
override fun convert(value: BankCredentialsResponse): BankCredentials {
|
||||
internal object BankCredentialsConverter : Converter<BankCredentialsResponse.Result, BankCredentials> {
|
||||
override fun convert(value: BankCredentialsResponse.Result): BankCredentials {
|
||||
return BankCredentials(
|
||||
type = value.type.orEmpty(),
|
||||
beneficiaryName = value.beneficiaryName.orEmpty(),
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ internal object CustomerInfoConverter : Converter<CustomerMeResponse.Result, Cus
|
|||
val name = displayName?.ifEmpty { null }
|
||||
return ProductInstance(
|
||||
id = id,
|
||||
cardId = cardId,
|
||||
cardId = cardId.orEmpty(),
|
||||
frozenState = cardFrozenState,
|
||||
status = status,
|
||||
displayName = if (name != null) CardDisplayName(name).getOrElse { null } else null,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.datasource.api.pay.models.response.TangemPayErrorResponse
|
|||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -23,7 +24,7 @@ internal class TangemPayErrorConverter @Inject constructor(
|
|||
if (value.code == ApiResponseError.HttpException.Code.UNAUTHORIZED) return VisaApiError.RefreshTokenExpired
|
||||
|
||||
val errorBody = value.errorBody ?: return VisaApiError.UnknownWithoutCode
|
||||
return runCatching {
|
||||
runCatching {
|
||||
tangemPayErrorAdapter.fromJson(errorBody)?.error?.code ?: value.code.numericCode
|
||||
}.map {
|
||||
VisaApiError.fromBackendError(it)
|
||||
|
|
@ -31,6 +32,7 @@ internal class TangemPayErrorConverter @Inject constructor(
|
|||
VisaApiError.UnknownWithoutCode
|
||||
}
|
||||
} else {
|
||||
TangemLogger.e("Not HttpException. ${value.message}", value)
|
||||
VisaApiError.UnknownWithoutCode
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ internal class MockAwareOnboardingRepository @Inject constructor(
|
|||
) : OnboardingRepository {
|
||||
|
||||
private val mockOrderIds: MutableSet<UserWalletId> = ConcurrentHashMap.newKeySet()
|
||||
private val mockVaOrderIds: MutableSet<UserWalletId> = ConcurrentHashMap.newKeySet()
|
||||
|
||||
private val isMockMode: Boolean
|
||||
get() = apiConfigsManager
|
||||
|
|
@ -74,6 +75,30 @@ internal class MockAwareOnboardingRepository @Inject constructor(
|
|||
return real.getOrderId(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun createVirtualAccountOrder(
|
||||
userWalletId: UserWalletId,
|
||||
paymentAccountAddress: String,
|
||||
): Either<VisaApiError, String> {
|
||||
if (isMockMode) {
|
||||
mockVaOrderIds.add(userWalletId)
|
||||
return MOCK_VA_ORDER_ID.right()
|
||||
}
|
||||
return real.createVirtualAccountOrder(userWalletId, paymentAccountAddress)
|
||||
}
|
||||
|
||||
override suspend fun getVirtualAccountOrderId(userWalletId: UserWalletId): String? {
|
||||
if (isMockMode) return MOCK_VA_ORDER_ID.takeIf { userWalletId in mockVaOrderIds }
|
||||
return real.getVirtualAccountOrderId(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun storeVirtualAccountOrderId(userWalletId: UserWalletId, vaOrderId: String) {
|
||||
if (isMockMode) {
|
||||
mockVaOrderIds.add(userWalletId)
|
||||
return
|
||||
}
|
||||
real.storeVirtualAccountOrderId(userWalletId, vaOrderId)
|
||||
}
|
||||
|
||||
override suspend fun hasTangemPayInWallet(userWalletId: UserWalletId): Either<VisaApiError, Boolean> =
|
||||
real.hasTangemPayInWallet(userWalletId)
|
||||
|
||||
|
|
@ -112,5 +137,6 @@ internal class MockAwareOnboardingRepository @Inject constructor(
|
|||
|
||||
private companion object {
|
||||
const val MOCK_ORDER_ID = "mock-order-id"
|
||||
const val MOCK_VA_ORDER_ID = "mock-va-order-id"
|
||||
}
|
||||
}
|
||||
|
|
@ -193,7 +193,7 @@ internal class DefaultPaymentAccountStatusFetcherTest {
|
|||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class `resolveVirtualAccountOnramp` {
|
||||
inner class ResolveVirtualAccountOnramp {
|
||||
|
||||
@Test
|
||||
fun `GIVEN feature toggle is off WHEN invoke THEN virtualAccount is null`() = runTest {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ internal class BankCredentialsConverterTest {
|
|||
@Test
|
||||
fun `GIVEN full response WHEN convert THEN all fields mapped`() {
|
||||
// Arrange
|
||||
val response = BankCredentialsResponse(
|
||||
val response = BankCredentialsResponse.Result(
|
||||
type = "fiat",
|
||||
beneficiaryName = "Ivan Ivanov",
|
||||
beneficiaryAddress = "18, Rue Rubens 20, Paris, Ile-de-France 75013, US",
|
||||
|
|
@ -39,7 +39,7 @@ internal class BankCredentialsConverterTest {
|
|||
@Test
|
||||
fun `GIVEN null fields WHEN convert THEN mapped to empty strings`() {
|
||||
// Arrange
|
||||
val response = BankCredentialsResponse(
|
||||
val response = BankCredentialsResponse.Result(
|
||||
type = null,
|
||||
beneficiaryName = null,
|
||||
beneficiaryAddress = null,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue