From a006a87f9d31216acb77ca54fb157359c4fa2573 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 13 Jul 2026 15:54:09 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../local/preferences/PreferencesKeys.kt | 3 + core/res/src/main/res/values/strings.xml | 4 + data/visa/build.gradle.kts | 4 + .../tangem/data/pay/di/TangemPayDataModule.kt | 14 ++ .../repository/DefaultCashbackRepository.kt | 12 ++ .../repository/DefaultOnboardingRepository.kt | 2 +- .../DefaultTangemPayCardDetailsRepository.kt | 2 +- .../DefaultTangemPayWithdrawRepository.kt | 2 +- .../repository/TangemPayRequestPerformer.kt | 2 +- .../pay/store}/DefaultTangemPayStorage.kt | 16 +- .../data/pay/store}/TangemPayStorage.kt | 6 +- .../repository/MockAwareCashbackRepository.kt | 6 + .../pay/store}/MockAwareTangemPayStorage.kt | 9 +- .../store/di}/TangemPayStorageMockedModule.kt | 6 +- .../di}/TangemPayStorageProductionModule.kt | 6 +- .../DefaultTangemPayWithdrawRepositoryTest.kt | 2 +- .../pay/repository/CashbackRepository.kt | 6 + ...GetCashbackDeactivationDismissedUseCase.kt | 17 ++ ...SetCashbackDeactivationDismissedUseCase.kt | 17 ++ .../entity/TangemPayDetailsStateFactory.kt | 3 + .../tangempay/entity/TangemPayDetailsUM.kt | 15 ++ .../tangempay/model/TangemPayDetailsModel.kt | 35 ++++ .../transformers/CashbackBlockTransformer.kt | 59 ++++++ .../features/tangempay/ui/CashbackBlock.kt | 109 +++++++++++ .../tangempay/ui/TangemPayDetailsScreenV2.kt | 9 + .../model/TangemPayDetailsModelTest.kt | 3 + .../CashbackBlockTransformerTest.kt | 171 ++++++++++++++++++ 27 files changed, 524 insertions(+), 16 deletions(-) rename {app/src/main/java/com/tangem/tap/data => data/visa/src/main/kotlin/com/tangem/data/pay/store}/DefaultTangemPayStorage.kt (95%) rename {core/datasource/src/main/java/com/tangem/datasource/local/visa => data/visa/src/main/kotlin/com/tangem/data/pay/store}/TangemPayStorage.kt (93%) rename {app/src/mocked/java/com/tangem/tap/data => data/visa/src/mocked/kotlin/com/tangem/data/pay/store}/MockAwareTangemPayStorage.kt (93%) rename {app/src/mocked/java/com/tangem/tap/di/data => data/visa/src/mocked/kotlin/com/tangem/data/pay/store/di}/TangemPayStorageMockedModule.kt (70%) rename {app/src/prodDi/java/com/tangem/tap/di/data => data/visa/src/prodDi/kotlin/com/tangem/data/pay/store/di}/TangemPayStorageProductionModule.kt (71%) create mode 100644 domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/GetCashbackDeactivationDismissedUseCase.kt create mode 100644 domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/SetCashbackDeactivationDismissedUseCase.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/CashbackBlockTransformer.kt create mode 100644 features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/CashbackBlock.kt create mode 100644 features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/model/transformers/CashbackBlockTransformerTest.kt 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 559e62a0a2..8f90fc3a0a 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 @@ -207,6 +207,9 @@ object PreferencesKeys { fun getTangemPayAddToWalletKey(customerWalletAddress: String) = booleanPreferencesKey("tangem_pay_add_to_wallet_done_key_$customerWalletAddress") + fun getTangemPayCashbackDeactivationDismissedKey(customerWalletAddress: String) = + booleanPreferencesKey("tangem_pay_cashback_deactivation_dismissed_key_$customerWalletAddress") + fun getTangemPayOrderIdKey(customerWalletAddress: String) = stringPreferencesKey("tangem_pay_order_id_key_$customerWalletAddress") diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index eeb613a920..61dcacefa9 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1919,6 +1919,10 @@ %d card %d cards + It was made due to your suspicious behavior. Contact support to learn more + Cashback deactivated + Will be deposited on %1$s + %1$s cashback in %2$s Change PIN-code Come back to the app if you forget it. Card diff --git a/data/visa/build.gradle.kts b/data/visa/build.gradle.kts index 5402f09333..53d5427cf1 100644 --- a/data/visa/build.gradle.kts +++ b/data/visa/build.gradle.kts @@ -43,6 +43,10 @@ dependencies { api(tangemDeps.hot.core) implementation(tangemDeps.blockchain) implementation(tangemDeps.card.core) + // AndroidSecureStorageV2 for TangemPay secure token storage (see com.tangem.data.pay.store). + implementation(tangemDeps.card.android) { + exclude(module = "joda-time") + } // endregion // region DI diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt index 552ed0e6f3..f0093125c2 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt @@ -306,6 +306,20 @@ internal interface TangemPayDataModule { return GetCashbackSummaryUseCase(cashbackRepository) } + @Provides + fun provideGetCashbackDeactivationDismissedUseCase( + cashbackRepository: CashbackRepository, + ): GetCashbackDeactivationDismissedUseCase { + return GetCashbackDeactivationDismissedUseCase(cashbackRepository) + } + + @Provides + fun provideSetCashbackDeactivationDismissedUseCase( + cashbackRepository: CashbackRepository, + ): SetCashbackDeactivationDismissedUseCase { + return SetCashbackDeactivationDismissedUseCase(cashbackRepository) + } + @Provides fun provideCheckOrderConflictUseCase( customerOrderRepository: CustomerOrderRepository, diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCashbackRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCashbackRepository.kt index b8e1a2ebf8..6a18ef489c 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCashbackRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCashbackRepository.kt @@ -3,6 +3,7 @@ package com.tangem.data.pay.repository import arrow.core.Either import com.tangem.data.pay.util.CashbackSummaryConverter import com.tangem.datasource.api.pay.TangemPayApi +import com.tangem.data.pay.store.TangemPayStorage import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.model.CashbackSummary import com.tangem.domain.pay.repository.CashbackRepository @@ -12,6 +13,7 @@ import javax.inject.Inject internal class DefaultCashbackRepository @Inject constructor( private val tangemPayApi: TangemPayApi, private val requestHelper: TangemPayRequestPerformer, + private val storage: TangemPayStorage, ) : CashbackRepository { override suspend fun getCashbackSummary(userWalletId: UserWalletId): Either { @@ -19,4 +21,14 @@ internal class DefaultCashbackRepository @Inject constructor( tangemPayApi.getCashbackSummary(authHeader = authHeader) }.map(CashbackSummaryConverter::convert) } + + override suspend fun isDeactivationBannerDismissed(userWalletId: UserWalletId): Boolean { + val customerWalletAddress = requestHelper.getCustomerWalletAddress(userWalletId) + return storage.getCashbackDeactivationDismissed(customerWalletAddress) + } + + override suspend fun setDeactivationBannerDismissed(userWalletId: UserWalletId) { + val customerWalletAddress = requestHelper.getCustomerWalletAddress(userWalletId) + storage.storeCashbackDeactivationDismissed(customerWalletAddress = customerWalletAddress, isDismissed = true) + } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt index 4c3b7dae3d..1638656a09 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt @@ -16,7 +16,7 @@ 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 -import com.tangem.datasource.local.visa.TangemPayStorage +import com.tangem.data.pay.store.TangemPayStorage import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore import com.tangem.domain.common.wallets.UserWalletsListRepository import com.tangem.domain.models.account.Account diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt index 18d0d10209..62be79cd12 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayCardDetailsRepository.kt @@ -20,7 +20,7 @@ import com.tangem.datasource.api.pay.models.request.SetPinRequest import com.tangem.datasource.api.pay.models.response.FreezeUnfreezeCardResponse import com.tangem.datasource.api.pay.models.response.OrderResponse.Result.Status import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore -import com.tangem.datasource.local.visa.TangemPayStorage +import com.tangem.data.pay.store.TangemPayStorage import com.tangem.domain.models.account.CardDisplayName import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.model.OrderStatus diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayWithdrawRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayWithdrawRepository.kt index 0a6842264b..8bfd7821d7 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayWithdrawRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayWithdrawRepository.kt @@ -9,7 +9,7 @@ import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.datasource.api.pay.models.request.WithdrawDataRequest import com.tangem.datasource.api.pay.models.request.WithdrawRequest import com.tangem.datasource.api.pay.models.response.WithdrawResponse -import com.tangem.datasource.local.visa.TangemPayStorage +import com.tangem.data.pay.store.TangemPayStorage import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt index c29153ce52..9dbfd6d8c2 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/TangemPayRequestPerformer.kt @@ -11,7 +11,7 @@ import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.pay.TangemPayAuthApi import com.tangem.datasource.api.pay.models.request.RefreshCustomerWalletAccessTokenRequest import com.tangem.datasource.api.pay.models.response.TangemPayGetTokensResponse -import com.tangem.datasource.local.visa.TangemPayStorage +import com.tangem.data.pay.store.TangemPayStorage import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.visa.error.VisaApiError import com.tangem.domain.visa.model.TangemPayAuthTokens diff --git a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/store/DefaultTangemPayStorage.kt similarity index 95% rename from app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt rename to data/visa/src/main/kotlin/com/tangem/data/pay/store/DefaultTangemPayStorage.kt index 6b48f51b6f..bac8bdeefe 100644 --- a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/store/DefaultTangemPayStorage.kt @@ -1,4 +1,4 @@ -package com.tangem.tap.data +package com.tangem.data.pay.store import android.content.Context import com.squareup.moshi.JsonAdapter @@ -14,7 +14,6 @@ import com.tangem.datasource.local.preferences.utils.getObjectMapSync import com.tangem.datasource.local.preferences.utils.getSyncOrDefault import com.tangem.datasource.local.preferences.utils.getSyncOrNull import com.tangem.datasource.local.preferences.utils.store -import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.TangemPayWithdrawState import com.tangem.domain.visa.model.TangemPayAuthTokens @@ -130,6 +129,19 @@ internal class DefaultTangemPayStorage @Inject constructor( appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), isDone) } + override suspend fun getCashbackDeactivationDismissed(customerWalletAddress: String): Boolean { + return appPreferencesStore.getSyncOrNull( + key = PreferencesKeys.getTangemPayCashbackDeactivationDismissedKey(customerWalletAddress), + ) == true + } + + override suspend fun storeCashbackDeactivationDismissed(customerWalletAddress: String, isDismissed: Boolean) { + appPreferencesStore.store( + key = PreferencesKeys.getTangemPayCashbackDeactivationDismissedKey(customerWalletAddress), + value = isDismissed, + ) + } + override suspend fun clearOrderId(customerWalletAddress: String) { appPreferencesStore.store(PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress), "") } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/store/TangemPayStorage.kt similarity index 93% rename from core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt rename to data/visa/src/main/kotlin/com/tangem/data/pay/store/TangemPayStorage.kt index 60c91d5d63..7e3b4647d9 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/store/TangemPayStorage.kt @@ -1,4 +1,4 @@ -package com.tangem.datasource.local.visa +package com.tangem.data.pay.store import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.TangemPayWithdrawState @@ -32,6 +32,10 @@ interface TangemPayStorage { suspend fun getAddToWalletDone(customerWalletAddress: String): Boolean suspend fun storeAddToWalletDone(customerWalletAddress: String, isDone: Boolean) + + suspend fun getCashbackDeactivationDismissed(customerWalletAddress: String): Boolean + + suspend fun storeCashbackDeactivationDismissed(customerWalletAddress: String, isDismissed: Boolean) suspend fun storeCheckCustomerWalletResult(userWalletId: UserWalletId, isPaeraCustomer: Boolean) suspend fun checkCustomerWalletResult(userWalletId: UserWalletId): Boolean? diff --git a/data/visa/src/mocked/kotlin/com/tangem/data/pay/repository/MockAwareCashbackRepository.kt b/data/visa/src/mocked/kotlin/com/tangem/data/pay/repository/MockAwareCashbackRepository.kt index 208c11fe9b..f56952ccd3 100644 --- a/data/visa/src/mocked/kotlin/com/tangem/data/pay/repository/MockAwareCashbackRepository.kt +++ b/data/visa/src/mocked/kotlin/com/tangem/data/pay/repository/MockAwareCashbackRepository.kt @@ -33,6 +33,12 @@ internal class MockAwareCashbackRepository @Inject constructor( return real.getCashbackSummary(userWalletId) } + override suspend fun isDeactivationBannerDismissed(userWalletId: UserWalletId): Boolean = + real.isDeactivationBannerDismissed(userWalletId) + + override suspend fun setDeactivationBannerDismissed(userWalletId: UserWalletId) = + real.setDeactivationBannerDismissed(userWalletId) + private companion object { val MOCK_SUMMARY = CashbackSummary.Enabled( displayMode = CashbackDisplayMode.FULL, diff --git a/app/src/mocked/java/com/tangem/tap/data/MockAwareTangemPayStorage.kt b/data/visa/src/mocked/kotlin/com/tangem/data/pay/store/MockAwareTangemPayStorage.kt similarity index 93% rename from app/src/mocked/java/com/tangem/tap/data/MockAwareTangemPayStorage.kt rename to data/visa/src/mocked/kotlin/com/tangem/data/pay/store/MockAwareTangemPayStorage.kt index cbbcb0c848..9a2b463c1b 100644 --- a/app/src/mocked/java/com/tangem/tap/data/MockAwareTangemPayStorage.kt +++ b/data/visa/src/mocked/kotlin/com/tangem/data/pay/store/MockAwareTangemPayStorage.kt @@ -1,9 +1,8 @@ -package com.tangem.tap.data +package com.tangem.data.pay.store import com.tangem.datasource.api.common.config.ApiConfig import com.tangem.datasource.api.common.config.ApiEnvironment import com.tangem.datasource.api.common.config.managers.ApiConfigsManager -import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.TangemPayWithdrawState import com.tangem.domain.visa.model.TangemPayAuthTokens @@ -78,6 +77,12 @@ internal class MockAwareTangemPayStorage @Inject constructor( override suspend fun storeAddToWalletDone(customerWalletAddress: String, isDone: Boolean) = real.storeAddToWalletDone(customerWalletAddress, isDone) + override suspend fun getCashbackDeactivationDismissed(customerWalletAddress: String): Boolean = + real.getCashbackDeactivationDismissed(customerWalletAddress) + + override suspend fun storeCashbackDeactivationDismissed(customerWalletAddress: String, isDismissed: Boolean) = + real.storeCashbackDeactivationDismissed(customerWalletAddress, isDismissed) + override suspend fun clearOrderId(customerWalletAddress: String) = real.clearOrderId(customerWalletAddress) diff --git a/app/src/mocked/java/com/tangem/tap/di/data/TangemPayStorageMockedModule.kt b/data/visa/src/mocked/kotlin/com/tangem/data/pay/store/di/TangemPayStorageMockedModule.kt similarity index 70% rename from app/src/mocked/java/com/tangem/tap/di/data/TangemPayStorageMockedModule.kt rename to data/visa/src/mocked/kotlin/com/tangem/data/pay/store/di/TangemPayStorageMockedModule.kt index 8ccf4a8ac4..73f44ac852 100644 --- a/app/src/mocked/java/com/tangem/tap/di/data/TangemPayStorageMockedModule.kt +++ b/data/visa/src/mocked/kotlin/com/tangem/data/pay/store/di/TangemPayStorageMockedModule.kt @@ -1,7 +1,7 @@ -package com.tangem.tap.di.data +package com.tangem.data.pay.store.di -import com.tangem.datasource.local.visa.TangemPayStorage -import com.tangem.tap.data.MockAwareTangemPayStorage +import com.tangem.data.pay.store.MockAwareTangemPayStorage +import com.tangem.data.pay.store.TangemPayStorage import dagger.Binds import dagger.Module import dagger.hilt.InstallIn diff --git a/app/src/prodDi/java/com/tangem/tap/di/data/TangemPayStorageProductionModule.kt b/data/visa/src/prodDi/kotlin/com/tangem/data/pay/store/di/TangemPayStorageProductionModule.kt similarity index 71% rename from app/src/prodDi/java/com/tangem/tap/di/data/TangemPayStorageProductionModule.kt rename to data/visa/src/prodDi/kotlin/com/tangem/data/pay/store/di/TangemPayStorageProductionModule.kt index dffc33767d..f38ac8cf29 100644 --- a/app/src/prodDi/java/com/tangem/tap/di/data/TangemPayStorageProductionModule.kt +++ b/data/visa/src/prodDi/kotlin/com/tangem/data/pay/store/di/TangemPayStorageProductionModule.kt @@ -1,7 +1,7 @@ -package com.tangem.tap.di.data +package com.tangem.data.pay.store.di -import com.tangem.datasource.local.visa.TangemPayStorage -import com.tangem.tap.data.DefaultTangemPayStorage +import com.tangem.data.pay.store.DefaultTangemPayStorage +import com.tangem.data.pay.store.TangemPayStorage import dagger.Binds import dagger.Module import dagger.hilt.InstallIn diff --git a/data/visa/src/test/kotlin/com/tangem/data/pay/repository/DefaultTangemPayWithdrawRepositoryTest.kt b/data/visa/src/test/kotlin/com/tangem/data/pay/repository/DefaultTangemPayWithdrawRepositoryTest.kt index eb7b4a94b3..9e4ac3f681 100644 --- a/data/visa/src/test/kotlin/com/tangem/data/pay/repository/DefaultTangemPayWithdrawRepositoryTest.kt +++ b/data/visa/src/test/kotlin/com/tangem/data/pay/repository/DefaultTangemPayWithdrawRepositoryTest.kt @@ -10,7 +10,7 @@ import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.datasource.api.pay.models.response.WithdrawDataResponse import com.tangem.datasource.api.pay.models.response.WithdrawResponse import com.tangem.datasource.api.tangemTech.models.QuotesResponse -import com.tangem.datasource.local.visa.TangemPayStorage +import com.tangem.data.pay.store.TangemPayStorage import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/CashbackRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/CashbackRepository.kt index 293cc5a2ff..ca0b4bb904 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/CashbackRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/CashbackRepository.kt @@ -12,4 +12,10 @@ interface CashbackRepository { /** Loads the cashback summary for the customer of [userWalletId]. */ suspend fun getCashbackSummary(userWalletId: UserWalletId): Either + + /** Whether the "Cashback deactivated" banner was permanently dismissed for [userWalletId]. */ + suspend fun isDeactivationBannerDismissed(userWalletId: UserWalletId): Boolean + + /** Permanently dismisses the "Cashback deactivated" banner for [userWalletId]. */ + suspend fun setDeactivationBannerDismissed(userWalletId: UserWalletId) } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/GetCashbackDeactivationDismissedUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/GetCashbackDeactivationDismissedUseCase.kt new file mode 100644 index 0000000000..a1463464d7 --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/GetCashbackDeactivationDismissedUseCase.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.pay.usecase + +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.repository.CashbackRepository + +/** + * Reads whether the "Cashback deactivated" banner was permanently dismissed for the wallet. + * + * Used by the Payment account cashback block to decide whether to show the deactivation banner. + */ +class GetCashbackDeactivationDismissedUseCase( + private val cashbackRepository: CashbackRepository, +) { + suspend operator fun invoke(userWalletId: UserWalletId): Boolean { + return cashbackRepository.isDeactivationBannerDismissed(userWalletId) + } +} \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/SetCashbackDeactivationDismissedUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/SetCashbackDeactivationDismissedUseCase.kt new file mode 100644 index 0000000000..05d67e63c1 --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/SetCashbackDeactivationDismissedUseCase.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.pay.usecase + +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.repository.CashbackRepository + +/** + * Permanently dismisses the "Cashback deactivated" banner for the wallet. + * + * Triggered by the "Got it" button on the Payment account cashback deactivation banner. + */ +class SetCashbackDeactivationDismissedUseCase( + private val cashbackRepository: CashbackRepository, +) { + suspend operator fun invoke(userWalletId: UserWalletId) { + cashbackRepository.setDeactivationBannerDismissed(userWalletId) + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt index 7aff06b034..1e9b7ecf71 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsStateFactory.kt @@ -69,6 +69,7 @@ internal class TangemPayDetailsStateFactory( addToWalletBlockState = null, errorNotificationConfig = null, accountDeactivatedNotificationConfig = null, + cashbackBlockState = null, ) } @@ -120,6 +121,7 @@ internal class TangemPayDetailsStateFactory( addToWalletBlockState = null, errorNotificationConfig = errorNotification ?: awaitingDepositNotification, accountDeactivatedNotificationConfig = null, + cashbackBlockState = null, ) } @@ -153,6 +155,7 @@ internal class TangemPayDetailsStateFactory( addToWalletBlockState = null, errorNotificationConfig = null, accountDeactivatedNotificationConfig = accountDeactivatedNotification, + cashbackBlockState = null, ) } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt index 9200676a5b..f1a1384f8a 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsUM.kt @@ -22,8 +22,23 @@ internal data class TangemPayDetailsUM( val isBalanceHidden: Boolean, val errorNotificationConfig: NotificationConfig?, val accountDeactivatedNotificationConfig: NotificationConfig?, + val cashbackBlockState: CashbackBlockUM? = null, ) +@Immutable +internal sealed interface CashbackBlockUM { + + data class Widget( + val title: TextReference, + val subtitle: TextReference, + val onClick: () -> Unit, + ) : CashbackBlockUM + + data class DeactivatedBanner( + val onGotIt: () -> Unit, + ) : CashbackBlockUM +} + internal data class TangemPayCardDetailsUM( val number: String, val numberShort: String, diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt index 36dc36261b..89a0d9acd7 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt @@ -36,12 +36,16 @@ import com.tangem.domain.pay.repository.OnboardingRepository import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository import com.tangem.domain.pay.repository.TangemPayWithdrawRepository import com.tangem.domain.pay.usecase.CancelTangemPayOrderUseCase +import com.tangem.domain.pay.usecase.GetCashbackDeactivationDismissedUseCase +import com.tangem.domain.pay.usecase.GetCashbackSummaryUseCase import com.tangem.domain.pay.usecase.GetCustomerOffersUseCase import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase +import com.tangem.domain.pay.usecase.SetCashbackDeactivationDismissedUseCase import com.tangem.domain.tangempay.TangemPayAnalyticsEvents import com.tangem.domain.visa.model.TangemPayTxHistoryItem import com.tangem.features.tangempay.TangemPayConstants import com.tangem.features.tangempay.TangemPayFeatureToggles +import com.tangem.features.tangempay.cashback.impl.model.TangemPayCashbackDateFormatter import com.tangem.features.tangempay.components.AddFundsListener import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent import com.tangem.features.tangempay.components.TangemPayIssueAdditionalCardComponent @@ -88,6 +92,9 @@ internal class TangemPayDetailsModel @Inject constructor( private val onboardingRepository: OnboardingRepository, private val getCustomerOffers: GetCustomerOffersUseCase, private val cancelTangemPayOrderUseCase: CancelTangemPayOrderUseCase, + private val getCashbackSummaryUseCase: GetCashbackSummaryUseCase, + private val getCashbackDeactivationDismissedUseCase: GetCashbackDeactivationDismissedUseCase, + private val setCashbackDeactivationDismissedUseCase: SetCashbackDeactivationDismissedUseCase, ) : Model(), TangemPayTxHistoryUiActions, TangemPayDetailIntents, @@ -128,6 +135,8 @@ internal class TangemPayDetailsModel @Inject constructor( private val refreshStateJobHolder = JobHolder() private val addToWalletBannerJobHolder = JobHolder() + private val cashbackBlockJobHolder = JobHolder() + private val cashbackDateFormatter = TangemPayCashbackDateFormatter() val bottomSheetNavigation: SlotNavigation = SlotNavigation() @@ -155,6 +164,7 @@ internal class TangemPayDetailsModel @Inject constructor( } is PaymentAccountStatusValue.Loaded -> { fetchAddToWalletBanner() + fetchCashbackBlock() val balanceTransformer = DetailsBalanceTransformer( fiatBalance = state.balance.fiatBalance, isMuted = !state.isFresh, @@ -265,6 +275,31 @@ internal class TangemPayDetailsModel @Inject constructor( }.saveIn(addToWalletBannerJobHolder) } + private fun fetchCashbackBlock() { + if (!tangemPayFeatureToggles.isCashbackEnabled) return + modelScope.launch { + getCashbackSummaryUseCase(userWalletId).onRight { summary -> + val isDismissed = getCashbackDeactivationDismissedUseCase(userWalletId) + uiState.update( + transformer = CashbackBlockTransformer( + summary = summary, + isDeactivationDismissed = isDismissed, + dateFormatter = cashbackDateFormatter, + onClick = ::onClickCashback, + onGotIt = ::onDismissCashbackDeactivation, + ), + ) + } + }.saveIn(cashbackBlockJobHolder) + } + + private fun onDismissCashbackDeactivation() { + modelScope.launch { + setCashbackDeactivationDismissedUseCase(userWalletId) + uiState.update { it.copy(cashbackBlockState = null) } + }.saveIn(cashbackBlockJobHolder) + } + private fun handleBalanceHiding() { getBalanceHidingSettingsUseCase().onEach { uiState.update(DetailBalanceVisibilityTransformer(isHidden = it.isBalanceHidden)) diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/CashbackBlockTransformer.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/CashbackBlockTransformer.kt new file mode 100644 index 0000000000..5ad9ccfa9f --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/transformers/CashbackBlockTransformer.kt @@ -0,0 +1,59 @@ +package com.tangem.features.tangempay.model.transformers + +import com.tangem.core.ui.R +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.defaultAmount +import com.tangem.core.ui.format.bigdecimal.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.core.ui.format.bigdecimal.getJavaCurrencyByCode +import com.tangem.domain.pay.model.CashbackSummary +import com.tangem.features.tangempay.cashback.impl.model.TangemPayCashbackDateFormatter +import com.tangem.features.tangempay.entity.CashbackBlockUM +import com.tangem.features.tangempay.entity.TangemPayDetailsUM +import com.tangem.utils.transformer.Transformer + +/** + * Resolves the Payment account cashback block from a [CashbackSummary]: + * - [CashbackSummary.Enabled] -> tappable [CashbackBlockUM.Widget]; + * - [CashbackSummary.Deactivated] (unless dismissed) -> [CashbackBlockUM.DeactivatedBanner]; + * - otherwise -> hidden (`null`). + */ +internal class CashbackBlockTransformer( + private val summary: CashbackSummary, + private val isDeactivationDismissed: Boolean, + private val dateFormatter: TangemPayCashbackDateFormatter, + private val onClick: () -> Unit, + private val onGotIt: () -> Unit, +) : Transformer { + + override fun transform(prevState: TangemPayDetailsUM): TangemPayDetailsUM { + val block: CashbackBlockUM? = when (summary) { + is CashbackSummary.Enabled -> buildWidget(summary) + CashbackSummary.Deactivated -> if (isDeactivationDismissed) { + null + } else { + CashbackBlockUM.DeactivatedBanner(onGotIt = onGotIt) + } + CashbackSummary.Disabled, + CashbackSummary.Unknown, + -> null + } + return prevState.copy(cashbackBlockState = block) + } + + private fun buildWidget(enabled: CashbackSummary.Enabled): CashbackBlockUM.Widget { + val cashback = enabled.cashback + val currency = getJavaCurrencyByCode(cashback.currency) + val amount = cashback.confirmedAmount.format { + fiat(currency.currencyCode, currency.symbol).defaultAmount() + } + val month = dateFormatter.formatMonth(cashback.period.year, cashback.period.month) + val window = dateFormatter.formatWindow(cashback.period.payoutStart, cashback.period.payoutEnd) + return CashbackBlockUM.Widget( + title = resourceReference(R.string.tangempay_cashback_widget_title, wrappedList(amount, month)), + subtitle = resourceReference(R.string.tangempay_cashback_deposited_on, wrappedList(window)), + onClick = onClick, + ) + } +} \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/CashbackBlock.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/CashbackBlock.kt new file mode 100644 index 0000000000..2709d514f3 --- /dev/null +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/CashbackBlock.kt @@ -0,0 +1,109 @@ +package com.tangem.features.tangempay.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.ds.button.* +import com.tangem.core.ui.ds.message.TangemMessage +import com.tangem.core.ui.ds.message.TangemMessageEffect +import com.tangem.core.ui.extensions.clickableSingle +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign +import com.tangem.features.tangempay.entity.CashbackBlockUM +import com.tangem.core.ui.R as CoreUiR + +/** + * Cashback block shown on the Payment account screen V2. Renders either a tappable widget with the + * accrued cashback amount or a warning banner when cashback has been deactivated. + */ +@Composable +internal fun CashbackBlock(state: CashbackBlockUM, modifier: Modifier = Modifier) { + when (state) { + is CashbackBlockUM.Widget -> { + TangemMessage( + modifier = modifier.clickableSingle(onClick = state.onClick), + title = state.title, + subtitle = state.subtitle, + messageEffect = TangemMessageEffect.Magic, + trailingContent = { + Icon( + imageVector = ImageVector.vectorResource(CoreUiR.drawable.ic_chevron_right_24), + contentDescription = null, + tint = TangemTheme.colors3.icon.secondary, + modifier = Modifier.size(20.dp), + ) + }, + ) + } + is CashbackBlockUM.DeactivatedBanner -> { + TangemMessage( + modifier = modifier, + title = resourceReference(CoreUiR.string.tangempay_cashback_deactivated_title), + subtitle = resourceReference(CoreUiR.string.tangempay_cashback_deactivated_description), + messageEffect = TangemMessageEffect.Warning, + buttons = { + TangemButton( + buttonUM = TangemButtonUM( + text = resourceReference(CoreUiR.string.common_got_it), + onClick = state.onGotIt, + iconPosition = TangemButtonIconPosition.End, + size = TangemButtonSize.X9, + type = TangemButtonType.Primary, + shape = TangemButtonShape.Rounded, + ), + modifier = Modifier.weight(1f), + ) + }, + ) + } + } +} + +// region preview + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun CashbackBlockWidgetPreview() { + TangemThemePreviewRedesign { + CashbackBlock( + state = CashbackBlockUM.Widget( + title = stringReference("$32.15 cashback in June"), + subtitle = stringReference("Will be deposited on July 2–5"), + onClick = {}, + ), + modifier = Modifier + .background(TangemTheme.colors3.bg.primary) + .padding(16.dp) + .fillMaxWidth(), + ) + } +} + +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun CashbackBlockDeactivatedBannerPreview() { + TangemThemePreviewRedesign { + CashbackBlock( + state = CashbackBlockUM.DeactivatedBanner(onGotIt = {}), + modifier = Modifier + .background(TangemTheme.colors3.bg.primary) + .padding(16.dp) + .fillMaxWidth(), + ) + } +} + +// endregion preview \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreenV2.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreenV2.kt index 80c23ef511..f1bd2f8542 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreenV2.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreenV2.kt @@ -118,6 +118,15 @@ internal fun TangemPayDetailsScreenV2( ), ) { payDetailsBody(state) + state.cashbackBlockState?.let { cashbackState -> + item("cashbackBlock") { + SpacerH12() + CashbackBlock( + state = cashbackState, + modifier = Modifier.padding(horizontal = TangemTheme.dimens2.x4), + ) + } + } item("promoBannersBlock") { promoBannersBlockComponent.Content( modifier = Modifier.padding(vertical = 12.dp), diff --git a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModelTest.kt b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModelTest.kt index 18e4532c16..fc2c2b505d 100644 --- a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModelTest.kt +++ b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModelTest.kt @@ -115,6 +115,9 @@ internal class TangemPayDetailsModelTest { onboardingRepository = mockk(relaxed = true), getCustomerOffers = mockk(relaxed = true), cancelTangemPayOrderUseCase = mockk(relaxed = true), + getCashbackSummaryUseCase = mockk(relaxed = true), + getCashbackDeactivationDismissedUseCase = mockk(relaxed = true), + setCashbackDeactivationDismissedUseCase = mockk(relaxed = true), ) } diff --git a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/model/transformers/CashbackBlockTransformerTest.kt b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/model/transformers/CashbackBlockTransformerTest.kt new file mode 100644 index 0000000000..de59f0cabf --- /dev/null +++ b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/model/transformers/CashbackBlockTransformerTest.kt @@ -0,0 +1,171 @@ +package com.tangem.features.tangempay.model.transformers + +import android.text.format.DateFormat +import com.google.common.truth.Truth.assertThat +import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig +import com.tangem.core.ui.extensions.TextReference +import com.tangem.domain.pay.model.CashbackDisplayMode +import com.tangem.domain.pay.model.CashbackSummary +import com.tangem.domain.pay.model.TangemPayCashback +import com.tangem.features.tangempay.cashback.impl.model.TangemPayCashbackDateFormatter +import com.tangem.features.tangempay.entity.CashbackBlockUM +import com.tangem.features.tangempay.entity.TangemPayDetailsBalanceBlockState +import com.tangem.features.tangempay.entity.TangemPayDetailsTopBarConfig +import com.tangem.features.tangempay.entity.TangemPayDetailsUM +import io.mockk.every +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import kotlinx.collections.immutable.persistentListOf +import org.joda.time.DateTime +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.MethodSource +import java.math.BigDecimal +import java.util.Locale + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +internal class CashbackBlockTransformerTest { + + private val defaultLocale = Locale.getDefault() + + private val onClick: () -> Unit = {} + private val onGotIt: () -> Unit = {} + private val dateFormatter = TangemPayCashbackDateFormatter() + + @BeforeEach + fun setup() { + // TangemPayCashbackDateFormatter -> DateTimeFormatters.dateMMMM -> android.text.format.DateFormat + Locale.setDefault(Locale.US) + mockkStatic(DateFormat::class) + every { DateFormat.getBestDateTimePattern(any(), any()) } answers { secondArg() } + } + + @AfterEach + fun tearDown() { + unmockkStatic(DateFormat::class) + Locale.setDefault(defaultLocale) + } + + @Test + fun `GIVEN enabled summary WHEN transform THEN cashback block is widget with content`() { + // Arrange + val transformer = createTransformer(summary = enabledSummary()) + + // Act + val block = transformer.transform(contentState()).cashbackBlockState + + // Assert + assertThat(block).isInstanceOf(CashbackBlockUM.Widget::class.java) + val widget = block as CashbackBlockUM.Widget + assertThat(widget.title).isInstanceOf(TextReference.Res::class.java) + assertThat(widget.title).isNotEqualTo(TextReference.EMPTY) + assertThat(widget.subtitle).isInstanceOf(TextReference.Res::class.java) + assertThat(widget.subtitle).isNotEqualTo(TextReference.EMPTY) + } + + @ParameterizedTest + @MethodSource("provideTestModels") + fun `GIVEN summary and dismissed flag WHEN transform THEN resolves expected cashback block`(model: BlockCase) { + // Arrange + val transformer = createTransformer(summary = model.summary, isDismissed = model.isDismissed) + + // Act + val block = transformer.transform(contentState()).cashbackBlockState + + // Assert + assertThat(block).isEqualTo(model.expectedBlock) + } + + private fun provideTestModels(): List = listOf( + BlockCase( + description = "deactivated & not dismissed -> banner", + summary = CashbackSummary.Deactivated, + isDismissed = false, + expectedBlock = CashbackBlockUM.DeactivatedBanner(onGotIt = onGotIt), + ), + BlockCase( + description = "deactivated & dismissed -> hidden", + summary = CashbackSummary.Deactivated, + isDismissed = true, + expectedBlock = null, + ), + BlockCase( + description = "disabled -> hidden", + summary = CashbackSummary.Disabled, + isDismissed = false, + expectedBlock = null, + ), + BlockCase( + description = "unknown -> hidden", + summary = CashbackSummary.Unknown, + isDismissed = false, + expectedBlock = null, + ), + ) + + private fun createTransformer( + summary: CashbackSummary, + isDismissed: Boolean = false, + ): CashbackBlockTransformer = CashbackBlockTransformer( + summary = summary, + isDeactivationDismissed = isDismissed, + dateFormatter = dateFormatter, + onClick = onClick, + onGotIt = onGotIt, + ) + + private fun enabledSummary( + confirmedAmount: BigDecimal = BigDecimal("32.15"), + currency: String = "USD", + year: Int = 2026, + month: Int = 6, + payoutStart: DateTime = DateTime.parse("2026-07-02"), + payoutEnd: DateTime = DateTime.parse("2026-07-05"), + ): CashbackSummary.Enabled = CashbackSummary.Enabled( + displayMode = CashbackDisplayMode.FULL, + cashback = TangemPayCashback( + confirmedAmount = confirmedAmount, + currency = currency, + period = TangemPayCashback.Period( + year = year, + month = month, + payoutStart = payoutStart, + payoutEnd = payoutEnd, + ), + ), + pendingAmount = BigDecimal("13.65"), + ) + + private fun contentState(): TangemPayDetailsUM = TangemPayDetailsUM( + topBarConfig = TangemPayDetailsTopBarConfig( + onBackClick = {}, + onOpenMenu = {}, + items = persistentListOf(), + itemsV2 = persistentListOf(), + ), + pullToRefreshConfig = PullToRefreshConfig(isRefreshing = false, onRefresh = {}), + balanceBlockState = TangemPayDetailsBalanceBlockState.Content( + actionButtons = persistentListOf(), + cardsBlockState = null, + fiatBalance = TextReference.EMPTY, + isBalanceFlickering = false, + isNegative = false, + ), + addToWalletBlockState = null, + isBalanceHidden = false, + errorNotificationConfig = null, + accountDeactivatedNotificationConfig = null, + ) + + internal class BlockCase( + val summary: CashbackSummary, + val isDismissed: Boolean, + val expectedBlock: CashbackBlockUM?, + private val description: String, + ) { + override fun toString(): String = description + } +} \ No newline at end of file