diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt index f7c7644e42..2e6bd18cf2 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt @@ -38,6 +38,7 @@ data class CustomerMeResponse( @Json(name = "id") val id: String?, @Json(name = "type") val type: String?, @Json(name = "name") val name: String?, + @Json(name = "program_name") val programName: String?, @Json(name = "description_items") val descriptionItems: List?, @Json(name = "images") val images: List? = null, @Json(name = "fees") val fees: List? = null, diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 3597532cea..2efaa954a6 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -98,15 +98,22 @@ Agregar dirección Agregue la dirección y seleccione la red Añadir contacto + Dirección copiada + Esta dirección ya está guardada como %1$s %d dirección %d direcciones + Seleccione una dirección + Borrar todo Contacto Nombre de contacto Copiar dirección + Contacto guardado No se pudo crear el contacto. Inténtelo de nuevo más tarde. + Eliminar contacto Este contacto se eliminará de todas sus libretas de direcciones + "%1$s\" tiene solo una dirección. Si la eliminas, también se eliminará el contacto. ¿Continuar?" No se pudo eliminar el contacto. Inténtelo de nuevo más tarde. Gestionar contactos y direcciones Descartar @@ -114,11 +121,22 @@ Introduzca la dirección Dirección no válida Seguir editando + No puede crear más de 20 direcciones. Elimine una para añadir una nueva. + No se puede agregar una nueva dirección + Se requiere el nombre del contacto. + El nombre del contacto contiene caracteres no válidos. + El nombre de contacto no debe superar los 50 caracteres + Ese nombre ya está en uso en esta billetera Nuevo contacto Aún no hay contactos Los contactos añadidos aparecerán aquí Eliminar dirección + Guardar dirección + Guardar contacto + Guardar en la billetera Este contacto se añadirá a la agenda de esta billetera. + No se encontraron resultados.\nPruebe con otro nombre. + Seleccionar todo Seleccionar red Libreta de direcciones Cambios no guardados diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 1f4da59adb..2d1d56553e 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1864,6 +1864,9 @@ Add funds Top-up options Add to Google Wallet + Cancel %1$s, move to %2$s + To pay monthly fee for plan and start use card + Top-up your account on %1$s Card Number PIN code The card is fully ready for payments. @@ -1898,6 +1901,8 @@ Card name Reveal Details + If it will remain below zero your %1$s cards will be closed on %2$s + Top up your account shortly Details Please try again later Unfreeze Card @@ -1933,9 +1938,14 @@ Come back to the app if you forget it. Card Error loading + Your %1$s plan is active till %2$s, then we will move you to %3$s. %4$s won\'t be charged. Change plan + %1$s monthly fee will be charged on %2$s Card related Plan related + Stay on %1$s + Your transition on %1$s will be canceled + Do you want to stay on %1$s? Current plan Set a limit from %s to %s Set limits @@ -2047,7 +2057,16 @@ Select Upgrade plan Compare plans + Your %1$s plan and %2$s cards will be active till %3$s + You can cancel this transition till %1$s + Your %1$s cards will be closed + %1$s monthly fee will be taken from your account + On %1$s we will move you to %2$s plan + No fee applied + You will get your virtual %1$s in minutes + You are switching to %1$s Confirm selection + We will issue %1$s for you Select plan We’re fixing a technical issue. Please try again later. Service temporarily unavailable diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/converter/TangemPayTariffPlanConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/converter/TangemPayTariffPlanConverter.kt index 53be058106..956f7b8f23 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/converter/TangemPayTariffPlanConverter.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/converter/TangemPayTariffPlanConverter.kt @@ -2,16 +2,29 @@ package com.tangem.data.pay.converter import com.tangem.datasource.api.pay.models.response.CustomerMeResponse import com.tangem.domain.models.account.TangemPayTariffPlan +import java.util.Locale internal object TangemPayTariffPlanConverter { fun convert(value: CustomerMeResponse.TariffPlan?): TangemPayTariffPlan? { val id = value?.id ?: return null val name = value.name ?: return null + val programName = value.programName ?: return null + + // We cannot base our logic on knowledge what exact tier type is it. + // Use it only as identifier to get data from other responses + val tierId = value.type ?: return null + + // Basic tier is a default tier. We can base some features on it. + // Other tiers are adjusted from admin panel. It is not guaranteed to have it in future + val isBasicTier = tierId.uppercase(Locale.US) == "BASIC" + return TangemPayTariffPlan( id = id, - type = TangemPayTariffPlan.Type.fromString(value.type), + tierId = tierId, + isBasicTier = isBasicTier, name = name, + programName = programName, descriptionItems = value.descriptionItems.orEmpty().mapNotNull(::convertDescriptionItem), images = value.images.orEmpty().mapNotNull(::convertImage), fees = value.fees.orEmpty().mapNotNull(::convertFee), diff --git a/data/visa/src/test/kotlin/com/tangem/data/pay/converter/TangemPayTariffPlanConverterTest.kt b/data/visa/src/test/kotlin/com/tangem/data/pay/converter/TangemPayTariffPlanConverterTest.kt index 3413c7369f..08819f32df 100644 --- a/data/visa/src/test/kotlin/com/tangem/data/pay/converter/TangemPayTariffPlanConverterTest.kt +++ b/data/visa/src/test/kotlin/com/tangem/data/pay/converter/TangemPayTariffPlanConverterTest.kt @@ -27,6 +27,30 @@ internal class TangemPayTariffPlanConverterTest { assertThat(TangemPayTariffPlanConverter.convert(value)).isNull() } + @Test + fun `GIVEN missing type WHEN convert THEN returns null`() { + val value = tariffPlan(type = null) + + assertThat(TangemPayTariffPlanConverter.convert(value)).isNull() + } + + @Test + fun `GIVEN missing programName WHEN convert THEN returns null`() { + val value = tariffPlan(programName = null) + + assertThat(TangemPayTariffPlanConverter.convert(value)).isNull() + } + + @Test + fun `GIVEN basic tier in mixed case WHEN convert THEN isBasicTier is true`() { + val value = tariffPlan(type = "Basic") + + val result = TangemPayTariffPlanConverter.convert(value) + + assertThat(result?.tierId).isEqualTo("Basic") + assertThat(result?.isBasicTier).isTrue() + } + @Test fun `GIVEN full valid plan WHEN convert THEN maps all fields`() { // GIVEN @@ -58,8 +82,10 @@ internal class TangemPayTariffPlanConverterTest { // THEN val expected = TangemPayTariffPlan( id = PLAN_ID, - type = TangemPayTariffPlan.Type.PLUS, + tierId = "PLUS", + isBasicTier = false, name = PLAN_NAME, + programName = PROGRAM_NAME, descriptionItems = listOf( TangemPayTariffPlan.DescriptionItem( section = TangemPayTariffPlan.Section.PLAN_RELATED, @@ -110,8 +136,10 @@ internal class TangemPayTariffPlanConverterTest { // THEN val expected = TangemPayTariffPlan( id = PLAN_ID, - type = TangemPayTariffPlan.Type.UNKNOWN, + tierId = "SOMETHING_NEW", + isBasicTier = false, name = PLAN_NAME, + programName = PROGRAM_NAME, descriptionItems = listOf( TangemPayTariffPlan.DescriptionItem( section = TangemPayTariffPlan.Section.UNKNOWN, @@ -168,6 +196,7 @@ internal class TangemPayTariffPlanConverterTest { id: String? = PLAN_ID, type: String? = "BASIC", name: String? = PLAN_NAME, + programName: String? = PROGRAM_NAME, descriptionItems: List? = null, images: List? = null, fees: List? = null, @@ -175,6 +204,7 @@ internal class TangemPayTariffPlanConverterTest { id = id, type = type, name = name, + programName = programName, descriptionItems = descriptionItems, images = images, fees = fees, @@ -183,5 +213,6 @@ internal class TangemPayTariffPlanConverterTest { private companion object { const val PLAN_ID = "plan-1" const val PLAN_NAME = "Plus" + const val PROGRAM_NAME = "program-1" } } \ No newline at end of file diff --git a/data/visa/src/test/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcherTest.kt b/data/visa/src/test/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcherTest.kt index 3494261c24..52be673077 100644 --- a/data/visa/src/test/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcherTest.kt +++ b/data/visa/src/test/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcherTest.kt @@ -120,8 +120,10 @@ internal class DefaultPaymentAccountStatusFetcherTest { private val basicPlan = TangemPayTariffPlan( id = "plan_basic", - type = TangemPayTariffPlan.Type.BASIC, + tierId = "BASIC", + isBasicTier = true, name = "Basic", + programName = "program_basic", descriptionItems = emptyList(), images = emptyList(), fees = emptyList(), diff --git a/data/visa/src/test/kotlin/com/tangem/data/pay/repository/DefaultTariffPlanTransitionsRepositoryTest.kt b/data/visa/src/test/kotlin/com/tangem/data/pay/repository/DefaultTariffPlanTransitionsRepositoryTest.kt index ee46d3ab16..9ef2a549b0 100644 --- a/data/visa/src/test/kotlin/com/tangem/data/pay/repository/DefaultTariffPlanTransitionsRepositoryTest.kt +++ b/data/visa/src/test/kotlin/com/tangem/data/pay/repository/DefaultTariffPlanTransitionsRepositoryTest.kt @@ -91,8 +91,10 @@ internal class DefaultTariffPlanTransitionsRepositoryTest { type = TangemPayTariffPlanTransition.Type.UPGRADE, plan = TangemPayTariffPlan( id = PLAN_ID, - type = TangemPayTariffPlan.Type.PLUS, + tierId = "PLUS", + isBasicTier = false, name = PLAN_NAME, + programName = PROGRAM_NAME, descriptionItems = emptyList(), images = emptyList(), fees = emptyList(), @@ -187,6 +189,7 @@ internal class DefaultTariffPlanTransitionsRepositoryTest { id = id, type = "PLUS", name = PLAN_NAME, + programName = PROGRAM_NAME, descriptionItems = null, images = null, fees = null, @@ -197,6 +200,7 @@ internal class DefaultTariffPlanTransitionsRepositoryTest { const val AUTH_HEADER = "auth-header" const val PLAN_ID = "plan-plus" const val PLAN_NAME = "Plus" + const val PROGRAM_NAME = "program-plus" const val PENDING_PLAN_ID = "plan-basic" } } \ No newline at end of file diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/account/TangemPayTariffPlan.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/account/TangemPayTariffPlan.kt index 26acf8e1e2..e0f801a134 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/account/TangemPayTariffPlan.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/account/TangemPayTariffPlan.kt @@ -8,8 +8,10 @@ import java.util.Locale @Serializable data class TangemPayTariffPlan( @SerialName("id") val id: String, - @SerialName("type") val type: Type, + @SerialName("tier_id") val tierId: String, + @SerialName("is_basic_tier") val isBasicTier: Boolean, @SerialName("name") val name: String, + @SerialName("program_name") val programName: String, @SerialName("description_items") val descriptionItems: List, @SerialName("images") val images: List = emptyList(), @SerialName("fees") val fees: List = emptyList(), @@ -100,31 +102,6 @@ data class TangemPayTariffPlan( } } - @Serializable - enum class Type { - @SerialName("BASIC") - BASIC, - - @SerialName("PLUS") - PLUS, - - @SerialName("PLUS_FF") - PLUS_FF, - - @SerialName("UNKNOWN") - UNKNOWN, - ; - - companion object { - fun fromString(value: String?) = when (value?.uppercase(Locale.US)) { - "BASIC" -> BASIC - "PLUS" -> PLUS - "PLUS_FF" -> PLUS_FF - else -> UNKNOWN - } - } - } - @Serializable enum class Section { @SerialName("CARD_RELATED") diff --git a/domain/visa/src/test/kotlin/com/tangem/domain/pay/usecase/GetTangemPayTariffPlanStateUseCaseTest.kt b/domain/visa/src/test/kotlin/com/tangem/domain/pay/usecase/GetTangemPayTariffPlanStateUseCaseTest.kt index e7b4bff205..ef00d92ea7 100644 --- a/domain/visa/src/test/kotlin/com/tangem/domain/pay/usecase/GetTangemPayTariffPlanStateUseCaseTest.kt +++ b/domain/visa/src/test/kotlin/com/tangem/domain/pay/usecase/GetTangemPayTariffPlanStateUseCaseTest.kt @@ -153,14 +153,18 @@ internal class GetTangemPayTariffPlanStateUseCaseTest { val CURRENT_PLAN = TangemPayTariffPlan( id = "plan-basic", - type = TangemPayTariffPlan.Type.BASIC, + tierId = "BASIC", + isBasicTier = true, name = "Basic", + programName = "program-basic", descriptionItems = emptyList(), ) val TARGET_PLAN = TangemPayTariffPlan( id = TARGET_PLAN_ID, - type = TangemPayTariffPlan.Type.PLUS, + tierId = "PLUS", + isBasicTier = false, name = "Plus", + programName = "program-plus", descriptionItems = emptyList(), ) val CUSTOMER_TARIFF = TangemPayCustomerTariffPlan( diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/CashbackTier.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/CashbackTier.kt index 6fd2f41086..c68de1ab18 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/CashbackTier.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/CashbackTier.kt @@ -1,10 +1,8 @@ package com.tangem.features.tangempay.cashback.impl.model -import com.tangem.domain.models.account.TangemPayTariffPlan - /** Cashback program tier, mapped once from the domain and shared by the rate tile and the details sheet. */ internal data class CashbackTier( - val planType: TangemPayTariffPlan.Type, + val tierId: String, val rate: Int?, val label: String, val scope: String, diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackInfoTilesConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackInfoTilesConverter.kt index 1351e92fa9..3869d62d7a 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackInfoTilesConverter.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackInfoTilesConverter.kt @@ -12,17 +12,13 @@ internal class TangemPayCashbackInfoTilesConverter( ) { // TODO([REDACTED_TASK_KEY]): move hardcoded strings to string resources - fun convert( - tiers: List, - currentPlanType: TangemPayTariffPlan.Type, - currentPlanName: String?, - ): TangemPayCashbackInfoTilesUM { - val rate = tiers.selectTier(currentPlanType)?.rate + fun convert(tiers: List, currentPlan: TangemPayTariffPlan?): TangemPayCashbackInfoTilesUM { + val rate = tiers.selectTier(currentPlan?.tierId)?.rate return TangemPayCashbackInfoTilesUM( rate = TangemPayCashbackInfoTilesUM.Tile( iconRes = R.drawable.ic_percent_24, title = stringReference(if (rate != null) "Cashback $rate%" else "Cashback"), - subtitle = currentPlanName?.let { stringReference("With your $it plan") } ?: TextReference.EMPTY, + subtitle = currentPlan?.name?.let { stringReference("With your $it plan") } ?: TextReference.EMPTY, onClick = onRateClick, ), accruals = TangemPayCashbackInfoTilesUM.Tile( @@ -34,9 +30,7 @@ internal class TangemPayCashbackInfoTilesConverter( ) } - private fun List.selectTier(planType: TangemPayTariffPlan.Type): CashbackTier? { - return firstOrNull { - planType != TangemPayTariffPlan.Type.UNKNOWN && it.planType == planType - } ?: firstOrNull() + private fun List.selectTier(tierId: String?): CashbackTier? { + return firstOrNull { it.tierId == tierId } ?: firstOrNull() } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackModel.kt index d84f59fc81..96a2aa454a 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackModel.kt @@ -86,8 +86,7 @@ internal class TangemPayCashbackModel @Inject constructor( infoTiles = promotions?.let { infoTilesConverter.convert( tiers = tiers, - currentPlanType = plan?.type ?: TangemPayTariffPlan.Type.UNKNOWN, - currentPlanName = plan?.name, + currentPlan = plan, ) }, ) diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackTiersConverter.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackTiersConverter.kt index d8e5bd52c5..7e08e3d3a7 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackTiersConverter.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackTiersConverter.kt @@ -4,7 +4,6 @@ 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.core.ui.format.bigdecimal.optionalDecimals -import com.tangem.domain.models.account.TangemPayTariffPlan import com.tangem.domain.pay.model.CashbackPromotions import com.tangem.utils.converter.Converter import java.math.BigDecimal @@ -14,7 +13,7 @@ internal class TangemPayCashbackTiersConverter : Converter { return value.cardTiers.map { tier -> CashbackTier( - planType = TangemPayTariffPlan.Type.fromString(tier.tier), + tierId = tier.tier, rate = CashbackRates.forTier(tier.tier), label = tier.label, scope = tier.scope, diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNotificationFactory.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNotificationFactory.kt index 41a53fa256..505b9d6252 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNotificationFactory.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayDetailsNotificationFactory.kt @@ -2,7 +2,7 @@ package com.tangem.features.tangempay.entity import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.models.account.PaymentAccountStatusValue import com.tangem.domain.models.account.TangemPayCustomerTariffPlan import com.tangem.domain.models.account.TangemPayTariffPlanState @@ -37,7 +37,6 @@ internal class TangemPayDetailsNotificationFactory( }, ) - // TODO v_rodionov: #[REDACTED_TASK_KEY] fix hardcoded strings fun createAwaitingDepositConfig(tariffPlan: TangemPayTariffPlanState?): NotificationConfig? { if (!isTiersPlusPlanEnabled) return null if (tariffPlan == null) return null @@ -52,13 +51,15 @@ internal class TangemPayDetailsNotificationFactory( val feeText = orderStep.toPlan.formatRecurringFeeOrNull() ?: return null - val title = "Top-up your account on $feeText" return NotificationConfig( - title = stringReference(title), - subtitle = stringReference("To pay monthly fee for plan and start use card"), + title = resourceReference(R.string.tangempay_card_details_awaiting_deposit_title, wrappedList(feeText)), + subtitle = resourceReference(R.string.tangempay_card_details_awaiting_deposit_subtitle), iconResId = R.drawable.ic_alert_circle_24, buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( - text = stringReference("Cancel ${orderStep.toPlan.name}, move to ${orderStep.fromPlan.name}"), + text = resourceReference( + R.string.tangempay_card_details_awaiting_deposit_cancel_button, + wrappedList(orderStep.toPlan.name, orderStep.fromPlan.name), + ), onClick = { intents.onCancelPlusTransition(order.orderId) }, ), ) @@ -81,16 +82,18 @@ internal class TangemPayDetailsNotificationFactory( iconResId = if (isRedesignEnabled) R.drawable.ic_alert_circle_24 else R.drawable.img_attention_20, ) - // TODO v_rodionov: #[REDACTED_TASK_KEY] fix hardcoded strings private fun createTariffSystemDownGradePendingConfig(tariffPlan: TangemPayTariffPlanState): NotificationConfig? { val date = tariffPlan.tariff.formatNextBillingDateOrNull() ?: return null val planName = tariffPlan.tariff.plan.name return NotificationConfig( - title = stringReference("Top up your account shortly"), - subtitle = stringReference("If it will remain below zero your $planName cards will be closed on $date"), + title = resourceReference(R.string.tangempay_card_details_system_downgrade_title), + subtitle = resourceReference( + R.string.tangempay_card_details_system_downgrade_subtitle, + wrappedList(planName, date), + ), iconResId = R.drawable.ic_alert_circle_24, buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( - text = stringReference("Add funds"), + text = resourceReference(R.string.tangempay_card_details_add_funds), onClick = intents::onClickAddFunds, ), ) 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 81e3c07a8b..9c45b8f7d6 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 @@ -12,7 +12,6 @@ import com.tangem.core.ui.res.generated.icons.Icons import com.tangem.core.ui.res.generated.icons.ic_document_20 import com.tangem.domain.models.account.PaymentAccountStatusValue import com.tangem.domain.models.account.TangemPayCustomerTariffPlan -import com.tangem.domain.models.account.TangemPayTariffPlan import com.tangem.domain.models.account.TangemPayTariffPlanState import com.tangem.domain.models.pay.TangemPayCard import com.tangem.domain.models.pay.TangemPayCardFrozenState @@ -286,10 +285,7 @@ internal class TangemPayDetailsStateFactory( ), ) } - if (isTiersPlusPlanEnabled && - tariffPlan != null && - tariffPlan.tariff.plan.type != TangemPayTariffPlan.Type.BASIC - ) { + if (isTiersPlusPlanEnabled && tariffPlan != null && !tariffPlan.tariff.plan.isBasicTier) { add( TangemPayDropDownItemUM( title = resourceReference(R.string.tangempay_visa_benefits), diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/current/TangemPayCurrentPlanModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/current/TangemPayCurrentPlanModel.kt index d8fe2b240a..40983e872c 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/current/TangemPayCurrentPlanModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/current/TangemPayCurrentPlanModel.kt @@ -7,6 +7,7 @@ import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.domain.models.account.TangemPayCustomerTariffPlan @@ -50,7 +51,6 @@ internal class TangemPayCurrentPlanModel @Inject constructor( onChangePlanClick = { router.push(TangemPayAccountDetailsInnerRoute.SelectPlan(params.tariffPlan)) }, ) - // TODO v_rodionov: #[REDACTED_TASK_KEY] fix hardcoded strings private fun createNotification(customerPlan: TangemPayCustomerTariffPlan): TangemPayCurrentPlanUM.Notification? { val date = customerPlan.formatNextBillingDateOrNull(formatter = DateTimeFormatters.dateMMMd) ?: return null val feeText = customerPlan.plan.formatRecurringFeeOrNull() ?: return null @@ -58,19 +58,25 @@ internal class TangemPayCurrentPlanModel @Inject constructor( TangemPayCustomerTariffPlan.Status.DOWNGRADE_PENDING -> { val targetPlan = customerPlan.pendingPlan ?: return null TangemPayCurrentPlanUM.Notification( - text = stringReference( - "Your ${customerPlan.plan.name} plan is active till $date, then we will move you to " + - "${targetPlan.name}. $feeText won't be charged.", + text = resourceReference( + R.string.tangempay_current_plan_active_till_notification, + wrappedList(customerPlan.plan.name, date, targetPlan.name, feeText), ), button = TangemPayCurrentPlanUM.Notification.Button( - text = stringReference("Stay on ${customerPlan.plan.name}"), + text = resourceReference( + R.string.tangempay_current_plan_stay_button, + wrappedList(customerPlan.plan.name), + ), isProcessing = isProcessing, onClick = ::onStayOnPlanClick, ), ) } TangemPayCustomerTariffPlan.Status.ACTIVE -> TangemPayCurrentPlanUM.Notification( - text = stringReference("$feeText monthly fee will be charged on $date"), + text = resourceReference( + R.string.tangempay_current_plan_fee_charged_notification, + wrappedList(feeText, date), + ), ) else -> null } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/select/TangemPaySelectPlanModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/select/TangemPaySelectPlanModel.kt index 14b33183a3..0879f945e6 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/select/TangemPaySelectPlanModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/tiers/select/TangemPaySelectPlanModel.kt @@ -9,6 +9,7 @@ import com.tangem.core.decompose.navigation.Router import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.domain.models.account.TangemPayTariffPlan import com.tangem.domain.models.account.TangemPayTariffPlanTransition @@ -189,28 +190,34 @@ internal class TangemPaySelectPlanModel @Inject constructor( ) } - // TODO v_rodionov: #[REDACTED_TASK_KEY] fix hardcoded strings private fun buildConfirmContent(): TangemPaySelectPlanUM.Content { val transition = allowedTransitions.getOrNull(selectedIndex) ?: return buildSelectContent() - val targetProgramme = transition.plan.name + val targetPlanName = transition.plan.name + val programName = transition.plan.programName return TangemPaySelectPlanUM.Content.Confirm( title = when (transition.type) { - TangemPayTariffPlanTransition.Type.UPGRADE -> stringReference( - "We will issue Visa $targetProgramme for you", + TangemPayTariffPlanTransition.Type.UPGRADE -> resourceReference( + R.string.tangempay_select_plan_confirm_upgrade_title, + wrappedList(programName), ) TangemPayTariffPlanTransition.Type.DOWNGRADE -> { val nextBillingDate = nextBillingDate() if (nextBillingDate != null) { - val programName = "UNKNOWN" // TODO v_rodionov: #[REDACTED_TASK_KEY] fix hardcoded strings - val planName = params.tariffPlan.plan.name - stringReference( - "Your $planName plan and $programName cards will be active till $nextBillingDate", + resourceReference( + R.string.tangempay_select_plan_confirm_downgrade_title, + wrappedList(params.tariffPlan.plan.name, programName, nextBillingDate), ) } else { - stringReference("You are switching to $targetProgramme") + resourceReference( + R.string.tangempay_select_plan_confirm_switch_title, + wrappedList(targetPlanName), + ) } } - else -> stringReference("You are switching to $targetProgramme") + else -> resourceReference( + R.string.tangempay_select_plan_confirm_switch_title, + wrappedList(targetPlanName), + ) }, points = buildConfirmPoints(transition), confirmButtonText = resourceReference( @@ -226,18 +233,27 @@ internal class TangemPaySelectPlanModel @Inject constructor( ) } - // TODO v_rodionov: #[REDACTED_TASK_KEY] fix hardcoded strings private fun buildConfirmPoints( transition: TangemPayTariffPlanTransition, ): ImmutableList { - val programName = "UNKNOWN" // TODO v_rodionov: #[REDACTED_TASK_KEY] fix hardcoded strings + val programName = transition.plan.programName return when (transition.type) { TangemPayTariffPlanTransition.Type.UPGRADE -> { val feeText = transition.plan.formatRecurringFeeOrNull() buildList { - add("You will get your virtual Visa $programName in minutes") + add( + resourceReference( + R.string.tangempay_select_plan_confirm_point_virtual_card, + wrappedList(programName), + ), + ) if (feeText != null) { - add("$feeText monthly fee will be taken from your account") + add( + resourceReference( + R.string.tangempay_select_plan_confirm_point_monthly_fee, + wrappedList(feeText), + ), + ) } } } @@ -245,18 +261,33 @@ internal class TangemPaySelectPlanModel @Inject constructor( val date = nextBillingDate() buildList { if (date != null) { - add("On $date we will move you to ${transition.plan.name} plan") + add( + resourceReference( + R.string.tangempay_select_plan_confirm_point_move_on_date, + wrappedList(date, transition.plan.name), + ), + ) } - add("Your Visa $programName cards will be closed") + add( + resourceReference( + R.string.tangempay_select_plan_confirm_point_cards_closed, + wrappedList(programName), + ), + ) if (date != null) { - add("You can cancel this transition till $date") + add( + resourceReference( + R.string.tangempay_select_plan_confirm_point_cancel_till, + wrappedList(date), + ), + ) } - add("No fee applied") + add(resourceReference(R.string.tangempay_select_plan_confirm_point_no_fee)) } } - else -> listOf("No fee applied") + else -> listOf(resourceReference(R.string.tangempay_select_plan_confirm_point_no_fee)) } - .map { TangemPaySelectPlanUM.PointUM(title = stringReference(it), body = null) } + .map { TangemPaySelectPlanUM.PointUM(title = it, body = null) } .toImmutableList() } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayMessagesFactory.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayMessagesFactory.kt index 40f023a510..20a0d16da4 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayMessagesFactory.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/utils/TangemPayMessagesFactory.kt @@ -4,7 +4,7 @@ import com.tangem.core.ui.R import com.tangem.core.ui.components.bottomsheets.message.* import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.message.BottomSheetMessage import com.tangem.core.ui.message.DialogMessage import com.tangem.core.ui.message.bottomSheetMessage @@ -149,7 +149,6 @@ internal object TangemPayMessagesFactory { } } - // TODO v_rodionov: #[REDACTED_TASK_KEY] fix hardcoded strings fun createStayOnPlanMessage( planName: String, targetPlanName: String, @@ -161,15 +160,18 @@ internal object TangemPayMessagesFactory { type = MessageBottomSheetUM.Icon.Type.Informative backgroundType = MessageBottomSheetUM.Icon.BackgroundType.Informative } - title = stringReference("Do you want to stay on $planName?") - body = stringReference("Your transition on $targetPlanName will be canceled") + title = resourceReference(R.string.tangempay_current_plan_stay_sheet_title, wrappedList(planName)) + body = resourceReference( + R.string.tangempay_current_plan_stay_sheet_body, + wrappedList(targetPlanName), + ) } secondaryButton { text = resourceReference(R.string.common_cancel) onClick { closeBs() } } primaryButton { - text = stringReference("Stay on $planName") + text = resourceReference(R.string.tangempay_current_plan_stay_button, wrappedList(planName)) onClick { onStayClick() closeBs() diff --git a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackDetailsConverterTest.kt b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackDetailsConverterTest.kt index 5ae2df9fd0..bb11072dc1 100644 --- a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackDetailsConverterTest.kt +++ b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackDetailsConverterTest.kt @@ -2,7 +2,6 @@ package com.tangem.features.tangempay.cashback.impl.model import com.google.common.truth.Truth.assertThat import com.tangem.core.ui.extensions.stringReference -import com.tangem.domain.models.account.TangemPayTariffPlan import org.junit.jupiter.api.Test internal class TangemPayCashbackDetailsConverterTest { @@ -81,7 +80,7 @@ internal class TangemPayCashbackDetailsConverterTest { min: String? = null, cap: String? = null, ) = CashbackTier( - planType = TangemPayTariffPlan.Type.UNKNOWN, + tierId = "basic", rate = rate, label = label, scope = scope, diff --git a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackInfoTilesConverterTest.kt b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackInfoTilesConverterTest.kt index 0a232a6454..0a1600419d 100644 --- a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackInfoTilesConverterTest.kt +++ b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackInfoTilesConverterTest.kt @@ -21,7 +21,7 @@ internal class TangemPayCashbackInfoTilesConverterTest { @MethodSource("rateSelectionModels") fun `GIVEN plan WHEN convert THEN rate tile shows the tier rate and the plan subtitle`(model: RateSelectionModel) { // Act - val result = converter.convert(twoTiers(), model.plan, model.planName) + val result = converter.convert(twoTiers(), model.plan) // Assert assertThat(result.rate.title).isEqualTo(stringReference(model.expectedTitle)) @@ -29,9 +29,9 @@ internal class TangemPayCashbackInfoTilesConverterTest { } @Test - fun `GIVEN empty tiers and unknown plan WHEN convert THEN rate tile has no percent and empty subtitle`() { + fun `GIVEN empty tiers and no plan WHEN convert THEN rate tile has no percent and empty subtitle`() { // Act - val result = converter.convert(emptyList(), TangemPayTariffPlan.Type.UNKNOWN, currentPlanName = null) + val result = converter.convert(emptyList(), currentPlan = null) // Assert assertThat(result.rate.title).isEqualTo(stringReference("Cashback")) @@ -41,7 +41,7 @@ internal class TangemPayCashbackInfoTilesConverterTest { @Test fun `GIVEN any tiers WHEN convert THEN accruals tile is static`() { // Act - val result = converter.convert(twoTiers(), TangemPayTariffPlan.Type.BASIC, currentPlanName = "Basic") + val result = converter.convert(twoTiers(), plan(tierId = "basic", name = "Basic")) // Assert assertThat(result.accruals.title).isEqualTo(stringReference("Accruals")) @@ -50,43 +50,52 @@ internal class TangemPayCashbackInfoTilesConverterTest { private fun rateSelectionModels() = listOf( RateSelectionModel( - plan = TangemPayTariffPlan.Type.PLUS, - planName = "Plus", + plan = plan(tierId = "plus", name = "Plus"), expectedTitle = "Cashback 2%", expectedSubtitle = stringReference("With your Plus plan"), ), RateSelectionModel( - plan = TangemPayTariffPlan.Type.BASIC, - planName = "Basic", + plan = plan(tierId = "basic", name = "Basic"), expectedTitle = "Cashback 1%", expectedSubtitle = stringReference("With your Basic plan"), ), RateSelectionModel( - plan = TangemPayTariffPlan.Type.UNKNOWN, - planName = null, + plan = plan(tierId = "gold", name = "Gold"), + expectedTitle = "Cashback 1%", + expectedSubtitle = stringReference("With your Gold plan"), + ), + RateSelectionModel( + plan = null, expectedTitle = "Cashback 1%", expectedSubtitle = TextReference.EMPTY, ), ) private fun twoTiers() = listOf( - tier(TangemPayTariffPlan.Type.BASIC, rate = 1), - tier(TangemPayTariffPlan.Type.PLUS, rate = 2), + tier(tierId = "basic", rate = 1), + tier(tierId = "plus", rate = 2), ) - private fun tier(planType: TangemPayTariffPlan.Type = TangemPayTariffPlan.Type.UNKNOWN, rate: Int? = null) = - CashbackTier( - planType = planType, - rate = rate, - label = "label", - scope = "scope", - minPurchase = null, - monthlyCap = null, - ) + private fun tier(tierId: String, rate: Int? = null) = CashbackTier( + tierId = tierId, + rate = rate, + label = "label", + scope = "scope", + minPurchase = null, + monthlyCap = null, + ) + + private fun plan(tierId: String, name: String) = TangemPayTariffPlan( + id = "plan-$tierId", + tierId = tierId, + isBasicTier = tierId == "basic", + name = name, + programName = "program", + descriptionItems = emptyList(), + ) data class RateSelectionModel( - val plan: TangemPayTariffPlan.Type, - val planName: String?, + val plan: TangemPayTariffPlan?, val expectedTitle: String, val expectedSubtitle: TextReference, ) diff --git a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackModelTest.kt b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackModelTest.kt index febeef9bc1..0539d5b783 100644 --- a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackModelTest.kt +++ b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackModelTest.kt @@ -45,7 +45,7 @@ internal class TangemPayCashbackModelTest { coEvery { cashbackRepository.getCashbackPromotions(any()) } returns promotions().right() coEvery { cashbackRepository.getCashbackAccrualDocs(any()) } returns docs().right() coEvery { onboardingRepository.getCustomerInfo(any()) } returns - customerInfo(TangemPayTariffPlan.Type.BASIC).right() + customerInfo(tierId = "basic", planName = "Basic").right() } @AfterEach @@ -69,7 +69,7 @@ internal class TangemPayCashbackModelTest { fun `GIVEN PLUS plan WHEN model created THEN rate tile shows the Plus tier rate`() { // Arrange coEvery { onboardingRepository.getCustomerInfo(any()) } returns - customerInfo(TangemPayTariffPlan.Type.PLUS).right() + customerInfo(tierId = "plus", planName = "Plus").right() // Act val model = createModel() @@ -162,12 +162,16 @@ internal class TangemPayCashbackModelTest { CashbackDocument(id = "terms", title = "Full terms of cashback program", url = "https://x/terms.pdf"), ) - private fun customerInfo(planType: TangemPayTariffPlan.Type): CustomerInfo { - val tariffPlanMock = mockk { - every { type } returns planType - every { name } returns planType.name - } - val customerTariffPlanMock = mockk { every { plan } returns tariffPlanMock } + private fun customerInfo(tierId: String, planName: String): CustomerInfo { + val currentPlan = TangemPayTariffPlan( + id = "plan-$tierId", + tierId = tierId, + isBasicTier = tierId == "basic", + name = planName, + programName = "program", + descriptionItems = emptyList(), + ) + val customerTariffPlanMock = mockk { every { plan } returns currentPlan } return mockk { every { tariffPlan } returns customerTariffPlanMock } } } \ No newline at end of file diff --git a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackTiersConverterTest.kt b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackTiersConverterTest.kt index 76e614d636..1f75fbc00d 100644 --- a/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackTiersConverterTest.kt +++ b/features/tangempay/details/impl/src/test/kotlin/com/tangem/features/tangempay/cashback/impl/model/TangemPayCashbackTiersConverterTest.kt @@ -1,7 +1,6 @@ package com.tangem.features.tangempay.cashback.impl.model import com.google.common.truth.Truth.assertThat -import com.tangem.domain.models.account.TangemPayTariffPlan import com.tangem.domain.pay.model.CashbackPromotions import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach @@ -38,7 +37,7 @@ internal class TangemPayCashbackTiersConverterTest { // Assert assertThat(result).containsExactly( CashbackTier( - planType = TangemPayTariffPlan.Type.BASIC, + tierId = "basic", rate = 1, label = "Basic cards", scope = "All purchases", @@ -46,7 +45,7 @@ internal class TangemPayCashbackTiersConverterTest { monthlyCap = "$100", ), CashbackTier( - planType = TangemPayTariffPlan.Type.PLUS, + tierId = "plus", rate = 2, label = "Plus cards", scope = "Everywhere", @@ -57,7 +56,7 @@ internal class TangemPayCashbackTiersConverterTest { } @Test - fun `GIVEN unknown tier WHEN convert THEN rate null and plan type UNKNOWN`() { + fun `GIVEN unknown tier WHEN convert THEN rate is null and tier id kept`() { // Arrange val promotions = promotions(tier(id = "gold", label = "Gold", scope = "All", min = null, cap = null)) @@ -67,7 +66,7 @@ internal class TangemPayCashbackTiersConverterTest { // Assert assertThat(result).containsExactly( CashbackTier( - planType = TangemPayTariffPlan.Type.UNKNOWN, + tierId = "gold", rate = null, label = "Gold", scope = "All",