Updated on 2026-08-14
This commit is contained in:
parent
fc7b758aff
commit
c5d0ba8f58
25 changed files with 448 additions and 133 deletions
|
|
@ -52,6 +52,9 @@ internal object PaymentAccountStatusValueDMConverter :
|
|||
customerId = value.customerId,
|
||||
)
|
||||
is PaymentAccountStatusValue.Empty -> PaymentAccountStatusValueDM.Empty()
|
||||
is PaymentAccountStatusValue.Deactivated -> PaymentAccountStatusValueDM.DeactivatedAccount(
|
||||
fiatBalance = value.fiatBalance.toDM(),
|
||||
)
|
||||
// Transient statuses are not persisted
|
||||
is PaymentAccountStatusValue.Loading,
|
||||
is PaymentAccountStatusValue.Error.ExposedDevice,
|
||||
|
|
@ -101,6 +104,10 @@ internal object PaymentAccountStatusValueDMConverter :
|
|||
kycStatus = value.kycStatus,
|
||||
customerId = value.customerId,
|
||||
)
|
||||
is PaymentAccountStatusValueDM.DeactivatedAccount -> PaymentAccountStatusValue.Deactivated(
|
||||
source = StatusSource.CACHE,
|
||||
fiatBalance = value.fiatBalance.toDomain(),
|
||||
)
|
||||
null -> PaymentAccountStatusValue.Error.Unavailable
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,16 +48,6 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
val account = Account.Payment(userWalletId = params.userWalletId)
|
||||
logger.i("fetch: ${params.userWalletId.stringValue}")
|
||||
|
||||
if (onboardingRepository.isTangemPayDeactivated(params.userWalletId)) {
|
||||
return@catchOn paymentAccountStatusesStore.store(
|
||||
userWalletId = params.userWalletId,
|
||||
status = AccountStatus.Payment(
|
||||
account = account,
|
||||
value = PaymentAccountStatusValue.Empty,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if (deviceSecurity.isSecurityExposed()) {
|
||||
logger.i("fetch security info: rooted: ${deviceSecurity.isRooted}")
|
||||
logger.i("fetch security info: xposed: ${deviceSecurity.isXposed}")
|
||||
|
|
@ -254,20 +244,31 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
private fun CustomerInfo.mapToPaymentAccountStatus(): PaymentAccountStatusValue {
|
||||
val cardInfo = this.cardInfo
|
||||
val productInstance = this.productInstance
|
||||
return if (kycStatus != KycStatus.APPROVED && !customerId.isNullOrEmpty()) {
|
||||
PaymentAccountStatusValue.UnderReview(
|
||||
source = StatusSource.ACTUAL,
|
||||
kycStatus = kycStatus,
|
||||
customerId = requireNotNull(customerId) { "CustomerId must not be null" },
|
||||
)
|
||||
} else if (cardInfo != null && productInstance != null && !customerId.isNullOrEmpty()) {
|
||||
convertToContentState(
|
||||
|
||||
val isDeactivated = productInstance?.status == CustomerInfo.ProductInstance.Status.DEACTIVATED
|
||||
val isFormer = state == CustomerInfo.State.FORMER
|
||||
val fiatBalance = fiatBalance
|
||||
|
||||
return when {
|
||||
kycStatus != KycStatus.APPROVED && !customerId.isNullOrEmpty() -> {
|
||||
PaymentAccountStatusValue.UnderReview(
|
||||
source = StatusSource.ACTUAL,
|
||||
kycStatus = kycStatus,
|
||||
customerId = requireNotNull(customerId) { "CustomerId must not be null" },
|
||||
)
|
||||
}
|
||||
fiatBalance != null && (isDeactivated || isFormer) -> {
|
||||
PaymentAccountStatusValue.Deactivated(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatBalance = fiatBalance,
|
||||
)
|
||||
}
|
||||
cardInfo != null && productInstance != null && !customerId.isNullOrEmpty() -> convertToContentState(
|
||||
productInstance = productInstance,
|
||||
cardInfo = cardInfo,
|
||||
customerId = requireNotNull(customerId) { "CustomerId must not be null" },
|
||||
)
|
||||
} else {
|
||||
PaymentAccountStatusValue.IssuingCard(source = StatusSource.ACTUAL)
|
||||
else -> PaymentAccountStatusValue.IssuingCard(source = StatusSource.ACTUAL)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -306,7 +307,6 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
return when (this) {
|
||||
is VisaApiError.RefreshTokenExpired -> PaymentAccountStatusValue.Error.NotSynced
|
||||
is VisaApiError.NotPaeraCustomer -> constructNotCreatedOrEmptyStatus(userWalletId)
|
||||
is VisaApiError.Deactivated -> constructNotCreatedOrEmptyStatus(userWalletId)
|
||||
else -> PaymentAccountStatusValue.Error.Unavailable
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.data.pay.repository
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.flatMap
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.pay.store.PaymentAccountStatusesStore
|
||||
|
|
@ -11,6 +10,7 @@ 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.response.CustomerMeResponse
|
||||
import com.tangem.datasource.api.pay.models.response.FiatBalance
|
||||
import com.tangem.datasource.api.pay.models.response.OrderResponse
|
||||
import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
|
|
@ -101,10 +101,8 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
val isFormer = result?.state?.let { CustomerInfo.State.fromString(it) } == CustomerInfo.State.FORMER
|
||||
if (isDeactivated || isFormer) {
|
||||
tangemPayStorage.storeIsTangemPayDeactivated(userWalletId)
|
||||
VisaApiError.Deactivated.left()
|
||||
} else {
|
||||
getCustomerInfo(userWalletId = userWalletId, response = result).right()
|
||||
}
|
||||
getCustomerInfo(userWalletId = userWalletId, response = result).right()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,10 +178,7 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
currencyCode = fiatBalance.currency,
|
||||
depositAddress = response.depositAddress,
|
||||
isPinSet = response.card?.isPinSet == true,
|
||||
fiatBalance = PaymentAccountStatusValue.FiatBalance(
|
||||
availableBalance = fiatBalance.availableBalance,
|
||||
currency = fiatBalance.currency,
|
||||
),
|
||||
fiatBalance = fiatBalance.toDomain(),
|
||||
cryptoBalance = PaymentAccountStatusValue.CryptoBalance(
|
||||
id = cryptoBalance.id,
|
||||
chainId = cryptoBalance.chainId.toLong(),
|
||||
|
|
@ -202,13 +197,20 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
}
|
||||
cardFrozenStateStore.store(key = instance.cardId, value = cardFrozenState)
|
||||
|
||||
ProductInstance(id = instance.id, cardId = instance.cardId, frozenState = cardFrozenState)
|
||||
ProductInstance(
|
||||
id = instance.id,
|
||||
cardId = instance.cardId,
|
||||
frozenState = cardFrozenState,
|
||||
status = instance.status.toDomain(),
|
||||
)
|
||||
}
|
||||
return CustomerInfo(
|
||||
customerId = response?.id,
|
||||
productInstance = productInstance,
|
||||
kycStatus = kycStatus,
|
||||
cardInfo = cardInfo,
|
||||
state = response?.state?.let { CustomerInfo.State.fromString(it) } ?: CustomerInfo.State.UNDEFINED,
|
||||
fiatBalance = fiatBalance?.toDomain(),
|
||||
).also {
|
||||
lastFetchedCustomerInfoMap[userWalletId] = it
|
||||
}
|
||||
|
|
@ -290,4 +292,24 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
setHideMainOnboardingBanner(userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun FiatBalance.toDomain() = PaymentAccountStatusValue.FiatBalance(
|
||||
availableBalance = availableBalance,
|
||||
currency = currency,
|
||||
)
|
||||
|
||||
private fun CustomerMeResponse.ProductInstance.Status.toDomain() = when (this) {
|
||||
CustomerMeResponse.ProductInstance.Status.NEW -> ProductInstance.Status.NEW
|
||||
CustomerMeResponse.ProductInstance.Status.READY_FOR_MANUFACTURING -> ProductInstance.Status.READY_FOR_MANUFACTURING
|
||||
CustomerMeResponse.ProductInstance.Status.MANUFACTURING -> ProductInstance.Status.MANUFACTURING
|
||||
CustomerMeResponse.ProductInstance.Status.SENT_TO_DELIVERY -> ProductInstance.Status.SENT_TO_DELIVERY
|
||||
CustomerMeResponse.ProductInstance.Status.DELIVERED -> ProductInstance.Status.DELIVERED
|
||||
CustomerMeResponse.ProductInstance.Status.ACTIVATING -> ProductInstance.Status.ACTIVATING
|
||||
CustomerMeResponse.ProductInstance.Status.ACTIVE -> ProductInstance.Status.ACTIVE
|
||||
CustomerMeResponse.ProductInstance.Status.BLOCKED -> ProductInstance.Status.BLOCKED
|
||||
CustomerMeResponse.ProductInstance.Status.DEACTIVATING -> ProductInstance.Status.DEACTIVATING
|
||||
CustomerMeResponse.ProductInstance.Status.DEACTIVATED -> ProductInstance.Status.DEACTIVATED
|
||||
CustomerMeResponse.ProductInstance.Status.CANCELED -> ProductInstance.Status.CANCELED
|
||||
CustomerMeResponse.ProductInstance.Status.UNKNOWN -> ProductInstance.Status.UNKNOWN
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
package com.tangem.data.pay.converter
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusValueDM
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class PaymentAccountStatusValueDMConverterTest {
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class Convert {
|
||||
|
||||
@Test
|
||||
fun `GIVEN domain Empty WHEN convert THEN returns DM Empty`() {
|
||||
// GIVEN
|
||||
val domain = PaymentAccountStatusValue.Empty
|
||||
|
||||
// WHEN
|
||||
val result = PaymentAccountStatusValueDMConverter.convert(domain)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(PaymentAccountStatusValueDM.Empty::class.java)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN domain Deactivated with balance WHEN convert THEN returns DM DeactivatedAccount with same balance`() {
|
||||
// GIVEN
|
||||
val domain = PaymentAccountStatusValue.Deactivated(
|
||||
source = StatusSource.ACTUAL,
|
||||
fiatBalance = PaymentAccountStatusValue.FiatBalance(
|
||||
availableBalance = BigDecimal("100"),
|
||||
currency = "USD",
|
||||
)
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = PaymentAccountStatusValueDMConverter.convert(domain)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(PaymentAccountStatusValueDM.DeactivatedAccount::class.java)
|
||||
val dm = result as PaymentAccountStatusValueDM.DeactivatedAccount
|
||||
assertThat(dm.fiatBalance.availableBalance).isEqualTo(BigDecimal("100"))
|
||||
assertThat(dm.fiatBalance.currency).isEqualTo("USD")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN domain Loading WHEN convert THEN returns null (transient, not persisted)`() {
|
||||
// GIVEN
|
||||
val domain = PaymentAccountStatusValue.Loading
|
||||
|
||||
// WHEN
|
||||
val result = PaymentAccountStatusValueDMConverter.convert(domain)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN domain Error Unavailable WHEN convert THEN returns null (transient, not persisted)`() {
|
||||
// GIVEN
|
||||
val domain = PaymentAccountStatusValue.Error.Unavailable
|
||||
|
||||
// WHEN
|
||||
val result = PaymentAccountStatusValueDMConverter.convert(domain)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN domain NotCreated WHEN convert THEN returns DM NotCreated`() {
|
||||
// GIVEN
|
||||
val domain = PaymentAccountStatusValue.NotCreated
|
||||
|
||||
// WHEN
|
||||
val result = PaymentAccountStatusValueDMConverter.convert(domain)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(PaymentAccountStatusValueDM.NotCreated::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class ConvertBack {
|
||||
|
||||
@Test
|
||||
fun `GIVEN DM Empty WHEN convertBack THEN returns domain Empty`() {
|
||||
// GIVEN
|
||||
val dm = PaymentAccountStatusValueDM.Empty()
|
||||
|
||||
// WHEN
|
||||
val result = PaymentAccountStatusValueDMConverter.convertBack(dm)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(PaymentAccountStatusValue.Empty)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN DM DeactivatedAccount WHEN convertBack THEN returns domain Deactivated with CACHE source`() {
|
||||
// GIVEN
|
||||
val dm = PaymentAccountStatusValueDM.DeactivatedAccount(
|
||||
fiatBalance = PaymentAccountStatusValueDM.FiatBalanceDM(
|
||||
availableBalance = BigDecimal("200"),
|
||||
currency = "EUR",
|
||||
)
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = PaymentAccountStatusValueDMConverter.convertBack(dm)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isInstanceOf(PaymentAccountStatusValue.Deactivated::class.java)
|
||||
val deactivated = result as PaymentAccountStatusValue.Deactivated
|
||||
assertThat(deactivated.source).isEqualTo(StatusSource.CACHE)
|
||||
assertThat(deactivated.fiatBalance.availableBalance).isEqualTo(BigDecimal("200"))
|
||||
assertThat(deactivated.fiatBalance.currency).isEqualTo("EUR")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN null DM WHEN convertBack THEN returns Error Unavailable`() {
|
||||
// GIVEN / WHEN
|
||||
val result = PaymentAccountStatusValueDMConverter.convertBack(null)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(PaymentAccountStatusValue.Error.Unavailable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN DM NotCreated WHEN convertBack THEN returns domain NotCreated`() {
|
||||
// GIVEN
|
||||
val dm = PaymentAccountStatusValueDM.NotCreated()
|
||||
|
||||
// WHEN
|
||||
val result = PaymentAccountStatusValueDMConverter.convertBack(dm)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(PaymentAccountStatusValue.NotCreated)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue