Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-14 18:26:55 +05:00
parent 85158f31dc
commit e99ffe39f0
28 changed files with 480 additions and 52 deletions

View file

@ -3,14 +3,15 @@ package com.tangem.domain.marketing.models
import java.math.BigDecimal
/**
* USD min/max eligibility gate. Applies only to swap/onramp campaigns and only when [amountUsd] is known;
* otherwise the campaign passes (non-amount screens and the "amount unknown" case are not gated).
* USD min/max eligibility gate (mirrors iOS `satisfiesAmount`). A campaign without min/max bounds is
* always eligible. A bounded campaign requires a known [amountUsd] while the amount is unknown the
* campaign is NOT eligible (hidden until a quote/amount arrives), then it must fall within the bounds.
*/
fun MarketingCampaign.matchesUsdAmount(amountUsd: BigDecimal?): Boolean {
val isAmountScreen = type == MarketingScreenType.SWAP || type == MarketingScreenType.ONRAMP
if (!isAmountScreen || amountUsd == null) return true
if (minAmount == null && maxAmount == null) return true
if (minAmount != null && amountUsd < minAmount) return false
if (maxAmount != null && amountUsd > maxAmount) return false
val usd = amountUsd ?: return false
if (minAmount != null && usd < minAmount) return false
if (maxAmount != null && usd > maxAmount) return false
return true
}

View file

@ -21,16 +21,24 @@ internal class MarketingCampaignAmountTest {
)
@Test
fun `GIVEN non swap-onramp type WHEN matchesUsdAmount THEN always true`() {
val c = campaign(MarketingScreenType.TOKEN_DETAILS, minAmount = BigDecimal(50), maxAmount = BigDecimal(300))
fun `GIVEN no min max bounds WHEN matchesUsdAmount THEN always true`() {
val c = campaign(MarketingScreenType.TOKEN_DETAILS, minAmount = null, maxAmount = null)
assertThat(c.matchesUsdAmount(BigDecimal(10))).isTrue()
assertThat(c.matchesUsdAmount(null)).isTrue()
}
@Test
fun `GIVEN swap with null amount WHEN matchesUsdAmount THEN true`() {
fun `GIVEN bounded campaign of any type WHEN amount out of range THEN false`() {
// Bounds apply regardless of screen type (iOS parity): type no longer exempts a bounded campaign.
val c = campaign(MarketingScreenType.TOKEN_DETAILS, minAmount = BigDecimal(50), maxAmount = BigDecimal(300))
assertThat(c.matchesUsdAmount(BigDecimal(10))).isFalse()
assertThat(c.matchesUsdAmount(BigDecimal(100))).isTrue()
}
@Test
fun `GIVEN bounded campaign with null amount WHEN matchesUsdAmount THEN false`() {
val c = campaign(MarketingScreenType.SWAP, minAmount = BigDecimal(50))
assertThat(c.matchesUsdAmount(null)).isTrue()
assertThat(c.matchesUsdAmount(null)).isFalse()
}
@Test

View file

@ -28,7 +28,9 @@ class GetMarketingBannerUseCase(
campaigns.asSequence()
.filterNot { it.id in dismissed }
.filter { matchesTarget(it, screen) }
.filter { it.matchesUsdAmount(amountUsd) }
// Amount gating runs reactively in the consumer (with the live amount). Skip it here when
// no amount is provided, so bounded swap/onramp campaigns aren't dropped on the pre-fetch.
.filter { amountUsd == null || it.matchesUsdAmount(amountUsd) }
.sortedBy { it.priority }
.toList()
}