Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-09 12:41:59 +04:00
parent 269a6d1d57
commit 161477c16a
11 changed files with 125 additions and 72 deletions

View file

@ -67,7 +67,11 @@ internal class DefaultTangemPayStorage @Inject constructor(
secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true)
}
override suspend fun clear(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
override suspend fun clearOrderId(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
secureStorage.delete(createOrderIdKey(customerWalletAddress))
}
override suspend fun clearAll(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
secureStorage.delete(createKey(customerWalletAddress))
secureStorage.delete(createOrderIdKey(customerWalletAddress))
}

View file

@ -12,5 +12,7 @@ interface TangemPayStorage {
suspend fun getOrderId(customerWalletAddress: String): String?
suspend fun clear(customerWalletAddress: String)
suspend fun clearOrderId(customerWalletAddress: String)
suspend fun clearAll(customerWalletAddress: String)
}

View file

@ -18,6 +18,7 @@ import kotlinx.coroutines.withContext
import javax.inject.Inject
private const val VALID_STATUS = "valid"
private const val APPROVED_KYC_STATUS = "APPROVED"
private const val TAG = "TangemPay: OnboardingRepository"
internal class DefaultOnboardingRepository @Inject constructor(
@ -47,13 +48,36 @@ internal class DefaultOnboardingRepository @Inject constructor(
override suspend fun getMainScreenCustomerInfo(): Either<UniversalError, MainScreenCustomerInfo> {
return requestHelper.runWithErrorLogs(TAG) {
val result = requestHelper.requestWithPersistedToken { authHeader ->
tangemPayApi.getCustomerMe(authHeader)
}.result
val customerWalletAddress = requestHelper.getCustomerWalletAddress()
val orderStatus = getOrderStatus().getOrNull() ?: error("Order status is null")
when (val orderId = tangemPayStorage.getOrderId(customerWalletAddress)) {
// If order id wasn't saved -> get customer info
null -> {
MainScreenCustomerInfo(
info = getCustomerInfoWithPersistedToken(),
orderStatus = OrderStatus.UNKNOWN,
)
}
// If order id was saved -> check its status
else -> {
val orderStatus = getOrderStatus(orderId)
val customerInfo = when (orderStatus) {
// Kyc is passed and user waits for order creation -> no need to get customer info
OrderStatus.NEW,
OrderStatus.PROCESSING,
-> CustomerInfo(productInstance = null, isKycApproved = true, cardInfo = null)
MainScreenCustomerInfo(info = getCustomerInfo(result), orderStatus = orderStatus)
// Order was created/cancelled -> clear order id and get customer info
OrderStatus.UNKNOWN,
OrderStatus.COMPLETED,
OrderStatus.CANCELED,
-> getCustomerInfoWithPersistedToken().also {
tangemPayStorage.clearOrderId(customerWalletAddress)
}
}
MainScreenCustomerInfo(info = customerInfo, orderStatus = orderStatus)
}
}
}
}
@ -84,27 +108,28 @@ internal class DefaultOnboardingRepository @Inject constructor(
}
return CustomerInfo(
productInstance = response?.productInstance?.let { ProductInstance(id = it.id, status = it.status) },
kycStatus = response?.kyc?.status,
isKycApproved = response?.kyc?.status == APPROVED_KYC_STATUS,
cardInfo = cardInfo,
)
}
private suspend fun getOrderStatus(): Either<UniversalError, OrderStatus> {
return requestHelper.runWithErrorLogs(TAG) {
val walletAddress = requestHelper.getCustomerWalletAddress()
val orderId: String = tangemPayStorage.getOrderId(walletAddress)
?: return@runWithErrorLogs OrderStatus.NOT_ISSUED
private suspend fun getOrderStatus(orderId: String): OrderStatus {
val result = requestHelper.request { authHeader ->
tangemPayApi.getOrder(authHeader, orderId)
}.result ?: error("Order result is null")
val result = requestHelper.request { authHeader ->
tangemPayApi.getOrder(authHeader, orderId)
}.result ?: error("Order result is null")
when (result.status) {
OrderStatus.NEW.apiName -> OrderStatus.NEW
OrderStatus.PROCESSING.apiName -> OrderStatus.PROCESSING
OrderStatus.COMPLETED.apiName -> OrderStatus.COMPLETED
else -> OrderStatus.CANCELED
}
return when (result.status) {
OrderStatus.NEW.apiName -> OrderStatus.NEW
OrderStatus.PROCESSING.apiName -> OrderStatus.PROCESSING
OrderStatus.COMPLETED.apiName -> OrderStatus.COMPLETED
else -> OrderStatus.CANCELED
}
}
private suspend fun getCustomerInfoWithPersistedToken(): CustomerInfo {
val result = requestHelper.requestWithPersistedToken { authHeader ->
tangemPayApi.getCustomerMe(authHeader)
}.result
return getCustomerInfo(result)
}
}

View file

@ -2,8 +2,6 @@ package com.tangem.domain.pay.model
import java.math.BigDecimal
private const val APPROVED_KYC_STATUS = "APPROVED"
data class MainScreenCustomerInfo(
val info: CustomerInfo,
val orderStatus: OrderStatus,
@ -11,7 +9,7 @@ data class MainScreenCustomerInfo(
data class CustomerInfo(
val productInstance: ProductInstance?,
val kycStatus: String?,
val isKycApproved: Boolean,
val cardInfo: CardInfo?,
) {
@ -26,6 +24,4 @@ data class CustomerInfo(
val currencyCode: String,
val customerWalletAddress: String,
)
fun isKycApproved() = kycStatus == APPROVED_KYC_STATUS
}

View file

@ -1,7 +1,7 @@
package com.tangem.domain.pay.model
enum class OrderStatus(val apiName: String) {
NOT_ISSUED(""),
UNKNOWN(""),
NEW("NEW"),
PROCESSING("PROCESSING"),
COMPLETED("COMPLETED"),

View file

@ -56,7 +56,7 @@ internal class TangemPayOnboardingModel @Inject constructor(
repository.getCustomerInfo()
.onRight { customerInfo ->
when {
!customerInfo.isKycApproved() -> {
!customerInfo.isKycApproved -> {
when (params) {
is TangemPayOnboardingComponent.Params.Deeplink ->
screenState.value = screenState.value.copy(fullScreenLoading = false)

View file

@ -359,10 +359,10 @@ internal class WalletModel @Inject constructor(
val info = tangemPayMainScreenCustomerInfoUseCase()
if (info != null) {
stateHolder.update(
transformer = TangemPayStateTransformer(
transformer = TangemPayInitialStateTransformer(
value = info,
onIssueOrderClick = ::issueOrder,
onContinueKycClick = innerWalletRouter::openTangemPayOnboarding,
onClickIssue = ::issueOrder,
onClickKyc = innerWalletRouter::openTangemPayOnboarding,
openDetails = innerWalletRouter::openTangemPayDetails,
),
)
@ -371,9 +371,9 @@ internal class WalletModel @Inject constructor(
private fun issueOrder() {
modelScope.launch {
stateHolder.update(TangemPayStateTransformer(issueProgressState = true))
stateHolder.update(TangemPayIssueProgressStateTransformer())
tangemPayIssueOrderUseCase().onLeft {
stateHolder.update(TangemPayStateTransformer(issueState = true, onIssueOrderClick = ::issueOrder))
stateHolder.update(TangemPayIssueAvailableStateTransformer(onClickIssue = ::issueOrder))
}
}
}

View file

@ -1,33 +1,28 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.pay.model.CustomerInfo.CardInfo
import com.tangem.domain.pay.model.MainScreenCustomerInfo
import com.tangem.domain.pay.model.OrderStatus.CANCELED
import com.tangem.domain.pay.model.OrderStatus.NOT_ISSUED
import com.tangem.domain.pay.model.OrderStatus.UNKNOWN
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueAvailableState
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueProgressState
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createKycInProgressState
import java.util.Currency
internal class TangemPayStateTransformer(
internal class TangemPayInitialStateTransformer(
private val value: MainScreenCustomerInfo? = null,
private val onIssueOrderClick: () -> Unit = {},
private val onContinueKycClick: () -> Unit = {},
private val onClickIssue: () -> Unit = {},
private val onClickKyc: () -> Unit = {},
private val openDetails: (customerWalletAddress: String, cardNumberEnd: String) -> Unit = { _, _ -> },
private val issueProgressState: Boolean = false,
private val issueState: Boolean = false,
) : WalletScreenStateTransformer {
override fun transform(prevState: WalletScreenState): WalletScreenState {
val tangemPayState = when {
issueProgressState -> createIssueProgressState()
issueState -> createIssueState()
else -> createInitialState()
}
val tangemPayState = createInitialState()
return prevState.copy(tangemPayState = tangemPayState)
}
@ -35,35 +30,13 @@ internal class TangemPayStateTransformer(
val cardInfo = value?.info?.cardInfo
return when {
value == null -> TangemPayState.Empty
!value.info.isKycApproved() -> createKycInProgressState(onContinueKycClick)
!value.info.isKycApproved -> createKycInProgressState(onClickKyc)
cardInfo != null -> getCardInfoState(cardInfo)
value.orderStatus == NOT_ISSUED || value.orderStatus == CANCELED -> createIssueState()
value.orderStatus == UNKNOWN || value.orderStatus == CANCELED -> createIssueAvailableState(onClickIssue)
else -> createIssueProgressState()
}
}
private fun createIssueProgressState(): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.EMPTY,
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = {},
showProgress = true,
)
private fun createIssueState() = Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.Res(R.string.common_continue),
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = onIssueOrderClick,
)
private fun createKycInProgressState(onContinueKycClick: () -> Unit): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
iconRes = R.drawable.ic_promo_kyc_36,
onButtonClick = onContinueKycClick,
)
private fun getCardInfoState(cardInfo: CardInfo): TangemPayState = TangemPayState.Card(
lastFourDigits = TextReference.Str("*${cardInfo.lastFourDigits}"),
balanceText = TextReference.Str(getBalanceText(cardInfo)),

View file

@ -0,0 +1,12 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueAvailableState
internal class TangemPayIssueAvailableStateTransformer(
private val onClickIssue: () -> Unit = {},
) : WalletScreenStateTransformer {
override fun transform(prevState: WalletScreenState): WalletScreenState =
prevState.copy(tangemPayState = createIssueAvailableState(onClickIssue))
}

View file

@ -0,0 +1,10 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.util.TangemPayStateCreator.createIssueProgressState
internal class TangemPayIssueProgressStateTransformer : WalletScreenStateTransformer {
override fun transform(prevState: WalletScreenState): WalletScreenState =
prevState.copy(tangemPayState = createIssueProgressState())
}

View file

@ -0,0 +1,31 @@
package com.tangem.feature.wallet.presentation.wallet.state.util
import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
internal object TangemPayStateCreator {
fun createKycInProgressState(onClickKyc: () -> Unit): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_title),
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
iconRes = R.drawable.ic_promo_kyc_36,
onButtonClick = onClickKyc,
)
fun createIssueAvailableState(onClickIssue: () -> Unit) = Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.Res(R.string.common_continue),
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = onClickIssue,
)
fun createIssueProgressState(): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_issue_card_notification_title),
buttonText = TextReference.EMPTY,
iconRes = R.drawable.ic_tangem_pay_promo_card_36,
onButtonClick = {},
showProgress = true,
)
}