diff --git a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt index 81e25125ae..1b04e8f513 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt @@ -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 { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampStatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampStatusResponse.kt index 6ef0416464..301fd2421f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampStatusResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampStatusResponse.kt @@ -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, -) \ No newline at end of file +) + +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, +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index a049a7db22..9f4cf80fea 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -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") diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt index a7fd3cafef..0369cf22cd 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt @@ -150,4 +150,12 @@ suspend inline fun 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 AppPreferencesStore.getObjectSet(key: Preferences.Key): Flow> { + val adapter = moshi.adapter>(Types.newParameterizedType(Set::class.java, T::class.java)) + return data.map { + it[key]?.let(adapter::fromJson) ?: emptySet() + } } \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt index 0ec508bd89..b03b5c6c69 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt @@ -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) { diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index 31afbf4c67..ef6cd4523e 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -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 = 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( key = PreferencesKeys.ONRAMP_DEFAULT_CURRENCY, diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt new file mode 100644 index 0000000000..85fb0b94f9 --- /dev/null +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt @@ -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( + 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( + PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + ) + + stored.firstOrNull { it.txId == txId } + } + + override fun getTransactions( + userWalletId: UserWalletId, + toCryptoCurrency: CryptoCurrency.ID, + ): Flow> = appPreferencesStore + .getObjectSet(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( + PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + )?.toMutableSet() + + stored?.removeIf { it.txId == txId } + + mutablePreferences.setObjectSet( + key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + value = stored ?: emptySet(), + ) + } + } + } + } +} \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/converters/StatusConverter.kt b/data/onramp/src/main/java/com/tangem/data/onramp/converters/StatusConverter.kt new file mode 100644 index 0000000000..70805a77fe --- /dev/null +++ b/data/onramp/src/main/java/com/tangem/data/onramp/converters/StatusConverter.kt @@ -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 { + 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, + ) + } +} \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt b/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt index d47543851a..fed11e54d0 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt @@ -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() + } } \ No newline at end of file diff --git a/domain/onramp/build.gradle.kts b/domain/onramp/build.gradle.kts index 43956b9497..b676e92120 100644 --- a/domain/onramp/build.gradle.kts +++ b/domain/onramp/build.gradle.kts @@ -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) diff --git a/domain/onramp/models/build.gradle.kts b/domain/onramp/models/build.gradle.kts index 7558c5ca61..dc13a99957 100644 --- a/domain/onramp/models/build.gradle.kts +++ b/domain/onramp/models/build.gradle.kts @@ -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) diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt new file mode 100644 index 0000000000..eec20f05f7 --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt @@ -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, + } +} \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt new file mode 100644 index 0000000000..9753bc85e7 --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt @@ -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, +) \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt new file mode 100644 index 0000000000..4692c6d6b7 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt @@ -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 { + return Either.catch { + onrampRepository.getStatus(txId) + }.mapLeft { + errorResolver.resolve(it) + } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt index 85b4e27f69..7e6dc7df12 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt @@ -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 suspend fun getCountries(): List suspend fun getCountryByIp(): OnrampCountry + suspend fun getStatus(txId: String): OnrampStatus + + // cache suspend fun saveDefaultCurrency(currency: OnrampCurrency) suspend fun getDefaultCurrencySync(): OnrampCurrency? fun getDefaultCurrency(): Flow diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt new file mode 100644 index 0000000000..5796bec18e --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt @@ -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> + + suspend fun removeTransaction(txId: String) +} \ No newline at end of file