Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-15 13:41:23 +05:00
parent 0a78db08ec
commit db358d8014
16 changed files with 303 additions and 8 deletions

View file

@ -1,6 +1,7 @@
package com.tangem.tap.di.domain
import com.tangem.domain.onramp.*
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository
import dagger.Module
import dagger.Provides
@ -48,6 +49,15 @@ internal object OnrampDomainModule {
return CheckOnrampAvailabilityUseCase(onrampRepository)
}
@Provides
@Singleton
fun provideGetOnrampStatusUseCase(
onrampRepository: OnrampRepository,
onrampErrorResolver: OnrampErrorResolver,
): GetOnrampStatusUseCase {
return GetOnrampStatusUseCase(onrampRepository, onrampErrorResolver)
}
@Provides
@Singleton
fun provideGetOnrampCurrencyUseCase(onrampRepository: OnrampRepository): GetOnrampCurrencyUseCase {

View file

@ -14,8 +14,8 @@ data class OnrampStatusResponse(
@Json(name = "payoutAddress")
val payoutAddress: String,
// @Json(name = "status")
// val status: ???
@Json(name = "status")
val status: Status,
@Json(name = "failReason")
val failReason: String?,
@ -48,14 +48,46 @@ data class OnrampStatusResponse(
val toDecimals: String,
@Json(name = "toAmount")
val toAmount: String,
val toAmount: String?,
@Json(name = "toActualAmount")
val toActualAmount: String,
val toActualAmount: String?,
@Json(name = "paymentMethod")
val paymentMethod: String,
@Json(name = "countryCode")
val countryCode: String,
)
)
enum class Status {
@Json(name = "created")
Created,
@Json(name = "expired")
Expired,
@Json(name = "waiting-for-payment")
WaitingForPayment,
@Json(name = "payment-processing")
PaymentProcessing,
@Json(name = "verifying")
Verifying,
@Json(name = "failed")
Failed,
@Json(name = "paid")
Paid,
@Json(name = "sending")
Sending,
@Json(name = "finished")
Finished,
@Json(name = "paused")
Paused,
}

View file

@ -107,6 +107,8 @@ object PreferencesKeys {
val ONRAMP_DEFAULT_COUNTRY by lazy { stringPreferencesKey(name = "onrampDefaultCountry") }
val ONRAMP_TRANSACTIONS_STATUSES_KEY by lazy { stringPreferencesKey(name = "onrampTransactionsStatuses") }
// region Permission
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")

View file

@ -150,4 +150,12 @@ suspend inline fun <reified T> AppPreferencesStore.getObjectSetSync(key: Prefere
?.get(key)
?.let(adapter::fromJson)
.orEmpty()
}
/** Get flow of set of [T] by string [key], or empty if data is not found */
inline fun <reified T> AppPreferencesStore.getObjectSet(key: Preferences.Key<String>): Flow<Set<T>> {
val adapter = moshi.adapter<Set<T>>(Types.newParameterizedType(Set::class.java, T::class.java))
return data.map {
it[key]?.let(adapter::fromJson) ?: emptySet()
}
}

View file

@ -5,9 +5,9 @@ import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.domain.onramp.model.OnrampError
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
internal class DefaultOnrampErrorResolver(
private val onrampErrorConverter: OnrampErrorConverter,
) : OnrampErrorResolver {
internal class DefaultOnrampErrorResolver : OnrampErrorResolver {
private val onrampErrorConverter = OnrampErrorConverter()
override fun resolve(throwable: Throwable): OnrampError {
return if (throwable is ApiResponseError.HttpException) {

View file

@ -2,6 +2,7 @@ package com.tangem.data.onramp
import com.tangem.data.onramp.converters.CountryConverter
import com.tangem.data.onramp.converters.CurrencyConverter
import com.tangem.data.onramp.converters.StatusConverter
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.onramp.OnrampApi
import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO
@ -13,6 +14,7 @@ import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.preferences.utils.storeObject
import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.onramp.model.OnrampStatus
import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
@ -27,6 +29,7 @@ internal class DefaultOnrampRepository(
private val currencyConverter = CurrencyConverter()
private val countryConverter = CountryConverter(currencyConverter)
private val statusConverter = StatusConverter()
override suspend fun getCurrencies(): List<OnrampCurrency> = withContext(dispatchers.io) {
onrampApi.getCurrencies()
@ -46,6 +49,12 @@ internal class DefaultOnrampRepository(
.let(countryConverter::convert)
}
override suspend fun getStatus(txId: String): OnrampStatus = withContext(dispatchers.io) {
onrampApi.getStatus(txId)
.getOrThrow()
.let(statusConverter::convert)
}
override suspend fun saveDefaultCurrency(currency: OnrampCurrency) = withContext(dispatchers.io) {
appPreferencesStore.storeObject<OnrampCurrencyDTO>(
key = PreferencesKeys.ONRAMP_DEFAULT_CURRENCY,

View file

@ -0,0 +1,77 @@
package com.tangem.data.onramp
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getObjectSet
import com.tangem.datasource.local.preferences.utils.getObjectSetSync
import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
internal class DefaultOnrampTransactionRepository(
private val appPreferencesStore: AppPreferencesStore,
private val dispatchers: CoroutineDispatcherProvider,
) : OnrampTransactionRepository {
override suspend fun storeTransaction(transaction: OnrampTransaction) {
withContext(dispatchers.io) {
appPreferencesStore.editData { mutablePreferences ->
val stored = mutablePreferences.getObjectSet<OnrampTransaction>(
PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
)
val updated = stored?.toMutableSet()
?.addOrReplace(transaction) { it.txId == transaction.txId }
?: mutableSetOf(transaction)
mutablePreferences.setObjectSet(
key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
value = updated,
)
}
}
}
override suspend fun getTransactionById(txId: String): OnrampTransaction? = withContext(dispatchers.io) {
val stored = appPreferencesStore.getObjectSetSync<OnrampTransaction>(
PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
)
stored.firstOrNull { it.txId == txId }
}
override fun getTransactions(
userWalletId: UserWalletId,
toCryptoCurrency: CryptoCurrency.ID,
): Flow<List<OnrampTransaction>> = appPreferencesStore
.getObjectSet<OnrampTransaction>(PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY)
.map { transactions ->
transactions.filter {
it.userWalletId == userWalletId && it.toCurrency.id == toCryptoCurrency
}
}
override suspend fun removeTransaction(txId: String) {
withContext(dispatchers.io) {
appPreferencesStore.editData { mutablePreferences ->
runCatching {
val stored = mutablePreferences.getObjectSet<OnrampTransaction>(
PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
)?.toMutableSet()
stored?.removeIf { it.txId == txId }
mutablePreferences.setObjectSet(
key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY,
value = stored ?: emptySet(),
)
}
}
}
}
}

View file

@ -0,0 +1,30 @@
package com.tangem.data.onramp.converters
import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse
import com.tangem.domain.onramp.model.OnrampStatus
import com.tangem.utils.converter.Converter
internal class StatusConverter : Converter<OnrampStatusResponse, OnrampStatus> {
override fun convert(value: OnrampStatusResponse): OnrampStatus {
return OnrampStatus(
txId = value.txId,
providerId = value.providerId,
payoutAddress = value.payoutAddress,
status = OnrampStatus.Status.valueOf(value.status.name),
failReason = value.failReason,
externalTxId = value.externalTxId,
externalTxUrl = value.externalTxUrl,
payoutHash = value.payoutHash,
createdAt = value.createdAt,
fromCurrencyCode = value.fromCurrencyCode,
fromAmount = value.fromAmount,
toContractAddress = value.toContractAddress,
toNetwork = value.toNetwork,
toDecimals = value.toDecimals,
toAmount = value.toAmount,
toActualAmount = value.toActualAmount,
paymentMethod = value.paymentMethod,
countryCode = value.countryCode,
)
}
}

View file

@ -1,9 +1,13 @@
package com.tangem.data.onramp.di
import com.tangem.data.onramp.DefaultOnrampErrorResolver
import com.tangem.data.onramp.DefaultOnrampRepository
import com.tangem.data.onramp.DefaultOnrampTransactionRepository
import com.tangem.datasource.api.onramp.OnrampApi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository
import com.tangem.domain.onramp.repositories.OnrampTransactionRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -28,4 +32,22 @@ internal object OnrampDataModule {
appPreferencesStore = appPreferencesStore,
)
}
@Provides
@Singleton
fun provideOnrampTransactionRepository(
appPreferencesStore: AppPreferencesStore,
dispatchers: CoroutineDispatcherProvider,
): OnrampTransactionRepository {
return DefaultOnrampTransactionRepository(
appPreferencesStore = appPreferencesStore,
dispatchers = dispatchers,
)
}
@Provides
@Singleton
fun provideOnrampErrorResolver(): OnrampErrorResolver {
return DefaultOnrampErrorResolver()
}
}

View file

@ -6,6 +6,8 @@ plugins {
dependencies {
api(projects.domain.onramp.models)
api(projects.domain.tokens.models)
api(projects.domain.wallets.models)
api(projects.domain.core)
implementation(deps.kotlin.serialization)

View file

@ -6,6 +6,8 @@ plugins {
dependencies {
implementation(projects.domain.core)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
implementation(deps.kotlin.serialization)
implementation(deps.jodatime)

View file

@ -0,0 +1,38 @@
package com.tangem.domain.onramp.model
import kotlinx.serialization.Serializable
@Serializable
data class OnrampStatus(
val txId: String,
val providerId: String,
val payoutAddress: String,
val status: Status,
val failReason: String?,
val externalTxId: String,
val externalTxUrl: String?,
val payoutHash: String?,
val createdAt: String,
val fromCurrencyCode: String,
val fromAmount: String,
val toContractAddress: String,
val toNetwork: String,
val toDecimals: String,
val toAmount: String?,
val toActualAmount: String?,
val paymentMethod: String,
val countryCode: String,
) {
enum class Status {
Created,
Expired,
Paused,
WaitingForPayment,
PaymentProcessing,
Verifying,
Failed,
Paid,
Sending,
Finished,
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.domain.onramp.model.cache
import com.tangem.domain.core.serialization.SerializedBigDecimal
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.serialization.Serializable
@Serializable
data class OnrampTransaction(
val txId: String,
val userWalletId: UserWalletId,
val fromAmount: SerializedBigDecimal,
val fromCurrency: OnrampCurrency,
val toAmount: SerializedBigDecimal,
val toCurrency: CryptoCurrency,
val timestamp: Long,
val providerName: String,
val providerImageUrl: String,
)

View file

@ -0,0 +1,21 @@
package com.tangem.domain.onramp
import arrow.core.Either
import com.tangem.domain.onramp.model.OnrampError
import com.tangem.domain.onramp.model.OnrampStatus
import com.tangem.domain.onramp.repositories.OnrampErrorResolver
import com.tangem.domain.onramp.repositories.OnrampRepository
class GetOnrampStatusUseCase(
private val onrampRepository: OnrampRepository,
private val errorResolver: OnrampErrorResolver,
) {
suspend operator fun invoke(txId: String): Either<OnrampError, OnrampStatus> {
return Either.catch {
onrampRepository.getStatus(txId)
}.mapLeft {
errorResolver.resolve(it)
}
}
}

View file

@ -2,12 +2,17 @@ package com.tangem.domain.onramp.repositories
import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.onramp.model.OnrampStatus
import kotlinx.coroutines.flow.Flow
interface OnrampRepository {
// api
suspend fun getCurrencies(): List<OnrampCurrency>
suspend fun getCountries(): List<OnrampCountry>
suspend fun getCountryByIp(): OnrampCountry
suspend fun getStatus(txId: String): OnrampStatus
// cache
suspend fun saveDefaultCurrency(currency: OnrampCurrency)
suspend fun getDefaultCurrencySync(): OnrampCurrency?
fun getDefaultCurrency(): Flow<OnrampCurrency?>

View file

@ -0,0 +1,17 @@
package com.tangem.domain.onramp.repositories
import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
interface OnrampTransactionRepository {
suspend fun storeTransaction(transaction: OnrampTransaction)
suspend fun getTransactionById(txId: String): OnrampTransaction?
fun getTransactions(userWalletId: UserWalletId, toCryptoCurrency: CryptoCurrency.ID): Flow<List<OnrampTransaction>>
suspend fun removeTransaction(txId: String)
}