Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-13 13:19:30 +05:00
parent 76cfb2e462
commit 5ca696d677
17 changed files with 431 additions and 0 deletions

View file

@ -301,6 +301,11 @@ internal interface TangemPayDataModule {
return GetCustomerOffersUseCase(customerOffersRepository)
}
@Provides
fun provideGetCashbackSummaryUseCase(cashbackRepository: CashbackRepository): GetCashbackSummaryUseCase {
return GetCashbackSummaryUseCase(cashbackRepository)
}
@Provides
fun provideCheckOrderConflictUseCase(
customerOrderRepository: CustomerOrderRepository,

View file

@ -0,0 +1,22 @@
package com.tangem.data.pay.repository
import arrow.core.Either
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.CashbackSummary
import com.tangem.domain.pay.repository.CashbackRepository
import com.tangem.domain.visa.error.VisaApiError
import javax.inject.Inject
internal class DefaultCashbackRepository @Inject constructor(
private val tangemPayApi: TangemPayApi,
private val requestHelper: TangemPayRequestPerformer,
) : CashbackRepository {
override suspend fun getCashbackSummary(userWalletId: UserWalletId): Either<VisaApiError, CashbackSummary> {
return requestHelper.performRequest(userWalletId) { authHeader ->
tangemPayApi.getCashbackSummary(authHeader = authHeader)
}.map(CashbackSummaryConverter::convert)
}
}

View file

@ -0,0 +1,44 @@
package com.tangem.data.pay.util
import com.tangem.datasource.api.pay.models.response.CashbackSummaryResponse
import com.tangem.domain.pay.model.CashbackDisplayMode
import com.tangem.domain.pay.model.CashbackProgramStatus
import com.tangem.domain.pay.model.CashbackSummary
import com.tangem.domain.pay.model.TangemPayCashback
import com.tangem.utils.converter.Converter
import org.joda.time.DateTime
import java.math.BigDecimal
/** Maps [CashbackSummaryResponse] (BFF) to the domain [CashbackSummary]. */
internal object CashbackSummaryConverter : Converter<CashbackSummaryResponse, CashbackSummary> {
override fun convert(value: CashbackSummaryResponse): CashbackSummary {
return when (CashbackProgramStatus.fromString(value.cashbackProgramStatus)) {
CashbackProgramStatus.ENABLED -> toEnabled(value)
CashbackProgramStatus.DEACTIVATED -> CashbackSummary.Deactivated
CashbackProgramStatus.DISABLED -> CashbackSummary.Disabled
CashbackProgramStatus.UNKNOWN -> CashbackSummary.Unknown
}
}
private fun toEnabled(value: CashbackSummaryResponse): CashbackSummary {
val period = value.period ?: return CashbackSummary.Unknown
val payoutStart = period.payoutStartDate?.let(DateTime::parse) ?: return CashbackSummary.Unknown
val payoutEnd = period.payoutEndDate?.let(DateTime::parse) ?: return CashbackSummary.Unknown
return CashbackSummary.Enabled(
displayMode = CashbackDisplayMode.fromString(value.cashbackDisplayMode),
cashback = TangemPayCashback(
confirmedAmount = value.confirmedAmount ?: BigDecimal.ZERO,
currency = value.currency.orEmpty(),
period = TangemPayCashback.Period(
year = period.year,
month = period.month,
payoutStart = payoutStart,
payoutEnd = payoutEnd,
),
),
pendingAmount = value.pendingAmount ?: BigDecimal.ZERO,
)
}
}

View file

@ -1,7 +1,9 @@
package com.tangem.data.pay.di
import com.tangem.data.pay.repository.MockAwareCashbackRepository
import com.tangem.data.pay.repository.MockAwareOnboardingRepository
import com.tangem.data.pay.repository.MockAwareTangemPayCardDetailsRepository
import com.tangem.domain.pay.repository.CashbackRepository
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import dagger.Binds
@ -21,4 +23,8 @@ internal interface TangemPayDataMockedModule {
@Binds
@Singleton
fun bindCardDetailsRepository(repository: MockAwareTangemPayCardDetailsRepository): TangemPayCardDetailsRepository
@Binds
@Singleton
fun bindCashbackRepository(repository: MockAwareCashbackRepository): CashbackRepository
}

View file

@ -0,0 +1,52 @@
package com.tangem.data.pay.repository
import arrow.core.Either
import arrow.core.right
import com.tangem.datasource.api.common.config.ApiConfig
import com.tangem.datasource.api.common.config.ApiEnvironment
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.CashbackSummary
import com.tangem.domain.pay.model.TangemPayCashback
import com.tangem.domain.pay.repository.CashbackRepository
import com.tangem.domain.visa.error.VisaApiError
import org.joda.time.DateTime
import java.math.BigDecimal
import javax.inject.Inject
import javax.inject.Singleton
/** In MOCK env returns a canned cashback summary; otherwise delegates to [DefaultCashbackRepository]. */
@Singleton
internal class MockAwareCashbackRepository @Inject constructor(
private val real: DefaultCashbackRepository,
private val apiConfigsManager: ApiConfigsManager,
) : CashbackRepository {
private val isMockMode: Boolean
get() = apiConfigsManager
.getEnvironmentConfig(ApiConfig.ID.TangemPay)
.environment == ApiEnvironment.MOCK
override suspend fun getCashbackSummary(userWalletId: UserWalletId): Either<VisaApiError, CashbackSummary> {
if (isMockMode) return MOCK_SUMMARY.right()
return real.getCashbackSummary(userWalletId)
}
private companion object {
val MOCK_SUMMARY = CashbackSummary.Enabled(
displayMode = CashbackDisplayMode.FULL,
cashback = TangemPayCashback(
confirmedAmount = BigDecimal("22.54"),
currency = "USD",
period = TangemPayCashback.Period(
year = 2026,
month = 6,
payoutStart = DateTime.parse("2026-07-02"),
payoutEnd = DateTime.parse("2026-07-05"),
),
),
pendingAmount = BigDecimal("13.65"),
)
}
}

View file

@ -1,7 +1,9 @@
package com.tangem.data.pay.di
import com.tangem.data.pay.repository.DefaultCashbackRepository
import com.tangem.data.pay.repository.DefaultOnboardingRepository
import com.tangem.data.pay.repository.DefaultTangemPayCardDetailsRepository
import com.tangem.domain.pay.repository.CashbackRepository
import com.tangem.domain.pay.repository.OnboardingRepository
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
import dagger.Binds
@ -21,4 +23,8 @@ internal interface TangemPayDataProductionModule {
@Binds
@Singleton
fun bindCardDetailsRepository(repository: DefaultTangemPayCardDetailsRepository): TangemPayCardDetailsRepository
@Binds
@Singleton
fun bindCashbackRepository(repository: DefaultCashbackRepository): CashbackRepository
}

View file

@ -0,0 +1,139 @@
package com.tangem.data.pay.util
import com.google.common.truth.Truth.assertThat
import com.tangem.datasource.api.pay.models.response.CashbackSummaryResponse
import com.tangem.domain.pay.model.CashbackDisplayMode
import com.tangem.domain.pay.model.CashbackSummary
import com.tangem.domain.pay.model.TangemPayCashback
import org.joda.time.DateTime
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 CashbackSummaryConverterTest {
@ParameterizedTest
@MethodSource("provideTestModels")
fun convert(model: ConvertModel) {
// Act
val actual = CashbackSummaryConverter.convert(model.response)
// Assert
assertThat(actual).isEqualTo(model.expected)
}
@Suppress("LongMethod")
private fun provideTestModels() = listOf(
ConvertModel(
name = "enabled + full -> Enabled(FULL)",
response = createResponse(status = "enabled", displayMode = "full"),
expected = CashbackSummary.Enabled(
displayMode = CashbackDisplayMode.FULL,
cashback = expectedCashback(),
pendingAmount = BigDecimal("13.65"),
),
),
ConvertModel(
name = "enabled + alt_block -> Enabled(ALT_BLOCK)",
response = createResponse(status = "enabled", displayMode = "alt_block"),
expected = CashbackSummary.Enabled(
displayMode = CashbackDisplayMode.ALT_BLOCK,
cashback = expectedCashback(),
pendingAmount = BigDecimal("13.65"),
),
),
ConvertModel(
name = "enabled + missing display mode -> Enabled(FULL)",
response = createResponse(status = "enabled", displayMode = null),
expected = CashbackSummary.Enabled(
displayMode = CashbackDisplayMode.FULL,
cashback = expectedCashback(),
pendingAmount = BigDecimal("13.65"),
),
),
ConvertModel(
name = "enabled + zero amounts -> Enabled with zeros",
response = createResponse(
status = "enabled",
displayMode = "full",
confirmedAmount = null,
pendingAmount = null,
),
expected = CashbackSummary.Enabled(
displayMode = CashbackDisplayMode.FULL,
cashback = expectedCashback(confirmedAmount = BigDecimal.ZERO),
pendingAmount = BigDecimal.ZERO,
),
),
ConvertModel(
name = "enabled but missing period -> Unknown",
response = createResponse(status = "enabled", period = null),
expected = CashbackSummary.Unknown,
),
ConvertModel(
name = "deactivated -> Deactivated",
response = createResponse(status = "deactivated"),
expected = CashbackSummary.Deactivated,
),
ConvertModel(
name = "disabled -> Disabled",
response = createResponse(status = "disabled"),
expected = CashbackSummary.Disabled,
),
ConvertModel(
name = "unavailable -> Disabled",
response = createResponse(status = "unavailable"),
expected = CashbackSummary.Disabled,
),
ConvertModel(
name = "unrecognized status -> Unknown",
response = createResponse(status = "something_new"),
expected = CashbackSummary.Unknown,
),
)
internal data class ConvertModel(
val name: String,
val response: CashbackSummaryResponse,
val expected: CashbackSummary,
) {
override fun toString(): String = name
}
private companion object {
fun createResponse(
status: String = "enabled",
displayMode: String? = "full",
period: CashbackSummaryResponse.Period? = CashbackSummaryResponse.Period(
year = 2026,
month = 6,
payoutStartDate = "2026-07-02",
payoutEndDate = "2026-07-05",
),
confirmedAmount: BigDecimal? = BigDecimal("22.54"),
pendingAmount: BigDecimal? = BigDecimal("13.65"),
currency: String? = "USD",
) = CashbackSummaryResponse(
cashbackProgramStatus = status,
cashbackDisplayMode = displayMode,
period = period,
confirmedAmount = confirmedAmount,
pendingAmount = pendingAmount,
currency = currency,
)
fun expectedCashback(confirmedAmount: BigDecimal = BigDecimal("22.54")) = TangemPayCashback(
confirmedAmount = confirmedAmount,
currency = "USD",
period = TangemPayCashback.Period(
year = 2026,
month = 6,
payoutStart = DateTime.parse("2026-07-02"),
payoutEnd = DateTime.parse("2026-07-05"),
),
)
}
}