From 096d1e40c9ab3d60c50eb5a3b8f7b97953964b1e Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 19 Mar 2026 18:24:41 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../api/pay/models/request/OrderRequest.kt | 11 +++-- .../pay/models/response/CustomerMeResponse.kt | 2 +- .../pay/DefaultTangemPayEligibilityManager.kt | 3 +- .../DefaultPaymentAccountStatusFetcher.kt | 7 +-- .../DefaultCustomerOrderRepository.kt | 1 + .../repository/DefaultOnboardingRepository.kt | 46 ++++++++++--------- .../com/tangem/domain/pay/model/OrderData.kt | 1 + .../pay/repository/OnboardingRepository.kt | 3 +- .../TangemPayMainScreenCustomerInfoUseCase.kt | 42 ++++++++++------- .../model/TangemPayOnboardingModel.kt | 14 +++++- .../TangemPayUpdateInfoStateTransformer.kt | 2 +- 11 files changed, 79 insertions(+), 53 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/OrderRequest.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/OrderRequest.kt index 73725d5782..b293b1e3aa 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/OrderRequest.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/request/OrderRequest.kt @@ -4,6 +4,11 @@ import com.squareup.moshi.Json import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) -data class OrderRequest( - @Json(name = "wallet_address") val walletAddress: String, -) \ No newline at end of file +data class OrderRequest(@Json(name = "data") val data: Data) { + @JsonClass(generateAdapter = true) + data class Data( + @Json(name = "customer_wallet_address") val customerWalletAddress: String, + @Json(name = "specification_name") val specificationName: String = "SP_000004", + @Json(name = "type") val type: String = "CARD_ISSUE_VIRTUAL_RAIN_KYC", + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt index 69405843f7..38e8982664 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/CustomerMeResponse.kt @@ -73,7 +73,7 @@ data class CustomerMeResponse( @JsonClass(generateAdapter = true) data class PaymentAccount( @Json(name = "id") val id: String, - @Json(name = "address") val address: String, + @Json(name = "address") val address: String?, @Json(name = "customer_wallet_address") val customerWalletAddress: String, ) diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt index c3eb76035e..5394296772 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/DefaultTangemPayEligibilityManager.kt @@ -92,8 +92,7 @@ internal class DefaultTangemPayEligibilityManager @Inject constructor( } private fun UserWallet.isCompatible(): Boolean = when (this) { - is UserWallet.Cold -> - scanResponse.card.firmwareVersion >= FirmwareVersion.HDWalletAvailable + is UserWallet.Cold -> scanResponse.card.firmwareVersion >= FirmwareVersion.HDWalletAvailable is UserWallet.Hot -> hotWalletId.authType != HotWalletId.AuthType.NoPassword } diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt index cdfbf0d9b4..fe1aa3f0bd 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/flow/DefaultPaymentAccountStatusFetcher.kt @@ -104,9 +104,9 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( ifRight = { customerInfo -> Timber.tag(TAG).i("proceedWithoutOrder data customerInfo $userWalletId") val status = customerInfo.mapToPaymentAccountStatus() - if (status is PaymentAccountStatus.IssuingCard && customerInfo.kycStatus == KycStatus.APPROVED) { - // If order id wasn't saved -> start order creation and get customer info + if (customerInfo.productInstance == null) { onboardingRepository.createOrder(userWalletId) + .onLeft { Timber.tag(TAG).e("createOrder failed: $it") } } status }, @@ -128,9 +128,6 @@ internal class DefaultPaymentAccountStatusFetcher @Inject constructor( -> PaymentAccountStatus.IssuingCard(source = StatusSource.ACTUAL) OrderStatus.CANCELED -> { - // If order was cancelled -> clear previous order from local storage and start order creation - onboardingRepository.clearOrderId(userWalletId) - onboardingRepository.createOrder(userWalletId) PaymentAccountStatus.Error.CardIssueFailed } OrderStatus.COMPLETED -> { diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOrderRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOrderRepository.kt index 508627ca37..8cc392d6ed 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOrderRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOrderRepository.kt @@ -27,6 +27,7 @@ internal class DefaultCustomerOrderRepository @Inject constructor( OrderResponse.Result.Status.CANCELED -> OrderStatus.CANCELED } OrderData( + customerId = response.result?.customerId.orEmpty(), status = status, withdrawTxHash = response.result?.data?.transactionHash?.ifEmpty { null }, ) diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt index b67a28e4e5..6977883f46 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultOnboardingRepository.kt @@ -1,13 +1,14 @@ package com.tangem.data.pay.repository import arrow.core.Either -import arrow.core.raise.catch +import arrow.core.right import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.datasource.api.pay.TangemPayApi import com.tangem.datasource.api.pay.models.request.DeeplinkValidityRequest import com.tangem.datasource.api.pay.models.request.OrderRequest import com.tangem.datasource.api.pay.models.request.SetTangemPayEnabledRequest import com.tangem.datasource.api.pay.models.response.CustomerMeResponse +import com.tangem.datasource.api.pay.models.response.OrderResponse import com.tangem.datasource.local.visa.TangemPayCardFrozenStateStore import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.domain.common.wallets.UserWalletsListRepository @@ -23,14 +24,11 @@ import com.tangem.domain.tangempay.TangemPayAnalyticsEvents import com.tangem.domain.visa.error.VisaApiError import com.tangem.domain.visa.model.TangemPayCardFrozenState import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import timber.log.Timber import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject private const val VALID_STATUS = "valid" -private const val TAG = "TangemPay: OnboardingRepository" @Suppress("LongParameterList") internal class DefaultOnboardingRepository @Inject constructor( @@ -109,25 +107,31 @@ internal class DefaultOnboardingRepository @Inject constructor( return lastFetchedCustomerInfoMap[userWalletId] } - override suspend fun createOrder(userWalletId: UserWalletId) = withContext(dispatcherProvider.io) { - launch { - catch( - block = { - val walletAddress = requestHelper.getCustomerWalletAddress(userWalletId) - val response = requestHelper.performRequest(userWalletId) { authHeader -> - tangemPayApi.createOrder(authHeader, body = OrderRequest(walletAddress)) - }.getOrNull() + override suspend fun createOrder(userWalletId: UserWalletId): Either = + withContext(dispatcherProvider.io) { + val existingOrderId = getOrderId(userWalletId) + if (existingOrderId != null) { + val orderStatus = requestHelper.performRequest(userWalletId) { authHeader -> + tangemPayApi.getOrder(authHeader, existingOrderId) + }.map { it.result?.status }.getOrNull() - val result = requireNotNull(response?.result) - val customerWalletAddress = requireNotNull(result.data.customerWalletAddress) - tangemPayStorage.storeOrderId(customerWalletAddress, result.id) - }, - catch = { - Timber.tag(TAG).e("createOrder: $it") - }, - ) + if (orderStatus == OrderResponse.Result.Status.NEW || + orderStatus == OrderResponse.Result.Status.PROCESSING + ) { + return@withContext existingOrderId.right() + } + } + + val walletAddress = requestHelper.getCustomerWalletAddress(userWalletId) + requestHelper.performRequest(userWalletId) { authHeader -> + val data = OrderRequest.Data(customerWalletAddress = walletAddress) + tangemPayApi.createOrder(authHeader, body = OrderRequest(data = data)) + }.map { response -> + val result = requireNotNull(response.result) + tangemPayStorage.storeOrderId(walletAddress, result.id) + result.id + } } - } private fun getUserWallet(userWalletId: UserWalletId): UserWallet { return userWalletsListRepository.userWallets.value?.firstOrNull { it.walletId == userWalletId } diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderData.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderData.kt index 42b7001564..3fd07971ec 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderData.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderData.kt @@ -1,6 +1,7 @@ package com.tangem.domain.pay.model data class OrderData( + val customerId: String, val status: OrderStatus, val withdrawTxHash: String?, ) \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt index 52197ec817..f15d30b93a 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/OnboardingRepository.kt @@ -5,7 +5,6 @@ import com.tangem.core.error.UniversalError import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.model.CustomerInfo import com.tangem.domain.visa.error.VisaApiError -import kotlinx.coroutines.Job interface OnboardingRepository { @@ -17,7 +16,7 @@ interface OnboardingRepository { suspend fun getCustomerInfo(userWalletId: UserWalletId): Either - suspend fun createOrder(userWalletId: UserWalletId): Job + suspend fun createOrder(userWalletId: UserWalletId): Either suspend fun clearOrderId(userWalletId: UserWalletId) diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt index 4116e2e4f4..7b403dd167 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt @@ -126,11 +126,13 @@ class TangemPayMainScreenCustomerInfoUseCase( } .map { customerInfo -> Timber.tag(TAG).i("customerInfo") - if (customerInfo.cardInfo == null && customerInfo.kycStatus == KycStatus.APPROVED) { - // If order id wasn't saved -> start order creation and get customer info + if (customerInfo.productInstance == null) { onboardingRepository.createOrder(userWalletId) + Timber.tag("ddk9499").d("TangemPayMainScreenCustomerInfoUseCase.proceedWithoutOrder: ") + MainScreenCustomerInfo(info = customerInfo, orderStatus = OrderStatus.NEW) + } else { + MainScreenCustomerInfo(info = customerInfo, orderStatus = OrderStatus.COMPLETED) } - MainScreenCustomerInfo(info = customerInfo, orderStatus = OrderStatus.UNKNOWN) } } @@ -145,27 +147,33 @@ class TangemPayMainScreenCustomerInfoUseCase( }, ifRight = { orderData -> when (orderData.status) { - // Kyc is passed and user waits for order creation -> no need to get customer info OrderStatus.NEW, OrderStatus.PROCESSING, - -> MainScreenCustomerInfo( - info = CustomerInfo( - customerId = null, - productInstance = null, - kycStatus = KycStatus.APPROVED, - cardInfo = null, - ), - orderStatus = orderData.status, - ).right() + -> { + onboardingRepository.getCustomerInfo(userWalletId = userWalletId) + .mapLeft { it.mapErrorForCustomer() } + .map { customerInfo -> + MainScreenCustomerInfo(info = customerInfo, orderStatus = orderData.status) + } + } + + // Order cancelled. No need to get customer info + OrderStatus.CANCELED -> { + MainScreenCustomerInfo( + info = CustomerInfo( + customerId = null, + productInstance = null, + kycStatus = KycStatus.INIT, + cardInfo = null, + ), + orderStatus = OrderStatus.CANCELED, + ).right() + } - // Order was created/cancelled -> clear order id and get customer info OrderStatus.COMPLETED, - OrderStatus.CANCELED, OrderStatus.UNKNOWN, -> { onboardingRepository.clearOrderId(userWalletId) - // If order was cancelled -> start order creation - if (orderData.status == OrderStatus.CANCELED) onboardingRepository.createOrder(userWalletId) onboardingRepository.getCustomerInfo(userWalletId = userWalletId) .mapLeft { it.mapErrorForCustomer() } .map { customerInfo -> diff --git a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt index 348378e343..ce4d7a58ce 100644 --- a/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt +++ b/features/tangempay/onboarding/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayOnboardingModel.kt @@ -94,9 +94,14 @@ internal class TangemPayOnboardingModel @Inject constructor( uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = true)) repository.getCustomerInfo(userWalletId = userWalletId) .onRight { customerInfo -> - uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = false)) when { customerInfo.kycStatus != KycStatus.APPROVED -> { + if (customerInfo.productInstance == null) { + repository.createOrder(userWalletId) + .onLeft { error -> + Timber.e("Error creating order before KYC: $error") + } + } when (params) { is TangemPayOnboardingComponent.Params.ContinueOnboarding -> openKyc(userWalletId) else -> startOnboarding(userWalletId) @@ -104,6 +109,7 @@ internal class TangemPayOnboardingModel @Inject constructor( } else -> back() } + uiState.transformerUpdate(TangemPayOnboardingButtonLoadingTransformer(isLoading = false)) } .onLeft { startOnboarding(userWalletId) } } @@ -174,6 +180,12 @@ internal class TangemPayOnboardingModel @Inject constructor( if (customerInfo.kycStatus == KycStatus.APPROVED) { back() } else { + if (customerInfo.productInstance == null) { + repository.createOrder(userWalletId) + .onLeft { error -> + Timber.e("Error creating order before KYC: $error") + } + } openKyc(userWalletId) } }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayUpdateInfoStateTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayUpdateInfoStateTransformer.kt index 2839523857..9c19535642 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayUpdateInfoStateTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/TangemPayUpdateInfoStateTransformer.kt @@ -56,7 +56,7 @@ internal class TangemPayUpdateInfoStateTransformer( value.orderStatus == OrderStatus.CANCELED -> createCancelledState(customerId) value.info.kycStatus != KycStatus.APPROVED && !value.info.customerId.isNullOrEmpty() -> createKycInProgressState(kycStatus = value.info.kycStatus, customerId = customerId) - cardInfo != null && productInstance != null -> + cardInfo != null && productInstance != null && value.orderStatus == OrderStatus.COMPLETED -> getCardInfoState(customerId, cardInfo, productInstance) else -> createIssueProgressState() }