Updated on 2026-08-14
This commit is contained in:
parent
15a518c576
commit
78cb0fc750
18 changed files with 747 additions and 15 deletions
|
|
@ -202,4 +202,10 @@ interface TangemPayApi {
|
|||
@Header("Authorization") authHeader: String,
|
||||
@Header("Accept-Language") language: String,
|
||||
): ApiResponse<CashbackAccrualDocsResponse>
|
||||
|
||||
@GET("v1/customer/cashback/history")
|
||||
suspend fun getCashbackHistory(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Query("months") months: Int,
|
||||
): ApiResponse<CashbackHistoryResponse>
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Response from `GET /v1/customer/cashback/history`.
|
||||
*
|
||||
* Confirmed cashback grouped by calendar month, ordered oldest to newest. Drives the monthly
|
||||
* earnings histogram on the Cashback screen.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CashbackHistoryResponse(
|
||||
@Json(name = "currency") val currency: String?,
|
||||
@Json(name = "items") val items: List<Item>?,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Item(
|
||||
@Json(name = "year") val year: Int,
|
||||
@Json(name = "month") val month: Int,
|
||||
@Json(name = "confirmed_amount") val confirmedAmount: BigDecimal?,
|
||||
)
|
||||
}
|
||||
|
|
@ -115,6 +115,13 @@ object DateTimeFormatters {
|
|||
getBestFormatterBySkeleton("MMMM")
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: "Jun"
|
||||
*/
|
||||
val dateMMM: DateTimeFormatter by lazy {
|
||||
getBestFormatterBySkeleton("MMM")
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: "June 1"
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ package com.tangem.data.pay.repository
|
|||
import arrow.core.Either
|
||||
import com.tangem.data.pay.store.TangemPayStorage
|
||||
import com.tangem.data.pay.util.CashbackAccrualDocsConverter
|
||||
import com.tangem.data.pay.util.CashbackHistoryConverter
|
||||
import com.tangem.data.pay.util.CashbackPromotionsConverter
|
||||
import com.tangem.data.pay.util.CashbackSummaryConverter
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.CashbackDocument
|
||||
import com.tangem.domain.pay.model.CashbackHistory
|
||||
import com.tangem.domain.pay.model.CashbackPromotions
|
||||
import com.tangem.domain.pay.model.CashbackSummary
|
||||
import com.tangem.domain.pay.repository.CashbackRepository
|
||||
|
|
@ -47,6 +49,15 @@ internal class DefaultCashbackRepository @Inject constructor(
|
|||
}.map(CashbackAccrualDocsConverter::convert)
|
||||
}
|
||||
|
||||
override suspend fun getCashbackHistory(
|
||||
userWalletId: UserWalletId,
|
||||
months: Int,
|
||||
): Either<VisaApiError, CashbackHistory> {
|
||||
return requestHelper.performRequest(userWalletId) { authHeader ->
|
||||
tangemPayApi.getCashbackHistory(authHeader = authHeader, months = months)
|
||||
}.map(CashbackHistoryConverter::convert)
|
||||
}
|
||||
|
||||
override suspend fun isDeactivationBannerDismissed(userWalletId: UserWalletId): Boolean {
|
||||
val customerWalletAddress = requestHelper.getCustomerWalletAddress(userWalletId)
|
||||
return storage.getCashbackDeactivationDismissed(customerWalletAddress)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.data.pay.util
|
||||
|
||||
import com.tangem.datasource.api.pay.models.response.CashbackHistoryResponse
|
||||
import com.tangem.domain.pay.model.CashbackHistory
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
/** Maps [CashbackHistoryResponse] (BFF) to the domain [CashbackHistory]. */
|
||||
internal object CashbackHistoryConverter : Converter<CashbackHistoryResponse, CashbackHistory> {
|
||||
|
||||
override fun convert(value: CashbackHistoryResponse): CashbackHistory {
|
||||
return CashbackHistory(
|
||||
currency = value.currency.orEmpty(),
|
||||
months = value.items.orEmpty().map { item ->
|
||||
CashbackHistory.MonthlyCashback(
|
||||
year = item.year,
|
||||
month = item.month,
|
||||
confirmedAmount = item.confirmedAmount ?: BigDecimal.ZERO,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
|||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.CashbackDisplayMode
|
||||
import com.tangem.domain.pay.model.CashbackDocument
|
||||
import com.tangem.domain.pay.model.CashbackHistory
|
||||
import com.tangem.domain.pay.model.CashbackPromotions
|
||||
import com.tangem.domain.pay.model.CashbackSummary
|
||||
import com.tangem.domain.pay.model.TangemPayCashback
|
||||
|
|
@ -49,6 +50,14 @@ internal class MockAwareCashbackRepository @Inject constructor(
|
|||
return real.getCashbackAccrualDocs(userWalletId)
|
||||
}
|
||||
|
||||
override suspend fun getCashbackHistory(
|
||||
userWalletId: UserWalletId,
|
||||
months: Int,
|
||||
): Either<VisaApiError, CashbackHistory> {
|
||||
if (isMockMode) return MOCK_HISTORY.copy(months = MOCK_HISTORY.months.takeLast(months)).right()
|
||||
return real.getCashbackHistory(userWalletId, months)
|
||||
}
|
||||
|
||||
override suspend fun isDeactivationBannerDismissed(userWalletId: UserWalletId): Boolean =
|
||||
real.isDeactivationBannerDismissed(userWalletId)
|
||||
|
||||
|
|
@ -104,5 +113,16 @@ internal class MockAwareCashbackRepository @Inject constructor(
|
|||
url = "https://tangem.com/docs/en/tangem-pay-cashback-terms.pdf",
|
||||
),
|
||||
)
|
||||
|
||||
val MOCK_HISTORY = CashbackHistory(
|
||||
currency = "USD",
|
||||
months = listOf(
|
||||
CashbackHistory.MonthlyCashback(year = 2026, month = 2, confirmedAmount = BigDecimal("12.02")),
|
||||
CashbackHistory.MonthlyCashback(year = 2026, month = 3, confirmedAmount = BigDecimal("44.22")),
|
||||
CashbackHistory.MonthlyCashback(year = 2026, month = 4, confirmedAmount = BigDecimal("38.52")),
|
||||
CashbackHistory.MonthlyCashback(year = 2026, month = 5, confirmedAmount = BigDecimal("26.10")),
|
||||
CashbackHistory.MonthlyCashback(year = 2026, month = 6, confirmedAmount = BigDecimal("22.54")),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
package com.tangem.data.pay.util
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.datasource.api.pay.models.response.CashbackHistoryResponse
|
||||
import com.tangem.domain.pay.model.CashbackHistory
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import java.math.BigDecimal
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class CashbackHistoryConverterTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun convert(model: ConvertModel) {
|
||||
// Act
|
||||
val actual = CashbackHistoryConverter.convert(model.response)
|
||||
|
||||
// Assert
|
||||
assertThat(actual).isEqualTo(model.expected)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
ConvertModel(
|
||||
name = "full response -> mapped history ordered oldest to newest",
|
||||
response = createResponse(
|
||||
currency = "USD",
|
||||
items = listOf(
|
||||
createItem(year = 2026, month = 2, confirmedAmount = BigDecimal("5.00")),
|
||||
createItem(year = 2026, month = 6, confirmedAmount = BigDecimal("22.54")),
|
||||
),
|
||||
),
|
||||
expected = CashbackHistory(
|
||||
currency = "USD",
|
||||
months = listOf(
|
||||
CashbackHistory.MonthlyCashback(year = 2026, month = 2, confirmedAmount = BigDecimal("5.00")),
|
||||
CashbackHistory.MonthlyCashback(year = 2026, month = 6, confirmedAmount = BigDecimal("22.54")),
|
||||
),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
name = "negative amount preserved for current-month refund",
|
||||
response = createResponse(
|
||||
items = listOf(createItem(year = 2026, month = 6, confirmedAmount = BigDecimal("-2.15"))),
|
||||
),
|
||||
expected = CashbackHistory(
|
||||
currency = "USD",
|
||||
months = listOf(
|
||||
CashbackHistory.MonthlyCashback(year = 2026, month = 6, confirmedAmount = BigDecimal("-2.15")),
|
||||
),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
name = "null confirmed_amount -> ZERO",
|
||||
response = createResponse(
|
||||
items = listOf(createItem(year = 2026, month = 6, confirmedAmount = null)),
|
||||
),
|
||||
expected = CashbackHistory(
|
||||
currency = "USD",
|
||||
months = listOf(
|
||||
CashbackHistory.MonthlyCashback(year = 2026, month = 6, confirmedAmount = BigDecimal.ZERO),
|
||||
),
|
||||
),
|
||||
),
|
||||
ConvertModel(
|
||||
name = "null currency -> empty string",
|
||||
response = createResponse(currency = null, items = emptyList()),
|
||||
expected = CashbackHistory(currency = "", months = emptyList()),
|
||||
),
|
||||
ConvertModel(
|
||||
name = "null items -> empty months",
|
||||
response = createResponse(items = null),
|
||||
expected = CashbackHistory(currency = "USD", months = emptyList()),
|
||||
),
|
||||
)
|
||||
|
||||
internal data class ConvertModel(
|
||||
val name: String,
|
||||
val response: CashbackHistoryResponse,
|
||||
val expected: CashbackHistory,
|
||||
) {
|
||||
override fun toString(): String = name
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
fun createResponse(
|
||||
currency: String? = "USD",
|
||||
items: List<CashbackHistoryResponse.Item>? = listOf(
|
||||
createItem(year = 2026, month = 6, confirmedAmount = BigDecimal("22.54")),
|
||||
),
|
||||
) = CashbackHistoryResponse(
|
||||
currency = currency,
|
||||
items = items,
|
||||
)
|
||||
|
||||
fun createItem(year: Int, month: Int, confirmedAmount: BigDecimal?) = CashbackHistoryResponse.Item(
|
||||
year = year,
|
||||
month = month,
|
||||
confirmedAmount = confirmedAmount,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.domain.pay.model
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Customer cashback history from `GET /v1/customer/cashback/history`.
|
||||
*
|
||||
* Confirmed cashback grouped by calendar month. Drives the monthly earnings histogram on the
|
||||
* Cashback screen.
|
||||
*/
|
||||
data class CashbackHistory(
|
||||
val currency: String,
|
||||
/** Confirmed cashback per calendar month, ordered oldest to newest. */
|
||||
val months: List<MonthlyCashback>,
|
||||
) {
|
||||
|
||||
data class MonthlyCashback(
|
||||
val year: Int,
|
||||
/** 1-based calendar month (6 = June). */
|
||||
val month: Int,
|
||||
/** Total confirmed cashback for this month; negative for refunds. */
|
||||
val confirmedAmount: BigDecimal,
|
||||
)
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.domain.pay.repository
|
|||
import arrow.core.Either
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.CashbackDocument
|
||||
import com.tangem.domain.pay.model.CashbackHistory
|
||||
import com.tangem.domain.pay.model.CashbackPromotions
|
||||
import com.tangem.domain.pay.model.CashbackSummary
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
|
|
@ -19,6 +20,13 @@ interface CashbackRepository {
|
|||
|
||||
suspend fun getCashbackAccrualDocs(userWalletId: UserWalletId): Either<VisaApiError, List<CashbackDocument>>
|
||||
|
||||
/**
|
||||
* Loads the confirmed cashback history for the customer of [userWalletId], grouped by month.
|
||||
*
|
||||
* @param months number of calendar months to return, counting back from and including the current month.
|
||||
*/
|
||||
suspend fun getCashbackHistory(userWalletId: UserWalletId, months: Int): Either<VisaApiError, CashbackHistory>
|
||||
|
||||
/** Whether the "Cashback deactivated" banner was permanently dismissed for [userWalletId]. */
|
||||
suspend fun isDeactivationBannerDismissed(userWalletId: UserWalletId): Boolean
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ internal class TangemPayCashbackDateFormatter {
|
|||
fun formatMonth(year: Int, month: Int): String =
|
||||
DateTimeFormatters.formatDate(DateTime(year, month, 1, 0, 0), DateTimeFormatters.dateMMMM)
|
||||
|
||||
fun formatShortMonth(year: Int, month: Int): String =
|
||||
DateTimeFormatters.formatDate(DateTime(year, month, 1, 0, 0), DateTimeFormatters.dateMMM)
|
||||
|
||||
fun formatMonthDay(date: DateTime): String = DateTimeFormatters.formatDate(date, DateTimeFormatters.dateMMMMd)
|
||||
|
||||
fun formatWindow(start: DateTime, end: DateTime): String {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.features.tangempay.cashback.impl.model
|
||||
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
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.core.ui.format.bigdecimal.optionalDecimals
|
||||
import com.tangem.domain.pay.model.CashbackHistory
|
||||
import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackHistogramUM
|
||||
import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackHistogramUM.Style
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class TangemPayCashbackHistogramConverter(
|
||||
private val dateFormatter: TangemPayCashbackDateFormatter = TangemPayCashbackDateFormatter(),
|
||||
) : Converter<CashbackHistory, TangemPayCashbackHistogramUM> {
|
||||
|
||||
// TODO([REDACTED_TASK_KEY]): move hardcoded strings to string resources
|
||||
override fun convert(value: CashbackHistory): TangemPayCashbackHistogramUM {
|
||||
val currency = getJavaCurrencyByCode(value.currency)
|
||||
val total = value.months.fold(BigDecimal.ZERO) { acc, month -> acc + month.confirmedAmount }
|
||||
val formattedTotal = total.format { fiat(currency.currencyCode, currency.symbol).optionalDecimals() }
|
||||
val lastIndex = value.months.lastIndex
|
||||
return TangemPayCashbackHistogramUM(
|
||||
title = stringReference("$formattedTotal earned in total"),
|
||||
bars = value.months.mapIndexed { index, month ->
|
||||
TangemPayCashbackHistogramUM.Bar(
|
||||
month = stringReference(dateFormatter.formatShortMonth(month.year, month.month)),
|
||||
amount = stringReference(
|
||||
month.confirmedAmount.format { fiat(currency.currencyCode, currency.symbol).defaultAmount() },
|
||||
),
|
||||
amountValue = month.confirmedAmount.toFloat(),
|
||||
style = when {
|
||||
index != lastIndex -> Style.Regular
|
||||
month.confirmedAmount.signum() < 0 -> Style.HighlightedNegative
|
||||
else -> Style.Highlighted
|
||||
},
|
||||
)
|
||||
}.toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.core.decompose.navigation.Router
|
|||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlan
|
||||
import com.tangem.domain.pay.model.CashbackDocument
|
||||
import com.tangem.domain.pay.model.CashbackHistory
|
||||
import com.tangem.domain.pay.model.CashbackPromotions
|
||||
import com.tangem.domain.pay.model.CashbackSummary
|
||||
import com.tangem.domain.pay.repository.CashbackRepository
|
||||
|
|
@ -26,6 +27,8 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val CASHBACK_HISTORY_MONTHS = 5
|
||||
|
||||
@Stable
|
||||
@ModelScoped
|
||||
internal class TangemPayCashbackModel @Inject constructor(
|
||||
|
|
@ -43,6 +46,7 @@ internal class TangemPayCashbackModel @Inject constructor(
|
|||
val bottomSheetNavigation: SlotNavigation<TangemPayCashbackNavigation> = SlotNavigation()
|
||||
|
||||
private val cashbackConverter = TangemPayCashbackUmConverter(onCloseClick = router::pop)
|
||||
private val histogramConverter = TangemPayCashbackHistogramConverter()
|
||||
private val tiersConverter = TangemPayCashbackTiersConverter()
|
||||
private val infoTilesConverter = TangemPayCashbackInfoTilesConverter(
|
||||
onRateClick = { bottomSheetNavigation.activate(TangemPayCashbackNavigation.Details) },
|
||||
|
|
@ -62,6 +66,7 @@ internal class TangemPayCashbackModel @Inject constructor(
|
|||
TangemPayCashbackScreenUM(
|
||||
cashback = cashbackConverter.convert(value = null),
|
||||
infoTiles = null,
|
||||
histogram = null,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -77,6 +82,7 @@ internal class TangemPayCashbackModel @Inject constructor(
|
|||
val planDeferred = async { loadPlan() }
|
||||
|
||||
val summary = summaryDeferred.await()
|
||||
val history = if (summary is CashbackSummary.Enabled) loadHistory() else null
|
||||
val promotions = promotionsDeferred.await()
|
||||
val plan = planDeferred.await()
|
||||
val tiers = promotions?.let(tiersConverter::convert).orEmpty()
|
||||
|
|
@ -89,6 +95,7 @@ internal class TangemPayCashbackModel @Inject constructor(
|
|||
currentPlan = plan,
|
||||
)
|
||||
},
|
||||
histogram = history?.takeIf { it.months.isNotEmpty() }?.let(histogramConverter::convert),
|
||||
)
|
||||
detailsSheet.value = detailsConverter.convert(tiers)
|
||||
accrualsSheet.value = accrualsConverter.convert(docsDeferred.await())
|
||||
|
|
@ -98,6 +105,10 @@ internal class TangemPayCashbackModel @Inject constructor(
|
|||
private suspend fun loadSummary(): CashbackSummary? =
|
||||
runSuspendCatching { cashbackRepository.getCashbackSummary(userWalletId).getOrNull() }.getOrNull()
|
||||
|
||||
private suspend fun loadHistory(): CashbackHistory? = runSuspendCatching {
|
||||
cashbackRepository.getCashbackHistory(userWalletId, CASHBACK_HISTORY_MONTHS).getOrNull()
|
||||
}.getOrNull()
|
||||
|
||||
private suspend fun loadPromotions(): CashbackPromotions? =
|
||||
runSuspendCatching { cashbackRepository.getCashbackPromotions(userWalletId).getOrNull() }.getOrNull()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,205 @@
|
|||
package com.tangem.features.tangempay.cashback.impl.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
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.cashback.impl.ui.state.TangemPayCashbackHistogramUM
|
||||
import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackHistogramUM.Style
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun TangemPayCashbackHistogram(state: TangemPayCashbackHistogramUM, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 56.dp)
|
||||
.padding(16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = state.title.resolveReference(),
|
||||
style = TangemTheme.typography3.heading.small,
|
||||
color = TangemTheme.colors3.text.primary,
|
||||
)
|
||||
}
|
||||
Chart(bars = state.bars)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Chart(bars: ImmutableList<TangemPayCashbackHistogramUM.Bar>, modifier: Modifier = Modifier) {
|
||||
val maxValue = bars.maxOfOrNull { it.amountValue }?.coerceAtLeast(0f) ?: 0f
|
||||
Column(modifier = modifier.fillMaxWidth()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
) {
|
||||
bars.forEach { bar ->
|
||||
BarColumn(bar = bar, maxValue = maxValue, modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(1.dp)
|
||||
.background(TangemTheme.colors3.border.primary),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
bars.forEach { bar ->
|
||||
Text(
|
||||
text = bar.month.resolveReference(),
|
||||
style = TangemTheme.typography3.caption.medium,
|
||||
color = monthColor(bar.style),
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BarColumn(bar: TangemPayCashbackHistogramUM.Bar, maxValue: Float, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
if (bar.style != Style.Regular || bar.amountValue != 0f) {
|
||||
Text(
|
||||
text = bar.amount.resolveReference(),
|
||||
style = TangemTheme.typography3.caption.medium,
|
||||
color = amountColor(bar.style),
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 1,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(barHeight(value = bar.amountValue, maxValue = maxValue))
|
||||
.clip(RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp))
|
||||
.background(barColor(bar.style)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun barHeight(value: Float, maxValue: Float): Dp {
|
||||
if (maxValue <= 0f) return 3.dp
|
||||
val fraction = (value / maxValue).coerceIn(0f, 1f)
|
||||
return maxOf(3.dp, 115.dp * fraction)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun barColor(style: Style): Color = when (style) {
|
||||
Style.Regular -> TangemTheme.colors3.bg.opaque.secondary
|
||||
Style.Highlighted -> TangemTheme.colors3.bg.brand
|
||||
Style.HighlightedNegative -> TangemTheme.colors3.bg.status.error
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun amountColor(style: Style): Color = when (style) {
|
||||
Style.Regular -> TangemTheme.colors3.text.tertiary
|
||||
else -> TangemTheme.colors3.text.primary
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun monthColor(style: Style): Color = when (style) {
|
||||
Style.Regular -> TangemTheme.colors3.text.tertiary
|
||||
else -> TangemTheme.colors3.text.secondary
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun TangemPayCashbackHistogramPreview(
|
||||
@PreviewParameter(TangemPayCashbackHistogramUMProvider::class) state: TangemPayCashbackHistogramUM,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
TangemPayCashbackHistogram(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(TangemTheme.colors3.bg.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private class TangemPayCashbackHistogramUMProvider : CollectionPreviewParameterProvider<TangemPayCashbackHistogramUM>(
|
||||
collection = listOf(
|
||||
// Populated
|
||||
TangemPayCashbackHistogramUM(
|
||||
title = stringReference("$132.15 earned in total"),
|
||||
bars = persistentListOf(
|
||||
bar(month = "Feb", amount = "$12.02", value = 12.02f, style = Style.Regular),
|
||||
bar(month = "Mar", amount = "$44.22", value = 44.22f, style = Style.Regular),
|
||||
bar(month = "Apr", amount = "$38.52", value = 38.52f, style = Style.Regular),
|
||||
bar(month = "May", amount = "$26.10", value = 26.10f, style = Style.Regular),
|
||||
bar(month = "Jun", amount = "$32.15", value = 32.15f, style = Style.Highlighted),
|
||||
),
|
||||
),
|
||||
// Negative current month
|
||||
TangemPayCashbackHistogramUM(
|
||||
title = stringReference("$132.15 earned in total"),
|
||||
bars = persistentListOf(
|
||||
bar(month = "Feb", amount = "$12.02", value = 12.02f, style = Style.Regular),
|
||||
bar(month = "Mar", amount = "$44.22", value = 44.22f, style = Style.Regular),
|
||||
bar(month = "Apr", amount = "$38.52", value = 38.52f, style = Style.Regular),
|
||||
bar(month = "May", amount = "$26.10", value = 26.10f, style = Style.Regular),
|
||||
bar(month = "Jun", amount = "-$2.15", value = -2.15f, style = Style.HighlightedNegative),
|
||||
),
|
||||
),
|
||||
// Empty
|
||||
TangemPayCashbackHistogramUM(
|
||||
title = stringReference("$0 earned in total"),
|
||||
bars = persistentListOf(
|
||||
bar(month = "Feb", amount = "$0.00", value = 0f, style = Style.Regular),
|
||||
bar(month = "Mar", amount = "$0.00", value = 0f, style = Style.Regular),
|
||||
bar(month = "Apr", amount = "$0.00", value = 0f, style = Style.Regular),
|
||||
bar(month = "May", amount = "$0.00", value = 0f, style = Style.Regular),
|
||||
bar(month = "Jun", amount = "$0.00", value = 0f, style = Style.Highlighted),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private fun bar(month: String, amount: String, value: Float, style: Style) = TangemPayCashbackHistogramUM.Bar(
|
||||
month = stringReference(month),
|
||||
amount = stringReference(amount),
|
||||
amountValue = value,
|
||||
style = style,
|
||||
)
|
||||
|
|
@ -10,7 +10,9 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -35,10 +37,13 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.core.ui.res.LocalIsInDarkTheme
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackHistogramUM
|
||||
import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackHistogramUM.Style
|
||||
import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackInfoTilesUM
|
||||
import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackScreenUM
|
||||
import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackUM
|
||||
import dev.chrisbanes.haze.HazeStyle
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import com.tangem.core.ui.R as CoreUiR
|
||||
|
||||
private const val GLOW_RADIUS_FACTOR = 0.585f
|
||||
|
|
@ -62,20 +67,33 @@ internal fun TangemPayCashbackScreen(state: TangemPayCashbackScreenUM, modifier:
|
|||
contentAlign = TangemTopNavigation.ContentAlign.Center,
|
||||
onClose = state.cashback.onCloseClick,
|
||||
)
|
||||
HeroBlock(state = state.cashback)
|
||||
state.cashback.banner?.let { banner ->
|
||||
CashbackBanner(
|
||||
banner = banner,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
state.infoTiles?.let { infoTiles ->
|
||||
TangemPayCashbackInfoTiles(
|
||||
state = infoTiles,
|
||||
modifier = Modifier.padding(top = 24.dp),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
HeroBlock(state = state.cashback)
|
||||
state.cashback.banner?.let { banner ->
|
||||
CashbackBanner(
|
||||
banner = banner,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
state.infoTiles?.let { infoTiles ->
|
||||
TangemPayCashbackInfoTiles(
|
||||
state = infoTiles,
|
||||
modifier = Modifier.padding(top = 24.dp),
|
||||
)
|
||||
}
|
||||
state.histogram?.let { histogram ->
|
||||
TangemPayCashbackHistogram(
|
||||
state = histogram,
|
||||
modifier = Modifier.padding(top = 24.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -193,6 +211,7 @@ private class TangemPayCashbackScreenUMProvider : CollectionPreviewParameterProv
|
|||
onCloseClick = {},
|
||||
),
|
||||
infoTiles = previewInfoTiles(),
|
||||
histogram = previewHistogram(),
|
||||
),
|
||||
TangemPayCashbackScreenUM(
|
||||
cashback = TangemPayCashbackUM(
|
||||
|
|
@ -208,6 +227,7 @@ private class TangemPayCashbackScreenUMProvider : CollectionPreviewParameterProv
|
|||
onCloseClick = {},
|
||||
),
|
||||
infoTiles = null,
|
||||
histogram = null,
|
||||
),
|
||||
TangemPayCashbackScreenUM(
|
||||
cashback = TangemPayCashbackUM(
|
||||
|
|
@ -218,6 +238,7 @@ private class TangemPayCashbackScreenUMProvider : CollectionPreviewParameterProv
|
|||
onCloseClick = {},
|
||||
),
|
||||
infoTiles = null,
|
||||
histogram = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -235,4 +256,24 @@ private fun previewInfoTiles() = TangemPayCashbackInfoTilesUM(
|
|||
subtitle = stringReference("Limits and exceptions"),
|
||||
onClick = {},
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun previewHistogram(): TangemPayCashbackHistogramUM {
|
||||
fun bar(month: String, amount: String, value: Float, style: Style) = TangemPayCashbackHistogramUM.Bar(
|
||||
month = stringReference(month),
|
||||
amount = stringReference(amount),
|
||||
amountValue = value,
|
||||
style = style,
|
||||
)
|
||||
return TangemPayCashbackHistogramUM(
|
||||
title = stringReference("$132.15 earned in total"),
|
||||
bars = persistentListOf(
|
||||
bar(month = "Feb", amount = "$12.02", value = 12.02f, style = Style.Regular),
|
||||
bar(month = "Mar", amount = "$44.22", value = 44.22f, style = Style.Regular),
|
||||
bar(month = "Apr", amount = "$38.52", value = 38.52f, style = Style.Regular),
|
||||
bar(month = "May", amount = "$26.10", value = 26.10f, style = Style.Regular),
|
||||
bar(month = "Jun", amount = "$32.15", value = 32.15f, style = Style.Highlighted),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.tangem.features.tangempay.cashback.impl.ui.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
/**
|
||||
* State for the monthly earnings histogram on the Cashback screen.
|
||||
*
|
||||
* @property title formatted total, e.g. "$132.15 earned in total"
|
||||
* @property bars one bar per calendar month, ordered oldest to newest
|
||||
*/
|
||||
@Immutable
|
||||
internal data class TangemPayCashbackHistogramUM(
|
||||
val title: TextReference,
|
||||
val bars: ImmutableList<Bar>,
|
||||
) {
|
||||
|
||||
/**
|
||||
* @property amount formatted earnings for this month, e.g. "$12.02" (or "-$2.15" for a refund)
|
||||
* @property amountValue numeric earnings, used only to size the bar relative to the others
|
||||
* @property style visual treatment; only the current (last) month is highlighted
|
||||
*/
|
||||
@Immutable
|
||||
data class Bar(
|
||||
val month: TextReference,
|
||||
val amount: TextReference,
|
||||
val amountValue: Float,
|
||||
val style: Style,
|
||||
)
|
||||
|
||||
enum class Style { Regular, Highlighted, HighlightedNegative }
|
||||
}
|
||||
|
|
@ -6,4 +6,5 @@ import androidx.compose.runtime.Immutable
|
|||
internal data class TangemPayCashbackScreenUM(
|
||||
val cashback: TangemPayCashbackUM,
|
||||
val infoTiles: TangemPayCashbackInfoTilesUM?,
|
||||
val histogram: TangemPayCashbackHistogramUM?,
|
||||
)
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.tangem.features.tangempay.cashback.impl.model
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.pay.model.CashbackHistory
|
||||
import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackHistogramUM
|
||||
import com.tangem.features.tangempay.cashback.impl.ui.state.TangemPayCashbackHistogramUM.Style
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
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 java.math.BigDecimal
|
||||
import java.util.Locale
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class TangemPayCashbackHistogramConverterTest {
|
||||
|
||||
private val defaultLocale = Locale.getDefault()
|
||||
private val converter = TangemPayCashbackHistogramConverter()
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
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 history WHEN convert THEN total sums months and last month highlighted`() {
|
||||
// Arrange
|
||||
val history = CashbackHistory(
|
||||
currency = "USD",
|
||||
months = listOf(
|
||||
month(2, BigDecimal("12.02")),
|
||||
month(3, BigDecimal("44.22")),
|
||||
month(6, BigDecimal("32.15")),
|
||||
),
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = converter.convert(history)
|
||||
|
||||
// Assert
|
||||
val expected = TangemPayCashbackHistogramUM(
|
||||
title = stringReference("$88.39 earned in total"),
|
||||
bars = persistentListOf(
|
||||
bar("Feb", "$12.02", 12.02f, Style.Regular),
|
||||
bar("Mar", "$44.22", 44.22f, Style.Regular),
|
||||
bar("Jun", "$32.15", 32.15f, Style.Highlighted),
|
||||
),
|
||||
)
|
||||
assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN zero amounts WHEN convert THEN empty total and last month still highlighted`() {
|
||||
// Arrange
|
||||
val history = CashbackHistory(
|
||||
currency = "USD",
|
||||
months = listOf(month(5, BigDecimal.ZERO), month(6, BigDecimal.ZERO)),
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = converter.convert(history)
|
||||
|
||||
// Assert
|
||||
assertThat(actual.title).isEqualTo(stringReference("$0 earned in total"))
|
||||
assertThat(actual.bars.map { it.style }).containsExactly(Style.Regular, Style.Highlighted).inOrder()
|
||||
assertThat(actual.bars.last().amount).isEqualTo(stringReference("$0.00"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN negative last month WHEN convert THEN last bar highlighted negative`() {
|
||||
// Arrange
|
||||
val history = CashbackHistory(
|
||||
currency = "USD",
|
||||
months = listOf(month(5, BigDecimal("26.10")), month(6, BigDecimal("-2.15"))),
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual = converter.convert(history)
|
||||
|
||||
// Assert
|
||||
assertThat(actual.title).isEqualTo(stringReference("$23.95 earned in total"))
|
||||
assertThat(actual.bars.last().style).isEqualTo(Style.HighlightedNegative)
|
||||
assertThat(actual.bars.last().amount).isEqualTo(stringReference("-$2.15"))
|
||||
}
|
||||
|
||||
private fun bar(month: String, amount: String, value: Float, style: Style) =
|
||||
TangemPayCashbackHistogramUM.Bar(stringReference(month), stringReference(amount), value, style)
|
||||
|
||||
private fun month(month: Int, amount: BigDecimal) =
|
||||
CashbackHistory.MonthlyCashback(year = 2026, month = month, confirmedAmount = amount)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.tangempay.cashback.impl.model
|
||||
|
||||
import android.text.format.DateFormat
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.core.decompose.model.MutableParamsContainer
|
||||
|
|
@ -9,18 +10,25 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.domain.models.account.TangemPayCustomerTariffPlan
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlan
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.CashbackDisplayMode
|
||||
import com.tangem.domain.pay.model.CashbackDocument
|
||||
import com.tangem.domain.pay.model.CashbackHistory
|
||||
import com.tangem.domain.pay.model.CashbackPromotions
|
||||
import com.tangem.domain.pay.model.CashbackSummary
|
||||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
import com.tangem.domain.pay.model.TangemPayCashback
|
||||
import com.tangem.domain.pay.repository.CashbackRepository
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.features.tangempay.cashback.api.TangemPayCashbackComponent
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import org.joda.time.DateTime
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
|
@ -40,6 +48,8 @@ internal class TangemPayCashbackModelTest {
|
|||
@BeforeEach
|
||||
fun setup() {
|
||||
Locale.setDefault(Locale.US)
|
||||
mockkStatic(DateFormat::class)
|
||||
every { DateFormat.getBestDateTimePattern(any(), any()) } answers { secondArg() }
|
||||
clearMocks(cashbackRepository, onboardingRepository)
|
||||
coEvery { cashbackRepository.getCashbackSummary(any()) } returns CashbackSummary.Disabled.right()
|
||||
coEvery { cashbackRepository.getCashbackPromotions(any()) } returns promotions().right()
|
||||
|
|
@ -50,6 +60,7 @@ internal class TangemPayCashbackModelTest {
|
|||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(DateFormat::class)
|
||||
Locale.setDefault(defaultLocale)
|
||||
}
|
||||
|
||||
|
|
@ -129,6 +140,30 @@ internal class TangemPayCashbackModelTest {
|
|||
assertThat(model.accrualsSheet.value.infoRows).isNotEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN enabled summary and history WHEN model created THEN histogram populated`() {
|
||||
// Arrange
|
||||
coEvery { cashbackRepository.getCashbackSummary(any()) } returns enabledSummary().right()
|
||||
coEvery { cashbackRepository.getCashbackHistory(any(), any()) } returns history().right()
|
||||
|
||||
// Act
|
||||
val model = createModel()
|
||||
|
||||
// Assert
|
||||
assertThat(model.uiState.value.histogram).isNotNull()
|
||||
assertThat(model.uiState.value.histogram?.bars).hasSize(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN disabled summary WHEN model created THEN history not requested and histogram null`() {
|
||||
// Act
|
||||
val model = createModel()
|
||||
|
||||
// Assert
|
||||
assertThat(model.uiState.value.histogram).isNull()
|
||||
coVerify(exactly = 0) { cashbackRepository.getCashbackHistory(any(), any()) }
|
||||
}
|
||||
|
||||
private fun createModel() = TangemPayCashbackModel(
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
paramsContainer = MutableParamsContainer(TangemPayCashbackComponent.Params(userWalletId = userWalletId)),
|
||||
|
|
@ -162,6 +197,31 @@ internal class TangemPayCashbackModelTest {
|
|||
CashbackDocument(id = "terms", title = "Full terms of cashback program", url = "https://x/terms.pdf"),
|
||||
)
|
||||
|
||||
private fun enabledSummary() = CashbackSummary.Enabled(
|
||||
displayMode = CashbackDisplayMode.FULL,
|
||||
cashback = TangemPayCashback(
|
||||
confirmedAmount = BigDecimal("32.15"),
|
||||
pendingAmount = BigDecimal.ZERO,
|
||||
currency = "USD",
|
||||
payoutCurrency = "USDC",
|
||||
payoutNetwork = "Polygon",
|
||||
period = TangemPayCashback.Period(
|
||||
year = 2026,
|
||||
month = 6,
|
||||
payoutStart = DateTime.parse("2026-07-02"),
|
||||
payoutEnd = DateTime.parse("2026-07-05"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private fun history() = CashbackHistory(
|
||||
currency = "USD",
|
||||
months = listOf(
|
||||
CashbackHistory.MonthlyCashback(year = 2026, month = 5, confirmedAmount = BigDecimal("26.10")),
|
||||
CashbackHistory.MonthlyCashback(year = 2026, month = 6, confirmedAmount = BigDecimal("32.15")),
|
||||
),
|
||||
)
|
||||
|
||||
private fun customerInfo(tierId: String, planName: String): CustomerInfo {
|
||||
val currentPlan = TangemPayTariffPlan(
|
||||
id = "plan-$tierId",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue