Updated on 2026-08-14
This commit is contained in:
parent
9c8bfd4139
commit
0896f80f53
30 changed files with 483 additions and 263 deletions
|
|
@ -7,8 +7,8 @@ import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
|||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.data.pay.entity.TangemPayCurrencyFactory
|
||||
import com.tangem.data.pay.util.TangemPayErrorConverter
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory
|
||||
|
|
@ -16,14 +16,8 @@ import com.tangem.utils.logging.TangemLogger
|
|||
import javax.inject.Inject
|
||||
|
||||
private const val TAG = "TangemPay: DefaultTangemPayCryptoCurrencyFactory"
|
||||
/**
|
||||
* Custom token parameters. Will be used only for F&F.
|
||||
*/
|
||||
private const val TOKEN_ID = "usd-coin"
|
||||
private const val TOKEN_NAME = "USDC"
|
||||
private const val TOKEN_CONTRACT_ADDRESS = "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"
|
||||
private const val TOKEN_DECIMALS = 6
|
||||
|
||||
@Deprecated("Use TangemPayCurrencyFactory instead")
|
||||
internal class DefaultTangemPayCryptoCurrencyFactory @Inject constructor(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
private val errorConverter: TangemPayErrorConverter,
|
||||
|
|
@ -47,32 +41,11 @@ internal class DefaultTangemPayCryptoCurrencyFactory @Inject constructor(
|
|||
)
|
||||
cryptoCurrencyFactory.createToken(
|
||||
network = requireNotNull(network),
|
||||
rawId = CryptoCurrency.RawID(TOKEN_ID),
|
||||
name = TOKEN_NAME,
|
||||
symbol = TOKEN_NAME,
|
||||
contractAddress = TOKEN_CONTRACT_ADDRESS,
|
||||
decimals = TOKEN_DECIMALS,
|
||||
)
|
||||
}.mapLeft { exception ->
|
||||
TangemLogger.withTag(TAG).e("Error", exception)
|
||||
errorConverter.convert(exception)
|
||||
}
|
||||
}
|
||||
|
||||
override fun create(userWallet: UserWallet): Either<UniversalError, CryptoCurrency.Token> {
|
||||
return catch {
|
||||
val network = networkFactory.create(
|
||||
blockchain = VisaUtilities.visaBlockchain,
|
||||
extraDerivationPath = null,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
cryptoCurrencyFactory.createToken(
|
||||
network = requireNotNull(network),
|
||||
rawId = CryptoCurrency.RawID(TOKEN_ID),
|
||||
name = TOKEN_NAME,
|
||||
symbol = TOKEN_NAME,
|
||||
contractAddress = TOKEN_CONTRACT_ADDRESS,
|
||||
decimals = TOKEN_DECIMALS,
|
||||
rawId = CryptoCurrency.RawID(TangemPayCurrencyFactory.TOKEN_ID),
|
||||
name = TangemPayCurrencyFactory.TOKEN_NAME,
|
||||
symbol = TangemPayCurrencyFactory.TOKEN_NAME,
|
||||
contractAddress = TangemPayCurrencyFactory.TOKEN_CONTRACT_ADDRESS,
|
||||
decimals = TangemPayCurrencyFactory.TOKEN_DECIMALS,
|
||||
)
|
||||
}.mapLeft { exception ->
|
||||
TangemLogger.withTag(TAG).e("Error", exception)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package com.tangem.data.pay.converter
|
||||
|
||||
import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter.convert
|
||||
import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter.convertBack
|
||||
import com.tangem.data.pay.entity.TangemPayCurrencyFactory
|
||||
import com.tangem.datasource.local.visa.entity.PaymentAccountStatusValueDM
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Two-way converter between [PaymentAccountStatusValue] and [PaymentAccountStatusValueDM].
|
||||
|
|
@ -15,10 +16,12 @@ import com.tangem.utils.converter.TwoWayConverter
|
|||
*
|
||||
* [convertBack] maps data model → domain. All restored statuses have [StatusSource.CACHE] as source.
|
||||
*/
|
||||
internal object PaymentAccountStatusValueDMConverter :
|
||||
TwoWayConverter<PaymentAccountStatusValue, PaymentAccountStatusValueDM?> {
|
||||
@Singleton
|
||||
internal class PaymentAccountStatusValueDMConverter @Inject constructor(
|
||||
private val tangemPayCurrencyFactory: TangemPayCurrencyFactory,
|
||||
) {
|
||||
|
||||
override fun convert(value: PaymentAccountStatusValue): PaymentAccountStatusValueDM? {
|
||||
fun convert(value: PaymentAccountStatusValue): PaymentAccountStatusValueDM? {
|
||||
return when (value) {
|
||||
is PaymentAccountStatusValue.NotCreated -> PaymentAccountStatusValueDM.NotCreated()
|
||||
is PaymentAccountStatusValue.UnderReview -> PaymentAccountStatusValueDM.UnderReview(
|
||||
|
|
@ -60,7 +63,7 @@ internal object PaymentAccountStatusValueDMConverter :
|
|||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: PaymentAccountStatusValueDM?): PaymentAccountStatusValue {
|
||||
fun convertBack(userWalletId: UserWalletId, value: PaymentAccountStatusValueDM?): PaymentAccountStatusValue {
|
||||
return when (value) {
|
||||
is PaymentAccountStatusValueDM.NotCreated -> PaymentAccountStatusValue.NotCreated
|
||||
is PaymentAccountStatusValueDM.CardIssueFailed -> PaymentAccountStatusValue.Error.CardIssueFailed(
|
||||
|
|
@ -80,6 +83,7 @@ internal object PaymentAccountStatusValueDMConverter :
|
|||
isPinSet = value.isPinSet,
|
||||
fiatBalance = value.fiatBalance.toDomain(),
|
||||
cryptoBalance = value.cryptoBalance.toDomain(),
|
||||
cryptoCurrency = tangemPayCurrencyFactory.create(userWalletId),
|
||||
)
|
||||
} else {
|
||||
PaymentAccountStatusValue.Loaded(
|
||||
|
|
@ -92,6 +96,7 @@ internal object PaymentAccountStatusValueDMConverter :
|
|||
isPinSet = value.isPinSet,
|
||||
fiatBalance = value.fiatBalance.toDomain(),
|
||||
cryptoBalance = value.cryptoBalance.toDomain(),
|
||||
cryptoCurrency = tangemPayCurrencyFactory.create(userWalletId),
|
||||
)
|
||||
}
|
||||
is PaymentAccountStatusValueDM.UnderReview -> PaymentAccountStatusValue.UnderReview(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import androidx.datastore.dataStoreFile
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.data.pay.DefaultTangemPayCryptoCurrencyFactory
|
||||
import com.tangem.data.pay.DefaultTangemPayEligibilityManager
|
||||
import com.tangem.data.pay.converter.PaymentAccountStatusValueDMConverter
|
||||
import com.tangem.data.pay.flow.DefaultPaymentAccountStatusFetcher
|
||||
import com.tangem.data.pay.flow.DefaultPaymentAccountStatusProducer
|
||||
import com.tangem.data.pay.repository.*
|
||||
|
|
@ -24,6 +25,7 @@ import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
|||
import com.tangem.domain.pay.flow.PaymentAccountStatusProducer
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
||||
import com.tangem.domain.pay.repository.*
|
||||
import com.tangem.domain.pay.usecase.GetPaymentAccountCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
import com.tangem.domain.pay.usecase.TangemPayMainScreenCustomerInfoUseCase
|
||||
import com.tangem.domain.tangempay.GetTangemPayCurrencyStatusUseCase
|
||||
|
|
@ -112,6 +114,7 @@ internal interface TangemPayDataModule {
|
|||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
scope: AppCoroutineScope,
|
||||
converter: PaymentAccountStatusValueDMConverter,
|
||||
): PaymentAccountStatusesStore {
|
||||
return PaymentAccountStatusesStore(
|
||||
runtimeStore = RuntimeSharedStore(),
|
||||
|
|
@ -124,6 +127,7 @@ internal interface TangemPayDataModule {
|
|||
produceFile = { context.dataStoreFile(fileName = "payment_account_statuses") },
|
||||
scope = scope,
|
||||
),
|
||||
converter = converter,
|
||||
scope = scope,
|
||||
)
|
||||
}
|
||||
|
|
@ -139,6 +143,14 @@ internal interface TangemPayDataModule {
|
|||
) {}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetTangemPayCryptoCurrencyStatusUseCase(
|
||||
paymentAccountStatusSupplier: PaymentAccountStatusSupplier,
|
||||
): GetPaymentAccountCryptoCurrencyStatusUseCase {
|
||||
return GetPaymentAccountCryptoCurrencyStatusUseCase(paymentAccountStatusSupplier)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTangemPayMainScreenCustomerInfoUseCase(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.data.pay.entity
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.requireUserWalletsSync
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class TangemPayCurrencyFactory @Inject constructor(
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val networkFactory: NetworkFactory,
|
||||
) {
|
||||
private val cryptoCurrencyFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
CryptoCurrencyFactory(excludedBlockchains)
|
||||
}
|
||||
|
||||
fun create(userWalletId: UserWalletId): CryptoCurrency.Token {
|
||||
val userWallet = userWalletsListRepository.requireUserWalletsSync()
|
||||
.firstOrNull { it.walletId == userWalletId }
|
||||
?: error("User wallet with id $userWalletId not found")
|
||||
val network = networkFactory.create(
|
||||
blockchain = VisaUtilities.visaBlockchain,
|
||||
userWallet = userWallet,
|
||||
extraDerivationPath = null,
|
||||
)
|
||||
return cryptoCurrencyFactory.createToken(
|
||||
network = requireNotNull(network),
|
||||
rawId = CryptoCurrency.RawID(TOKEN_ID),
|
||||
name = TOKEN_NAME,
|
||||
symbol = TOKEN_NAME,
|
||||
contractAddress = TOKEN_CONTRACT_ADDRESS,
|
||||
decimals = TOKEN_DECIMALS,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal const val TOKEN_ID = "usd-coin"
|
||||
internal const val TOKEN_NAME = "USDC"
|
||||
internal const val TOKEN_CONTRACT_ADDRESS = "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359"
|
||||
internal const val TOKEN_DECIMALS = 6
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.pay.flow
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.data.pay.entity.TangemPayCurrencyFactory
|
||||
import com.tangem.data.pay.store.PaymentAccountStatusesStore
|
||||
import com.tangem.domain.core.utils.catchOn
|
||||
import com.tangem.domain.models.StatusSource
|
||||
|
|
@ -8,6 +9,7 @@ import com.tangem.domain.models.account.Account
|
|||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.domain.models.kyc.KycStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
||||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
import com.tangem.domain.pay.model.OrderStatus
|
||||
|
|
@ -29,6 +31,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
private val customerOrderRepository: CustomerOrderRepository,
|
||||
private val deviceSecurity: DeviceSecurityInfoProvider,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val tangemPayCurrencyFactory: TangemPayCurrencyFactory,
|
||||
) : PaymentAccountStatusFetcher {
|
||||
|
||||
private val logger = TangemLogger.withTag(TAG)
|
||||
|
|
@ -132,7 +135,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
},
|
||||
ifRight = { customerInfo ->
|
||||
logger.i("proceedWithoutOrder data customerInfo ${account.userWalletId}")
|
||||
val status = customerInfo.mapToPaymentAccountStatus()
|
||||
val status = customerInfo.mapToPaymentAccountStatus(account.userWalletId)
|
||||
if (status is PaymentAccountStatusValue.IssuingCard && customerInfo.kycStatus == KycStatus.APPROVED) {
|
||||
// If order id wasn't saved -> start order creation and get customer info
|
||||
onboardingRepository.createOrder(account.userWalletId)
|
||||
|
|
@ -167,7 +170,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
},
|
||||
ifRight = { customerInfo ->
|
||||
if (customerInfo.kycStatus == KycStatus.REJECTED) {
|
||||
customerInfo.mapToPaymentAccountStatus()
|
||||
customerInfo.mapToPaymentAccountStatus(account.userWalletId)
|
||||
} else {
|
||||
PaymentAccountStatusValue.Error.CardIssueFailed(
|
||||
customerId = orderData.customerId,
|
||||
|
|
@ -182,7 +185,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
onboardingRepository.getCustomerInfo(userWalletId = account.userWalletId)
|
||||
.fold(
|
||||
ifLeft = { it.mapToPaymentAccountStatus() },
|
||||
ifRight = { customerInfo -> customerInfo.mapToPaymentAccountStatus() },
|
||||
ifRight = { it.mapToPaymentAccountStatus(account.userWalletId) },
|
||||
)
|
||||
}
|
||||
OrderStatus.UNKNOWN -> PaymentAccountStatusValue.Error.Unavailable
|
||||
|
|
@ -191,7 +194,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun CustomerInfo.mapToPaymentAccountStatus(): PaymentAccountStatusValue {
|
||||
private fun CustomerInfo.mapToPaymentAccountStatus(userWalletId: UserWalletId): PaymentAccountStatusValue {
|
||||
val cardInfo = this.cardInfo
|
||||
val productInstance = this.productInstance
|
||||
return if (kycStatus != KycStatus.APPROVED && !customerId.isNullOrEmpty()) {
|
||||
|
|
@ -202,6 +205,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
)
|
||||
} else if (cardInfo != null && productInstance != null && !customerId.isNullOrEmpty()) {
|
||||
convertToContentState(
|
||||
userWalletId = userWalletId,
|
||||
productInstance = productInstance,
|
||||
cardInfo = cardInfo,
|
||||
customerId = requireNotNull(customerId) { "CustomerId must not be null" },
|
||||
|
|
@ -212,10 +216,12 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
}
|
||||
|
||||
private fun convertToContentState(
|
||||
userWalletId: UserWalletId,
|
||||
productInstance: CustomerInfo.ProductInstance,
|
||||
cardInfo: CustomerInfo.CardInfo,
|
||||
customerId: String,
|
||||
): PaymentAccountStatusValue {
|
||||
val cryptoCurrency = tangemPayCurrencyFactory.create(userWalletId)
|
||||
return when (productInstance.frozenState) {
|
||||
TangemPayCardFrozenState.Frozen -> PaymentAccountStatusValue.Locked(
|
||||
source = StatusSource.ACTUAL,
|
||||
|
|
@ -227,6 +233,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
isPinSet = cardInfo.isPinSet,
|
||||
fiatBalance = cardInfo.fiatBalance,
|
||||
cryptoBalance = cardInfo.cryptoBalance,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
else -> PaymentAccountStatusValue.Loaded(
|
||||
source = StatusSource.ACTUAL,
|
||||
|
|
@ -238,6 +245,7 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor(
|
|||
isPinSet = cardInfo.isPinSet,
|
||||
fiatBalance = cardInfo.fiatBalance,
|
||||
cryptoBalance = cardInfo.cryptoBalance,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ internal typealias WalletIdWithPaymentStatusDM = Map<String, PaymentAccountStatu
|
|||
internal class PaymentAccountStatusesStore(
|
||||
private val runtimeStore: RuntimeSharedStore<WalletIdWithPaymentStatus>,
|
||||
private val persistenceDataStore: DataStore<WalletIdWithPaymentStatusDM>,
|
||||
private val converter: PaymentAccountStatusValueDMConverter,
|
||||
scope: AppCoroutineScope,
|
||||
) {
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ internal class PaymentAccountStatusesStore(
|
|||
runtimeStore.store(
|
||||
value = cachedStatuses.mapValues { (rawUserWalletId, statusDM) ->
|
||||
val account = Account.Payment(userWalletId = UserWalletId(rawUserWalletId))
|
||||
val statusValue = PaymentAccountStatusValueDMConverter.convertBack(value = statusDM)
|
||||
val statusValue = converter.convertBack(userWalletId = account.userWalletId, value = statusDM)
|
||||
AccountStatus.Payment(account = account, value = statusValue)
|
||||
},
|
||||
)
|
||||
|
|
@ -87,7 +88,7 @@ internal class PaymentAccountStatusesStore(
|
|||
}
|
||||
|
||||
private suspend fun storeInPersistence(userWalletId: UserWalletId, status: PaymentAccountStatusValue) {
|
||||
val statusDM = PaymentAccountStatusValueDMConverter.convert(value = status) ?: return
|
||||
val statusDM = converter.convert(value = status) ?: return
|
||||
persistenceDataStore.updateData { storedStatuses ->
|
||||
storedStatuses.toMutableMap().apply {
|
||||
put(key = userWalletId.stringValue, value = statusDM)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue