Updated on 2026-08-14
This commit is contained in:
parent
84af4950a1
commit
ff2559df3e
27 changed files with 904 additions and 20 deletions
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.features.tangempay.components
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
|
||||
interface TangemPayTransactionBottomSheetComponent : ComposableBottomSheetComponent {
|
||||
|
||||
data class Params(
|
||||
val isBalanceHidden: Boolean,
|
||||
val transaction: TangemPayTxHistoryItem,
|
||||
val userWalletId: UserWalletId,
|
||||
val customerId: String,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, TangemPayTransactionBottomSheetComponent>
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ internal class TangemPayDetailsComponent(
|
|||
)
|
||||
is TangemPayDetailsNavigation.TransactionDetails -> TangemPayTxHistoryDetailsComponent(
|
||||
appComponentContext = context,
|
||||
params = TangemPayTxHistoryDetailsComponent.Params(
|
||||
params = TangemPayTransactionBottomSheetComponent.Params(
|
||||
transaction = navigation.transaction,
|
||||
isBalanceHidden = navigation.isBalanceHidden,
|
||||
userWalletId = params.initialStatus.userWalletId,
|
||||
|
|
|
|||
|
|
@ -5,16 +5,17 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.features.tangempay.components.TangemPayTransactionBottomSheetComponent
|
||||
import com.tangem.features.tangempay.model.TangemPayTxHistoryDetailsModel
|
||||
import com.tangem.features.tangempay.ui.TangemPayTxHistoryDetailsContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class TangemPayTxHistoryDetailsComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
params: Params,
|
||||
) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext {
|
||||
internal class TangemPayTxHistoryDetailsComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: TangemPayTransactionBottomSheetComponent.Params,
|
||||
) : TangemPayTransactionBottomSheetComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: TangemPayTxHistoryDetailsModel = getOrCreateModel(params = params)
|
||||
|
||||
|
|
@ -28,11 +29,11 @@ internal class TangemPayTxHistoryDetailsComponent(
|
|||
TangemPayTxHistoryDetailsContent(state = state)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val transaction: TangemPayTxHistoryItem,
|
||||
val isBalanceHidden: Boolean,
|
||||
val userWalletId: UserWalletId,
|
||||
val customerId: String,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
@AssistedFactory
|
||||
interface Factory : TangemPayTransactionBottomSheetComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: TangemPayTransactionBottomSheetComponent.Params,
|
||||
): TangemPayTxHistoryDetailsComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@ package com.tangem.features.tangempay.di
|
|||
|
||||
import com.tangem.features.tangempay.components.DefaultTangemPayDetailsContainerComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayDetailsContainerComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayTransactionBottomSheetComponent
|
||||
import com.tangem.features.tangempay.components.txHistory.TangemPayTxHistoryDetailsComponent
|
||||
import com.tangem.features.tangempay.model.listener.CardDetailsEventListener
|
||||
import com.tangem.features.tangempay.model.listener.DefaultCardDetailsEventListener
|
||||
import dagger.Binds
|
||||
|
|
@ -23,4 +25,10 @@ internal interface TangemPayDetailsFeatureModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindCardDetailsEventListener(impl: DefaultCardDetailsEventListener): CardDetailsEventListener
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindTangemPayTransactionBottomSheetComponentFactory(
|
||||
factory: TangemPayTxHistoryDetailsComponent.Factory,
|
||||
): TangemPayTransactionBottomSheetComponent.Factory
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
|
|||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||
import com.tangem.features.tangempay.components.txHistory.TangemPayTxHistoryDetailsComponent
|
||||
import com.tangem.features.tangempay.components.TangemPayTransactionBottomSheetComponent
|
||||
import com.tangem.features.tangempay.entity.TangemPayTxHistoryDetailsUM
|
||||
import com.tangem.features.tangempay.model.transformers.TangemPayTxHistoryDetailsConverter
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -34,7 +34,7 @@ internal class TangemPayTxHistoryDetailsModel @Inject constructor(
|
|||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<TangemPayTxHistoryDetailsComponent.Params>()
|
||||
private val params = paramsContainer.require<TangemPayTransactionBottomSheetComponent.Params>()
|
||||
val uiState: StateFlow<TangemPayTxHistoryDetailsUM>
|
||||
field = MutableStateFlow(
|
||||
value = TangemPayTxHistoryDetailsConverter.convert(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.tangempay.deeplink
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
interface TangemPayMainDeepLinkHandler {
|
||||
|
||||
interface Factory {
|
||||
fun create(scope: CoroutineScope, payload: Map<String, String>): TangemPayMainDeepLinkHandler
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package com.tangem.features.tangempay.deeplink
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.CUSTOMER_ID_KEY
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.CUSTOMER_WALLET_ID_KEY
|
||||
import com.tangem.common.routing.deeplink.DeeplinkConst.TYPE_KEY
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
||||
import com.tangem.domain.pay.flow.PaymentAccountStatusSupplier
|
||||
import com.tangem.domain.visa.model.TangemPayPushNotificationType
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.domain.wallets.usecase.SelectWalletUseCase
|
||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionTrigger
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultTangemPayMainDeepLinkHandler @AssistedInject constructor(
|
||||
@Assisted private val scope: CoroutineScope,
|
||||
@Assisted private val payload: Map<String, String>,
|
||||
private val appRouter: AppRouter,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
private val selectWalletUseCase: SelectWalletUseCase,
|
||||
private val walletDeepLinkActionTrigger: WalletDeepLinkActionTrigger,
|
||||
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
|
||||
private val paymentAccountSupplier: PaymentAccountStatusSupplier,
|
||||
) : TangemPayMainDeepLinkHandler {
|
||||
|
||||
init {
|
||||
handleDeepLink()
|
||||
}
|
||||
|
||||
private fun handleDeepLink() {
|
||||
val walletId = payload[CUSTOMER_WALLET_ID_KEY]
|
||||
|
||||
scope.launch {
|
||||
val userWalletId = walletId?.let(::UserWalletId) ?: run {
|
||||
appRouter.popTo(AppRoute.Wallet)
|
||||
return@launch
|
||||
}
|
||||
val userWallet = getUserWalletUseCase(userWalletId).getOrNull()
|
||||
if (userWallet == null || userWallet.isLocked) {
|
||||
appRouter.popTo(AppRoute.Wallet)
|
||||
return@launch
|
||||
}
|
||||
if (selectWalletUseCase(userWalletId).getOrNull() == null) {
|
||||
appRouter.popTo(AppRoute.Wallet)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val pushAction = buildPushAction()
|
||||
|
||||
appRouter.popTo(
|
||||
route = AppRoute.Wallet,
|
||||
onComplete = {
|
||||
walletDeepLinkActionTrigger.selectWallet(userWalletId)
|
||||
when (pushAction) {
|
||||
is TangemPayPushAction.CardReady,
|
||||
is TangemPayPushAction.TopUp,
|
||||
-> navigateToTangemPayDetails(userWalletId)
|
||||
is TangemPayPushAction.TransactionSpend -> {
|
||||
walletDeepLinkActionTrigger.showTangemPayTransaction(
|
||||
transaction = pushAction.transaction,
|
||||
customerId = pushAction.customerId,
|
||||
)
|
||||
}
|
||||
is TangemPayPushAction.CollateralTransaction -> {
|
||||
walletDeepLinkActionTrigger.showTangemPayTransaction(
|
||||
transaction = pushAction.transaction,
|
||||
customerId = pushAction.customerId,
|
||||
)
|
||||
}
|
||||
null -> Unit
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildPushAction(): TangemPayPushAction? {
|
||||
val type = payload[TYPE_KEY]?.let(TangemPayPushNotificationType::fromValue) ?: return null
|
||||
val customerId = payload[CUSTOMER_ID_KEY].orEmpty()
|
||||
|
||||
return when (type) {
|
||||
TangemPayPushNotificationType.CARD_READY -> TangemPayPushAction.CardReady
|
||||
TangemPayPushNotificationType.TRANSACTION_SPEND -> {
|
||||
val transaction = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
if (transaction != null) TangemPayPushAction.TransactionSpend(transaction, customerId) else null
|
||||
}
|
||||
TangemPayPushNotificationType.TOP_UP -> TangemPayPushAction.TopUp
|
||||
TangemPayPushNotificationType.COLLATERAL -> {
|
||||
val transaction = TangemPayPushPayloadToTxHistoryItemConverter.convertCollateral(payload)
|
||||
if (transaction != null) TangemPayPushAction.CollateralTransaction(transaction, customerId) else null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToTangemPayDetails(walletId: UserWalletId) {
|
||||
scope.launch {
|
||||
paymentAccountStatusFetcher.invoke(PaymentAccountStatusFetcher.Params(walletId))
|
||||
val paymentAccountStatus = paymentAccountSupplier.invoke(userWalletId = walletId)
|
||||
.firstOrNull()
|
||||
?: return@launch
|
||||
appRouter.push(route = AppRoute.TangemPayDetails(status = paymentAccountStatus))
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : TangemPayMainDeepLinkHandler.Factory {
|
||||
override fun create(scope: CoroutineScope, payload: Map<String, String>): DefaultTangemPayMainDeepLinkHandler
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.tangempay.deeplink
|
||||
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
|
||||
internal sealed class TangemPayPushAction {
|
||||
|
||||
data object CardReady : TangemPayPushAction()
|
||||
|
||||
data class TransactionSpend(
|
||||
val transaction: TangemPayTxHistoryItem,
|
||||
val customerId: String,
|
||||
) : TangemPayPushAction()
|
||||
|
||||
data object TopUp : TangemPayPushAction()
|
||||
|
||||
data class CollateralTransaction(
|
||||
val transaction: TangemPayTxHistoryItem,
|
||||
val customerId: String,
|
||||
) : TangemPayPushAction()
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package com.tangem.features.tangempay.deeplink
|
||||
|
||||
import com.tangem.domain.pay.utils.TangemPayTxHistoryItemStatusConverter
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import org.joda.time.DateTime
|
||||
import org.joda.time.DateTimeZone
|
||||
import java.math.BigDecimal
|
||||
import java.util.Currency
|
||||
|
||||
object TangemPayPushPayloadToTxHistoryItemConverter {
|
||||
|
||||
private const val KEY_ID = "transaction_id"
|
||||
private const val KEY_AMOUNT = "amount"
|
||||
private const val KEY_CURRENCY = "currency"
|
||||
private const val KEY_LOCAL_AMOUNT = "local_amount"
|
||||
private const val KEY_LOCAL_CURRENCY = "local_currency"
|
||||
private const val KEY_AUTHORIZED_AMOUNT = "authorized_amount"
|
||||
private const val KEY_MERCHANT_NAME = "merchant_name"
|
||||
private const val KEY_ENRICHED_MERCHANT_NAME = "enriched_merchant_name"
|
||||
private const val KEY_ENRICHED_MERCHANT_ICON = "enriched_merchant_icon"
|
||||
private const val KEY_ENRICHED_MERCHANT_CATEGORY = "enriched_merchant_category"
|
||||
private const val KEY_MERCHANT_CATEGORY = "merchant_category"
|
||||
private const val KEY_MERCHANT_CATEGORY_CODE = "merchant_category_code"
|
||||
private const val KEY_STATUS = "status"
|
||||
private const val KEY_DECLINED_REASON = "declined_reason"
|
||||
private const val KEY_AUTHORIZED_AT = "authorized_at"
|
||||
private const val KEY_POSTED_AT = "posted_at"
|
||||
private const val KEY_TRANSACTION_HASH = "transaction_hash"
|
||||
|
||||
@Suppress("ComplexCondition")
|
||||
fun convertSpend(payload: Map<String, String>): TangemPayTxHistoryItem.Spend? {
|
||||
val id = payload[KEY_ID]?.ifEmpty { null } ?: return null
|
||||
val amount = payload[KEY_AMOUNT]?.toBigDecimalOrNull() ?: return null
|
||||
val currency = payload[KEY_CURRENCY]?.let(::parseCurrency) ?: return null
|
||||
val merchantName = payload[KEY_MERCHANT_NAME] ?: payload[KEY_ENRICHED_MERCHANT_NAME] ?: return null
|
||||
val status = payload[KEY_STATUS]?.ifEmpty { null } ?: return null
|
||||
val authorizedAt = payload[KEY_AUTHORIZED_AT]?.let(::parseDateTime) ?: return null
|
||||
|
||||
return TangemPayTxHistoryItem.Spend(
|
||||
id = id,
|
||||
jsonRepresentation = payload.toString(),
|
||||
date = authorizedAt.withZone(DateTimeZone.getDefault()),
|
||||
amount = amount,
|
||||
currency = currency,
|
||||
authorizedAmount = payload[KEY_AUTHORIZED_AMOUNT]?.toBigDecimalOrNull().orZero(),
|
||||
localAmount = payload[KEY_LOCAL_AMOUNT]?.toBigDecimalOrNull(),
|
||||
localCurrency = payload[KEY_LOCAL_CURRENCY]?.let(::parseCurrency),
|
||||
enrichedMerchantName = payload[KEY_ENRICHED_MERCHANT_NAME],
|
||||
merchantName = merchantName,
|
||||
enrichedMerchantCategory = payload[KEY_ENRICHED_MERCHANT_CATEGORY],
|
||||
merchantCategoryCode = payload[KEY_MERCHANT_CATEGORY_CODE],
|
||||
merchantCategory = payload[KEY_MERCHANT_CATEGORY],
|
||||
status = TangemPayTxHistoryItemStatusConverter.convert(status),
|
||||
enrichedMerchantIconUrl = payload[KEY_ENRICHED_MERCHANT_ICON],
|
||||
declinedReason = payload[KEY_DECLINED_REASON],
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("ComplexCondition")
|
||||
fun convertCollateral(payload: Map<String, String>): TangemPayTxHistoryItem.Collateral? {
|
||||
val id = payload[KEY_ID]?.ifEmpty { null } ?: return null
|
||||
val amount = payload[KEY_AMOUNT]?.toBigDecimalOrNull() ?: return null
|
||||
val transactionHash = payload[KEY_TRANSACTION_HASH]?.ifEmpty { null } ?: return null
|
||||
val postedAt = payload[KEY_POSTED_AT]?.let(::parseDateTime) ?: return null
|
||||
val currency = payload[KEY_CURRENCY]?.let(::parseCurrency) ?: return null
|
||||
|
||||
return TangemPayTxHistoryItem.Collateral(
|
||||
id = id,
|
||||
jsonRepresentation = payload.toString(),
|
||||
date = postedAt.withZone(DateTimeZone.getDefault()),
|
||||
currency = currency,
|
||||
amount = amount,
|
||||
transactionHash = transactionHash,
|
||||
type = if (amount >= BigDecimal.ZERO) {
|
||||
TangemPayTxHistoryItem.Type.Deposit
|
||||
} else {
|
||||
TangemPayTxHistoryItem.Type.Withdrawal
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseCurrency(code: String): Currency? = runCatching {
|
||||
return Currency.getInstance(code.uppercase())
|
||||
}.getOrNull()
|
||||
|
||||
private fun parseDateTime(value: String): DateTime? = runCatching {
|
||||
return DateTime.parse(value).withZone(DateTimeZone.getDefault())
|
||||
}.getOrNull()
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.features.tangempay.di
|
||||
|
||||
import com.tangem.features.tangempay.deeplink.DefaultOnboardVisaDeepLinkHandler
|
||||
import com.tangem.features.tangempay.deeplink.DefaultTangemPayMainDeepLinkHandler
|
||||
import com.tangem.features.tangempay.deeplink.OnboardVisaDeepLinkHandler
|
||||
import com.tangem.features.tangempay.deeplink.TangemPayMainDeepLinkHandler
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -15,4 +17,10 @@ internal interface TangemPayDeeplinkModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindDeepLinkHandlerFactory(impl: DefaultOnboardVisaDeepLinkHandler.Factory): OnboardVisaDeepLinkHandler.Factory
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindTangemPayMainDeepLinkHandlerFactory(
|
||||
impl: DefaultTangemPayMainDeepLinkHandler.Factory,
|
||||
): TangemPayMainDeepLinkHandler.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,400 @@
|
|||
package com.tangem.features.tangempay.deeplink
|
||||
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class TangemPayPushPayloadToTxHistoryItemConverterTest {
|
||||
|
||||
@Test
|
||||
fun `convertSpend returns Spend tx with all fields when payload is complete`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "txn-123",
|
||||
"amount" to "5.24",
|
||||
"currency" to "usd",
|
||||
"local_amount" to "5.24",
|
||||
"local_currency" to "usd",
|
||||
"authorized_amount" to "5.24",
|
||||
"merchant_name" to "PLAYSTATION NETWORK",
|
||||
"enriched_merchant_name" to "Playstation",
|
||||
"enriched_merchant_icon" to "https://example.com/icon.png",
|
||||
"enriched_merchant_category" to "Gaming",
|
||||
"merchant_category" to "Digital Goods",
|
||||
"merchant_category_code" to "5818",
|
||||
"status" to "completed",
|
||||
"declined_reason" to "",
|
||||
"authorized_at" to "2025-10-24T10:32:24.496Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
|
||||
assertThat(result).isNotNull()
|
||||
assertThat(result!!.id).isEqualTo("txn-123")
|
||||
assertThat(result.amount).isEqualTo(BigDecimal("5.24"))
|
||||
assertThat(result.currency.currencyCode).isEqualTo("USD")
|
||||
assertThat(result.localAmount).isEqualTo(BigDecimal("5.24"))
|
||||
assertThat(result.localCurrency?.currencyCode).isEqualTo("USD")
|
||||
assertThat(result.authorizedAmount).isEqualTo(BigDecimal("5.24"))
|
||||
assertThat(result.merchantName).isEqualTo("PLAYSTATION NETWORK")
|
||||
assertThat(result.enrichedMerchantName).isEqualTo("Playstation")
|
||||
assertThat(result.enrichedMerchantIconUrl).isEqualTo("https://example.com/icon.png")
|
||||
assertThat(result.enrichedMerchantCategory).isEqualTo("Gaming")
|
||||
assertThat(result.merchantCategory).isEqualTo("Digital Goods")
|
||||
assertThat(result.merchantCategoryCode).isEqualTo("5818")
|
||||
assertThat(result.status).isEqualTo(TangemPayTxHistoryItem.Status.COMPLETED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend returns null when transaction_id is missing`() {
|
||||
val payload = mapOf(
|
||||
"amount" to "5.24",
|
||||
"currency" to "usd",
|
||||
"merchant_name" to "Test",
|
||||
"status" to "completed",
|
||||
"authorized_at" to "2025-10-24T10:32:24.496Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend returns null when amount is missing`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "txn-123",
|
||||
"currency" to "usd",
|
||||
"merchant_name" to "Test",
|
||||
"status" to "completed",
|
||||
"authorized_at" to "2025-10-24T10:32:24.496Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend returns null when currency is missing`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "txn-123",
|
||||
"amount" to "5.24",
|
||||
"merchant_name" to "Test",
|
||||
"status" to "completed",
|
||||
"authorized_at" to "2025-10-24T10:32:24.496Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend returns null when merchant_name is missing`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "txn-123",
|
||||
"amount" to "5.24",
|
||||
"currency" to "usd",
|
||||
"status" to "completed",
|
||||
"authorized_at" to "2025-10-24T10:32:24.496Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend falls back to enriched_merchant_name when merchant_name is absent`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "txn-123",
|
||||
"amount" to "5.24",
|
||||
"currency" to "usd",
|
||||
"enriched_merchant_name" to "Playstation",
|
||||
"status" to "completed",
|
||||
"authorized_at" to "2025-10-24T10:32:24.496Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
|
||||
assertThat(result).isNotNull()
|
||||
assertThat(result!!.merchantName).isEqualTo("Playstation")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend returns null when status is missing`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "txn-123",
|
||||
"amount" to "5.24",
|
||||
"currency" to "usd",
|
||||
"merchant_name" to "Test",
|
||||
"authorized_at" to "2025-10-24T10:32:24.496Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend returns null when authorized_at is missing`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "txn-123",
|
||||
"amount" to "5.24",
|
||||
"currency" to "usd",
|
||||
"merchant_name" to "Test",
|
||||
"status" to "completed",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend returns null when amount is not a number`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "txn-123",
|
||||
"amount" to "abc",
|
||||
"currency" to "usd",
|
||||
"merchant_name" to "Test",
|
||||
"status" to "completed",
|
||||
"authorized_at" to "2025-10-24T10:32:24.496Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend returns null when currency is invalid`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "txn-123",
|
||||
"amount" to "5.24",
|
||||
"currency" to "INVALID",
|
||||
"merchant_name" to "Test",
|
||||
"status" to "completed",
|
||||
"authorized_at" to "2025-10-24T10:32:24.496Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend maps all statuses correctly`() {
|
||||
fun spendPayloadWithStatus(status: String) = mapOf(
|
||||
"transaction_id" to "txn-123",
|
||||
"amount" to "1.00",
|
||||
"currency" to "usd",
|
||||
"merchant_name" to "Test",
|
||||
"status" to status,
|
||||
"authorized_at" to "2025-10-24T10:32:24.496Z",
|
||||
)
|
||||
|
||||
assertThat(TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(spendPayloadWithStatus("pending"))!!.status)
|
||||
.isEqualTo(TangemPayTxHistoryItem.Status.PENDING)
|
||||
assertThat(TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(spendPayloadWithStatus("reserved"))!!.status)
|
||||
.isEqualTo(TangemPayTxHistoryItem.Status.RESERVED)
|
||||
assertThat(TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(spendPayloadWithStatus("completed"))!!.status)
|
||||
.isEqualTo(TangemPayTxHistoryItem.Status.COMPLETED)
|
||||
assertThat(TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(spendPayloadWithStatus("declined"))!!.status)
|
||||
.isEqualTo(TangemPayTxHistoryItem.Status.DECLINED)
|
||||
assertThat(TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(spendPayloadWithStatus("reversed"))!!.status)
|
||||
.isEqualTo(TangemPayTxHistoryItem.Status.REVERSED)
|
||||
assertThat(TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(spendPayloadWithStatus("unknown_value"))!!.status)
|
||||
.isEqualTo(TangemPayTxHistoryItem.Status.UNKNOWN)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend returns null when transaction_id is empty`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "",
|
||||
"amount" to "5.24",
|
||||
"currency" to "usd",
|
||||
"merchant_name" to "Test",
|
||||
"status" to "completed",
|
||||
"authorized_at" to "2025-10-24T10:32:24.496Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertCollateral returns Collateral with all fields when payload is complete`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "col-123",
|
||||
"amount" to "50.00",
|
||||
"currency" to "usd",
|
||||
"transaction_hash" to "0xabc123",
|
||||
"posted_at" to "2025-10-25T19:22:22.597Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertCollateral(payload)
|
||||
|
||||
assertThat(result).isNotNull()
|
||||
assertThat(result!!.id).isEqualTo("col-123")
|
||||
assertThat(result.amount).isEqualTo(BigDecimal("50.00"))
|
||||
assertThat(result.currency.currencyCode).isEqualTo("USD")
|
||||
assertThat(result.transactionHash).isEqualTo("0xabc123")
|
||||
assertThat(result.type).isEqualTo(TangemPayTxHistoryItem.Type.Deposit)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertCollateral returns Withdrawal type for negative amount`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "col-456",
|
||||
"amount" to "-10.00",
|
||||
"currency" to "usd",
|
||||
"transaction_hash" to "0xdef789",
|
||||
"posted_at" to "2025-10-25T19:22:22.597Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertCollateral(payload)
|
||||
|
||||
assertThat(result).isNotNull()
|
||||
assertThat(result!!.type).isEqualTo(TangemPayTxHistoryItem.Type.Withdrawal)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertCollateral returns null when transaction_id is missing`() {
|
||||
val payload = mapOf(
|
||||
"amount" to "50.00",
|
||||
"currency" to "usd",
|
||||
"transaction_hash" to "0xabc123",
|
||||
"posted_at" to "2025-10-25T19:22:22.597Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertCollateral(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertCollateral returns null when amount is missing`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "col-123",
|
||||
"currency" to "usd",
|
||||
"transaction_hash" to "0xabc123",
|
||||
"posted_at" to "2025-10-25T19:22:22.597Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertCollateral(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertCollateral returns null when transaction_hash is missing`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "col-123",
|
||||
"amount" to "50.00",
|
||||
"currency" to "usd",
|
||||
"posted_at" to "2025-10-25T19:22:22.597Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertCollateral(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertCollateral returns null when posted_at is missing`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "col-123",
|
||||
"amount" to "50.00",
|
||||
"currency" to "usd",
|
||||
"transaction_hash" to "0xabc123",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertCollateral(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertCollateral returns null when currency is missing`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "col-123",
|
||||
"amount" to "50.00",
|
||||
"transaction_hash" to "0xabc123",
|
||||
"posted_at" to "2025-10-25T19:22:22.597Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertCollateral(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertCollateral returns null when transaction_hash is empty`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "col-123",
|
||||
"amount" to "50.00",
|
||||
"currency" to "usd",
|
||||
"transaction_hash" to "",
|
||||
"posted_at" to "2025-10-25T19:22:22.597Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertCollateral(payload)
|
||||
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend handles optional fields as null`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "txn-123",
|
||||
"amount" to "5.24",
|
||||
"currency" to "usd",
|
||||
"merchant_name" to "Test",
|
||||
"status" to "completed",
|
||||
"authorized_at" to "2025-10-24T10:32:24.496Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(payload)
|
||||
|
||||
assertThat(result).isNotNull()
|
||||
assertThat(result!!.localAmount).isNull()
|
||||
assertThat(result.localCurrency).isNull()
|
||||
assertThat(result.enrichedMerchantName).isNull()
|
||||
assertThat(result.enrichedMerchantIconUrl).isNull()
|
||||
assertThat(result.enrichedMerchantCategory).isNull()
|
||||
assertThat(result.merchantCategory).isNull()
|
||||
assertThat(result.merchantCategoryCode).isNull()
|
||||
assertThat(result.declinedReason).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertCollateral returns Deposit type for zero amount`() {
|
||||
val payload = mapOf(
|
||||
"transaction_id" to "col-789",
|
||||
"amount" to "0",
|
||||
"currency" to "usd",
|
||||
"transaction_hash" to "0xdef",
|
||||
"posted_at" to "2025-10-25T19:22:22.597Z",
|
||||
)
|
||||
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertCollateral(payload)
|
||||
|
||||
assertThat(result).isNotNull()
|
||||
assertThat(result!!.type).isEqualTo(TangemPayTxHistoryItem.Type.Deposit)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertSpend returns null for empty payload`() {
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertSpend(emptyMap())
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `convertCollateral returns null for empty payload`() {
|
||||
val result = TangemPayPushPayloadToTxHistoryItemConverter.convertCollateral(emptyMap())
|
||||
assertThat(result).isNull()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue