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