Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-08 19:32:44 +05:00
parent 33efe2ae52
commit 499b7c04ea
33 changed files with 1502 additions and 0 deletions

View file

@ -178,5 +178,9 @@
{
"name": "TWI_1637_CASHBACK_AND_REACTIVATION_CAMPAIGNS_ENABLED",
"version": "6.0.1"
},
{
"name": "TWI_1522_MARKETING_BANNERS_ENABLED",
"version": "6.0.1"
}
]

View file

@ -0,0 +1,11 @@
package com.tangem.datasource.api.marketing.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/** Cached campaigns response plus its ETag, persisted per [CampaignDto.type] for revalidation. */
@JsonClass(generateAdapter = true)
data class MarketingCampaignsCacheEntry(
@Json(name = "eTag") val eTag: String?,
@Json(name = "response") val response: MarketingCampaignsResponse,
)

View file

@ -0,0 +1,42 @@
package com.tangem.datasource.api.marketing.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.math.BigDecimal
@JsonClass(generateAdapter = true)
data class MarketingCampaignsResponse(
@Json(name = "campaigns") val campaigns: List<CampaignDto>,
)
@JsonClass(generateAdapter = true)
data class CampaignDto(
@Json(name = "id") val id: Int,
@Json(name = "type") val type: String,
@Json(name = "priority") val priority: Int,
@Json(name = "startAt") val startAt: String? = null,
@Json(name = "endAt") val endAt: String? = null,
@Json(name = "minAmount") val minAmount: BigDecimal? = null,
@Json(name = "maxAmount") val maxAmount: BigDecimal? = null,
@Json(name = "providerIds") val providerIds: List<String>? = null,
@Json(name = "tokens") val tokens: List<CampaignTokenDto>? = null,
@Json(name = "banner") val banner: BannerDto,
)
@JsonClass(generateAdapter = true)
data class CampaignTokenDto(
@Json(name = "networkId") val networkId: String? = null,
@Json(name = "contractAddress") val contractAddress: String? = null,
@Json(name = "id") val id: String? = null,
)
@JsonClass(generateAdapter = true)
data class BannerDto(
@Json(name = "uiType") val uiType: String,
@Json(name = "text") val text: String? = null,
@Json(name = "icon") val icon: String? = null,
@Json(name = "iconAlign") val iconAlign: String? = null,
@Json(name = "bgColor") val bgColor: String? = null,
@Json(name = "deeplink") val deeplink: String? = null,
@Json(name = "dismissible") val isDismissible: Boolean = false,
)

View file

@ -3,6 +3,7 @@ package com.tangem.datasource.api.tangemTech
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.promotion.models.CreatePromotionRegistrationBody
import com.tangem.datasource.api.promotion.models.PromotionRegistrationResponse
import com.tangem.datasource.api.marketing.models.MarketingCampaignsResponse
import com.tangem.datasource.api.promotion.models.PromotionsResponse
import com.tangem.datasource.api.promotion.models.YieldBoostStatusResponse
import com.tangem.datasource.api.stories.models.StoryContentResponse
@ -242,4 +243,18 @@ interface TangemTechApi {
@GET("v1/earn/networks")
suspend fun getEarnNetworks(@Query("type") type: String? = null): ApiResponse<EarnNetworkListResponse>
// endregion
// region marketing
@GET("api/v1/marketing/campaigns")
suspend fun getMarketingCampaigns(
@Query("type") type: String,
@Query("language") language: String? = null,
@Query("fromNetwork") fromNetwork: String? = null,
@Query("fromContractAddress") fromContractAddress: String? = null,
@Query("toNetwork") toNetwork: String? = null,
@Query("toContractAddress") toContractAddress: String? = null,
@Query("fromFiat") fromFiat: String? = null,
@Header("If-None-Match") eTag: String? = null,
): ApiResponse<MarketingCampaignsResponse>
// endregion
}

View file

@ -0,0 +1,75 @@
package com.tangem.datasource.api.marketing
import com.google.common.truth.Truth.assertThat
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.common.adapter.BigDecimalAdapter
import com.tangem.datasource.api.marketing.models.MarketingCampaignsResponse
import org.junit.jupiter.api.Test
import java.math.BigDecimal
internal class MarketingCampaignsResponseTest {
private val moshi = Moshi.Builder().add(BigDecimalAdapter()).build()
private val adapter = moshi.adapter(MarketingCampaignsResponse::class.java)
@Test
fun `GIVEN swap response json WHEN parsed THEN fields mapped`() {
// Arrange
val json = """
{"campaigns":[{"id":12,"type":"swap","priority":1,"minAmount":50,"maxAmount":300,
"providerIds":["provider1"],
"banner":{"uiType":"linked_to_provider","text":"Cashback 4 U","icon":"https://x/star.webp",
"bgColor":"#FF0011","deeplink":"https://tangem.com","dismissible":true}}]}
""".trimIndent()
// Act
val result = adapter.fromJson(json)!!
// Assert
val campaign = result.campaigns.single()
assertThat(campaign.id).isEqualTo(12)
assertThat(campaign.type).isEqualTo("swap")
assertThat(campaign.minAmount).isEqualTo(BigDecimal(50))
assertThat(campaign.providerIds).containsExactly("provider1")
assertThat(campaign.banner.uiType).isEqualTo("linked_to_provider")
assertThat(campaign.banner.isDismissible).isTrue()
assertThat(campaign.tokens).isNull()
}
@Test
fun `GIVEN token_details response WHEN parsed THEN network targets mapped`() {
// Arrange
val json = """
{"campaigns":[{"id":12,"type":"token_details","priority":1,
"tokens":[{"networkId":"ethereum","contractAddress":"0xA0b8"}],
"banner":{"uiType":"standalone","dismissible":false}}]}
""".trimIndent()
// Act
val campaign = adapter.fromJson(json)!!.campaigns.single()
// Assert
val token = campaign.tokens!!.single()
assertThat(token.networkId).isEqualTo("ethereum")
assertThat(token.contractAddress).isEqualTo("0xA0b8")
assertThat(token.id).isNull()
assertThat(campaign.minAmount).isNull()
}
@Test
fun `GIVEN markets response WHEN parsed THEN coingecko ids mapped`() {
// Arrange
val json = """
{"campaigns":[{"id":12,"type":"markets_token","priority":1,
"tokens":[{"id":"1696501400"},{"id":"3296501412"}],
"banner":{"uiType":"standalone","dismissible":true}}]}
""".trimIndent()
// Act
val tokens = adapter.fromJson(json)!!.campaigns.single().tokens!!
// Assert
assertThat(tokens.map { it.id }).containsExactly("1696501400", "3296501412")
assertThat(tokens.all { it.networkId == null }).isTrue()
}
}