Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-26 21:41:59 +07:00
commit eb9eed8d6c
43 changed files with 1296 additions and 131 deletions

View file

@ -56,6 +56,7 @@ interface TangemTechApi {
@Body userTokens: UserTokensResponse,
): ApiResponse<Unit>
// region Referral
/** Returns referral status by [walletId] */
@GET("v1/referral/{walletId}")
suspend fun getReferralStatus(@Path("walletId") walletId: String): ApiResponse<ReferralResponse>
@ -64,6 +65,10 @@ interface TangemTechApi {
@POST("v1/referral")
suspend fun startReferral(@Body startReferralBody: StartReferralBody): ApiResponse<ReferralResponse>
@POST("v1/referral/bind-wallets-by-code")
suspend fun bindWalletsByReferralCode(@Body body: BindWalletsByReferralCodeBody): ApiResponse<Unit>
// endregion
@GET("v1/quotes")
suspend fun getQuotes(
@Query("currencyId") currencyId: String,

View file

@ -4,14 +4,21 @@ import com.tangem.datasource.api.tangemTech.models.CardInfoBody
import com.tangem.datasource.api.tangemTech.models.WalletIdBody
import com.tangem.datasource.api.tangemTech.models.WalletType
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.models.AppsFlyerConversionData
object WalletIdBodyConverter {
fun convert(userWallet: UserWallet, publicKeys: Map<String, String>? = null): WalletIdBody {
fun convert(
userWallet: UserWallet,
conversionData: AppsFlyerConversionData? = null,
publicKeys: Map<String, String>? = null,
): WalletIdBody {
return WalletIdBody(
walletId = userWallet.walletId.stringValue,
name = userWallet.name,
walletType = WalletType.from(userWallet),
refcode = conversionData?.refcode,
campaign = conversionData?.campaign,
cards = publicKeys?.map { publicKeyById ->
CardInfoBody(
cardId = publicKeyById.key,

View file

@ -0,0 +1,11 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class BindWalletsByReferralCodeBody(
@Json(name = "walletIds") val walletIds: List<String>,
@Json(name = "referralCode") val refcode: String,
@Json(name = "utmCampaign") val campaign: String? = null,
)

View file

@ -17,6 +17,8 @@ data class UserTokensResponse(
@Json(name = "notifyStatus") val notifyStatus: Boolean? = null,
@Json(name = "name") val walletName: String? = null,
@Json(name = "type") val walletType: WalletType? = null,
@Json(name = "ref") val refcode: String? = null,
@Json(name = "campaign") val campaign: String? = null,
) {
@JsonClass(generateAdapter = true)

View file

@ -9,4 +9,6 @@ data class WalletIdBody(
@Json(name = "name") val name: String,
@Json(name = "type") val walletType: WalletType? = null,
@Json(name = "cards") val cards: List<CardInfoBody>? = null,
@Json(name = "ref") val refcode: String? = null,
@Json(name = "campaign") val campaign: String? = null,
)

View file

@ -0,0 +1,21 @@
package com.tangem.datasource.di
import com.tangem.datasource.local.appsflyer.AppsFlyerConversionStore
import com.tangem.datasource.local.appsflyer.DefaultAppsFlyerConversionStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object AppsFlyerConversionStoreModule {
@Provides
@Singleton
fun provideAppFlyerConversionStore(appPreferencesStore: AppPreferencesStore): AppsFlyerConversionStore {
return DefaultAppsFlyerConversionStore(appPreferencesStore = appPreferencesStore)
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.datasource.local.appsflyer
import com.tangem.domain.wallets.models.AppsFlyerConversionData
interface AppsFlyerConversionStore {
suspend fun get(): AppsFlyerConversionData?
suspend fun store(value: AppsFlyerConversionData)
suspend fun storeIfAbsent(value: AppsFlyerConversionData)
}

View file

@ -0,0 +1,21 @@
package com.tangem.datasource.local.appsflyer
import com.tangem.domain.wallets.models.AppsFlyerConversionData
import com.tangem.utils.converter.TwoWayConverter
internal object ConversionDataConverter : TwoWayConverter<AppsFlyerConversionData, ConversionDataDTO> {
override fun convert(value: AppsFlyerConversionData): ConversionDataDTO {
return ConversionDataDTO(
refcode = value.refcode,
campaign = value.campaign,
)
}
override fun convertBack(value: ConversionDataDTO): AppsFlyerConversionData {
return AppsFlyerConversionData(
refcode = value.refcode,
campaign = value.campaign,
)
}
}

View file

@ -0,0 +1,53 @@
package com.tangem.datasource.local.appsflyer
import androidx.datastore.preferences.core.stringPreferencesKey
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
import com.tangem.datasource.local.preferences.utils.storeObject
import com.tangem.domain.wallets.models.AppsFlyerConversionData
import timber.log.Timber
internal class DefaultAppsFlyerConversionStore(
private val appPreferencesStore: AppPreferencesStore,
) : AppsFlyerConversionStore {
override suspend fun get(): AppsFlyerConversionData? {
val dto = appPreferencesStore.getObjectSyncOrNull<ConversionDataDTO>(KEY) ?: return null
return ConversionDataConverter.convertBack(value = dto).also {
Timber.i("Getting conversion data from store: $it")
}
}
override suspend fun store(value: AppsFlyerConversionData) {
Timber.i("Storing conversion data to store: $value")
val dto = ConversionDataConverter.convert(value)
appPreferencesStore.storeObject(KEY, dto)
}
override suspend fun storeIfAbsent(value: AppsFlyerConversionData) {
Timber.i("Storing conversion data to store if absent: $value")
val dto = appPreferencesStore.getObjectSyncOrNull<ConversionDataDTO>(KEY)
if (dto == null) {
Timber.i("Conversion data is absent, storing $value")
appPreferencesStore.storeObject(KEY, ConversionDataConverter.convert(value))
}
}
private companion object {
val KEY = stringPreferencesKey("APPS_FLYER_CONVERSION_DATA")
}
}
@JsonClass(generateAdapter = true)
internal data class ConversionDataDTO(
@Json(name = "refcode") val refcode: String,
@Json(name = "campaign") val campaign: String?,
)

View file

@ -30,7 +30,11 @@ class WalletIdBodyConverterTest {
)
// WHEN
val result = WalletIdBodyConverter.convert(userWallet, publicKeys)
val result = WalletIdBodyConverter.convert(
userWallet = userWallet,
conversionData = null,
publicKeys = publicKeys,
)
// THEN
assertThat(result).isEqualTo(
@ -68,7 +72,11 @@ class WalletIdBodyConverterTest {
val publicKeys = emptyMap<String, String>()
// WHEN
val result = WalletIdBodyConverter.convert(userWallet, publicKeys)
val result = WalletIdBodyConverter.convert(
userWallet = userWallet,
conversionData = null,
publicKeys = publicKeys,
)
// THEN
assertThat(result).isEqualTo(