Updated on 2026-08-14
This commit is contained in:
parent
1f8386c05e
commit
0bfc7eee60
11 changed files with 112 additions and 9 deletions
|
|
@ -17,6 +17,12 @@ interface TangemPayApi {
|
|||
@Query("limit") limit: Int = TX_HISTORY_PAGING_DEFAULT_LIMIT,
|
||||
): ApiResponse<TangemPayTxHistoryResponse>
|
||||
|
||||
@GET("v1/customer/transactions/{transaction_id}")
|
||||
suspend fun getCustomerTransaction(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Path("transaction_id") transactionId: String,
|
||||
): ApiResponse<TangemPayTransactionResponse>
|
||||
|
||||
@GET("v1/customer/kyc")
|
||||
suspend fun getKycAccess(@Header("Authorization") authHeader: String): ApiResponse<KycAccessInfoResponse>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.pay.models.response
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/** Response of `GET v1/customer/transactions/{transaction_id}` — a single transaction by its id. */
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TangemPayTransactionResponse(
|
||||
@Json(name = "result") val result: TangemPayTxHistoryResponse.Transaction,
|
||||
)
|
||||
|
|
@ -44,6 +44,8 @@ data class TangemPayTxHistoryResponse(
|
|||
@Json(name = "enriched_merchant_category") val enrichedMerchantCategory: String? = null,
|
||||
@Json(name = "card_id") val cardId: String? = null,
|
||||
@Json(name = "card_type") val cardType: String? = null,
|
||||
@Json(name = "card_display_name") val cardDisplayName: String? = null,
|
||||
@Json(name = "card_number_end") val cardNumberEnd: String? = null,
|
||||
@Json(name = "status") val status: String,
|
||||
@Json(name = "declined_reason") val declinedReason: String? = null,
|
||||
@Json(name = "authorized_at") val authorizedAt: DateTime,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.data.pay.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.visa.utils.TangemPayTxHistoryItemConverter
|
||||
|
|
@ -11,6 +12,7 @@ import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchFlow
|
|||
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchingContext
|
||||
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListConfig
|
||||
import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.pagination.BatchFetchResult
|
||||
import com.tangem.pagination.BatchListSource
|
||||
|
|
@ -99,6 +101,17 @@ internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
|||
return "tangem_pay_tx_history_${userWalletId.stringValue}_${cursor ?: INITIAL_CURSOR}"
|
||||
}
|
||||
|
||||
override suspend fun getTransaction(
|
||||
userWalletId: UserWalletId,
|
||||
transactionId: String,
|
||||
): Either<VisaApiError, TangemPayTxHistoryItem?> {
|
||||
return requestPerformer.performRequest(userWalletId = userWalletId) { authHeader ->
|
||||
visaApi.getCustomerTransaction(authHeader = authHeader, transactionId = transactionId)
|
||||
}.map { response ->
|
||||
txHistoryItemConverter.convert(response.result)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetch(userWalletId: UserWalletId, cursor: String?, pageSize: Int) {
|
||||
requestPerformer.performRequest(userWalletId = userWalletId) { authHeader ->
|
||||
visaApi.getTangemPayTxHistory(authHeader = authHeader, limit = pageSize, cursor = cursor)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ internal class TangemPayTxHistoryItemConverter(moshi: Moshi) :
|
|||
status = TangemPayTxHistoryItemStatusConverter.convert(spend.status),
|
||||
enrichedMerchantIconUrl = spend.enrichedMerchantIcon,
|
||||
declinedReason = spend.declinedReason,
|
||||
cardName = spend.cardDisplayName,
|
||||
cardNumberLast4 = spend.cardNumberEnd,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ sealed class TangemPayTxHistoryItem {
|
|||
val status: Status,
|
||||
val enrichedMerchantIconUrl: String?,
|
||||
val declinedReason: String?,
|
||||
val cardName: String? = null,
|
||||
val cardNumberLast4: String? = null,
|
||||
) : TangemPayTxHistoryItem()
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package com.tangem.domain.tangempay.repository
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchFlow
|
||||
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchingContext
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
|
||||
interface TangemPayTxHistoryRepository {
|
||||
fun getTxHistoryBatchFlow(
|
||||
|
|
@ -10,4 +13,10 @@ interface TangemPayTxHistoryRepository {
|
|||
batchSize: Int,
|
||||
context: TangemPayTxHistoryListBatchingContext,
|
||||
): TangemPayTxHistoryListBatchFlow
|
||||
|
||||
/** Loads a single transaction via `GET v1/customer/transactions/{transactionId}`. */
|
||||
suspend fun getTransaction(
|
||||
userWalletId: UserWalletId,
|
||||
transactionId: String,
|
||||
): Either<VisaApiError, TangemPayTxHistoryItem?>
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@ internal data class TangemPayTxHistoryDetailsUMV2(
|
|||
val subtitle: TextReference,
|
||||
val iconState: TangemIconUM,
|
||||
val transactionTitle: TextReference,
|
||||
val card: TextReference?,
|
||||
val transactionCategory: TextReference,
|
||||
val mcc: TextReference?,
|
||||
val transactionAmount: String,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ 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.domain.tangempay.repository.TangemPayTxHistoryRepository
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.features.tangempay.components.TangemPayTransactionBottomSheetComponent
|
||||
import com.tangem.features.tangempay.entity.TangemPayTxHistoryDetailsUiStates
|
||||
import com.tangem.features.tangempay.model.transformers.TangemPayTxHistoryDetailsConverter
|
||||
|
|
@ -31,31 +33,51 @@ internal class TangemPayTxHistoryDetailsModel @Inject constructor(
|
|||
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val balanceHidingSettings: GetBalanceHidingSettingsUseCase,
|
||||
private val tangemPayTxHistoryRepository: TangemPayTxHistoryRepository,
|
||||
private val analytics: AnalyticsEventHandler,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<TangemPayTransactionBottomSheetComponent.Params>()
|
||||
val uiState: StateFlow<TangemPayTxHistoryDetailsUiStates>
|
||||
field = MutableStateFlow(buildUiStates(isBalanceHidden = params.isBalanceHidden))
|
||||
|
||||
private val transaction = MutableStateFlow(params.transaction)
|
||||
|
||||
val uiState: StateFlow<TangemPayTxHistoryDetailsUiStates> = combine(
|
||||
balanceHidingSettings.isBalanceHidden(),
|
||||
transaction,
|
||||
) { isBalanceHidden, transaction ->
|
||||
buildUiStates(isBalanceHidden, transaction)
|
||||
}.stateIn(
|
||||
scope = modelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = buildUiStates(isBalanceHidden = params.isBalanceHidden, transaction = params.transaction),
|
||||
)
|
||||
|
||||
init {
|
||||
subscribeToBalanceHiding()
|
||||
loadTransaction()
|
||||
}
|
||||
|
||||
fun dismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
|
||||
private fun subscribeToBalanceHiding() {
|
||||
balanceHidingSettings.isBalanceHidden()
|
||||
.onEach { isBalanceHidden -> uiState.update { buildUiStates(isBalanceHidden) } }
|
||||
.launchIn(modelScope)
|
||||
private fun loadTransaction() {
|
||||
val current = params.transaction
|
||||
if (current !is TangemPayTxHistoryItem.Spend) return
|
||||
modelScope.launch {
|
||||
tangemPayTxHistoryRepository.getTransaction(
|
||||
userWalletId = params.userWalletId,
|
||||
transactionId = current.id,
|
||||
).onRight { loaded -> if (loaded != null) transaction.value = loaded }
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildUiStates(isBalanceHidden: Boolean): TangemPayTxHistoryDetailsUiStates {
|
||||
private fun buildUiStates(
|
||||
isBalanceHidden: Boolean,
|
||||
transaction: TangemPayTxHistoryItem,
|
||||
): TangemPayTxHistoryDetailsUiStates {
|
||||
val converterInput = TangemPayTxHistoryDetailsConverter.Input(
|
||||
item = params.transaction,
|
||||
item = transaction,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
onExplorerClick = ::openExplorer,
|
||||
onDisputeClick = { dispute(customerId = params.customerId) },
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ internal object TangemPayTxHistoryDetailsConverterV2 :
|
|||
subtitle = transaction.extractDate(),
|
||||
iconState = transaction.extractIcon(),
|
||||
transactionTitle = transaction.extractTransactionTitle(),
|
||||
card = transaction.extractCard(),
|
||||
transactionCategory = transaction.extractTransactionCategory(),
|
||||
mcc = transaction.extractMcc(),
|
||||
transactionAmount = transaction.extractAmount(),
|
||||
|
|
@ -289,6 +290,18 @@ internal object TangemPayTxHistoryDetailsConverterV2 :
|
|||
}
|
||||
}
|
||||
|
||||
private fun TangemPayTxHistoryItem.extractCard(): TextReference? {
|
||||
if (this !is TangemPayTxHistoryItem.Spend) return null
|
||||
val name = cardName?.takeIf { it.isNotEmpty() }
|
||||
val last4 = cardNumberLast4?.takeIf { it.isNotEmpty() }
|
||||
return when {
|
||||
name != null && last4 != null -> stringReference("$name *$last4")
|
||||
last4 != null -> stringReference("*$last4")
|
||||
name != null -> stringReference(name)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun Input.extractButtonState(): ButtonState {
|
||||
return ButtonState(
|
||||
text = resourceReference(R.string.tangem_pay_get_help),
|
||||
|
|
|
|||
|
|
@ -233,6 +233,24 @@ internal fun TransactionLabel(label: TransactionLabelUM, modifier: Modifier = Mo
|
|||
@Composable
|
||||
private fun TransactionDetailsBlock(state: TangemPayTxHistoryDetailsUMV2, modifier: Modifier = Modifier) {
|
||||
Column(modifier = modifier) {
|
||||
if (state.card != null) {
|
||||
TangemRow(
|
||||
divider = true,
|
||||
contentLead = TangemRowContentLead.Start,
|
||||
titleSlot = {
|
||||
TangemRowText(
|
||||
text = resourceReference(R.string.tangempay_common_card),
|
||||
role = TangemRowTextRole.Title,
|
||||
)
|
||||
},
|
||||
valueSlot = {
|
||||
TangemRowText(
|
||||
text = state.card,
|
||||
role = TangemRowTextRole.Value,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
TangemRow(
|
||||
divider = state.mcc != null,
|
||||
contentLead = TangemRowContentLead.Start,
|
||||
|
|
@ -289,6 +307,7 @@ private class TangemPayTxHistoryDetailsUMProviderV2 :
|
|||
subtitle = stringReference("12 June 2026, 12:40"),
|
||||
iconState = TangemIconUM.Icon(iconRes = R.drawable.ic_category_24),
|
||||
transactionTitle = stringReference("Starbucks"),
|
||||
card = stringReference("Basic card *9092"),
|
||||
transactionCategory = stringReference("Food and drinks"),
|
||||
mcc = stringReference("5814"),
|
||||
transactionAmount = "-$5.86",
|
||||
|
|
@ -310,6 +329,7 @@ private class TangemPayTxHistoryDetailsUMProviderV2 :
|
|||
subtitle = stringReference("12 June 2026, 12:40"),
|
||||
iconState = TangemIconUM.Icon(iconRes = R.drawable.ic_category_24),
|
||||
transactionTitle = stringReference("NuCaloric"),
|
||||
card = stringReference("Basic card *9092"),
|
||||
transactionCategory = stringReference("Groceries"),
|
||||
mcc = stringReference("0000"),
|
||||
transactionAmount = "-$820.52",
|
||||
|
|
@ -332,6 +352,7 @@ private class TangemPayTxHistoryDetailsUMProviderV2 :
|
|||
subtitle = stringReference("12 June 2026, 12:40"),
|
||||
iconState = TangemIconUM.Icon(iconRes = R.drawable.ic_category_24),
|
||||
transactionTitle = stringReference("Starbucks"),
|
||||
card = stringReference("Basic card *9092"),
|
||||
transactionCategory = stringReference("Food and drinks"),
|
||||
mcc = null,
|
||||
transactionAmount = "-$5.86",
|
||||
|
|
@ -353,6 +374,7 @@ private class TangemPayTxHistoryDetailsUMProviderV2 :
|
|||
subtitle = stringReference("12 June 2026, 12:40"),
|
||||
iconState = TangemIconUM.Icon(iconRes = R.drawable.ic_percent_24),
|
||||
transactionTitle = stringReference("Service fees"),
|
||||
card = null,
|
||||
transactionCategory = stringReference("Service fees"),
|
||||
mcc = null,
|
||||
transactionAmount = "-$5.86",
|
||||
|
|
@ -375,6 +397,7 @@ private class TangemPayTxHistoryDetailsUMProviderV2 :
|
|||
subtitle = stringReference("12 June 2026, 12:40"),
|
||||
iconState = TangemIconUM.Icon(imageVector = Icons.ic_arrow_down_24),
|
||||
transactionTitle = resourceReference(R.string.common_transfer),
|
||||
card = null,
|
||||
transactionCategory = resourceReference(R.string.common_transfer),
|
||||
mcc = null,
|
||||
transactionAmount = "+$20",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue