From 11576427229dd97d5a8937ffb7fb2b6b6a14952c Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 23 Jun 2026 13:17:24 +0500 Subject: [PATCH] Updated on 2026-08-14 --- .../domain/pay/model/OrderConflictRules.kt | 12 ++------- .../tangem/domain/pay/model/OrderStatus.kt | 12 +++++++-- .../com/tangem/domain/pay/model/OrderType.kt | 26 ++++++++++++++++++- .../pay/usecase/IssueAdditionalCardUseCase.kt | 8 +++--- .../pay/model/OrderConflictRulesTest.kt | 9 +++++++ .../usecase/IssueAdditionalCardUseCaseTest.kt | 9 ++++--- 6 files changed, 55 insertions(+), 21 deletions(-) diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderConflictRules.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderConflictRules.kt index 4c576e6bd3..43b47e9cf8 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderConflictRules.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderConflictRules.kt @@ -49,7 +49,7 @@ object OrderConflictRules { private fun blocks(intent: OrderIntent, order: Order): Boolean { if (!order.isActive) return false return when (intent) { - OrderIntent.IssueCard -> order.type.isIssuing() + OrderIntent.IssueCard -> order.type.isIssuing OrderIntent.Withdraw -> order.type == OrderType.WITHDRAW is OrderIntent.Freeze -> sameProductInstance(order, intent.productInstanceId) && order.type.isFreezeOrReissue() @@ -65,15 +65,7 @@ object OrderConflictRules { return order.productInstanceId == productInstanceId } - private fun OrderType.isIssuing(): Boolean { - return this == OrderType.CARD_ISSUE_ADDITIONAL || - this == OrderType.CARD_ISSUE_VIRTUAL_RAIN_KYC || - this == OrderType.CARD_ISSUE_VIRTUAL_RAIN_KYC_V2 - } - private fun OrderType.isFreezeOrReissue(): Boolean { - return this == OrderType.CARD_FREEZE || - this == OrderType.CARD_UNFREEZE || - this == OrderType.CARD_REISSUE + return this.isFreezingUnfreezing || this.isReissuing } } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderStatus.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderStatus.kt index 9741d825f6..27ad8bc888 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderStatus.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderStatus.kt @@ -8,8 +8,16 @@ enum class OrderStatus { ; /** An order is active while it is still being processed (NEW or PROCESSING). */ - val isActive: Boolean get() = this == NEW || this == PROCESSING + val isActive: Boolean get() = activeStatuses.contains(this) /** Terminal statuses (COMPLETED or CANCELED) — used to invalidate the local order hint. */ - val isTerminal: Boolean get() = this == COMPLETED || this == CANCELED + val isTerminal: Boolean get() = terminalStatuses.contains(this) + + companion object { + /** Statuses of an in-flight order (still being processed). */ + val activeStatuses: Set = setOf(NEW, PROCESSING) + + /** Statuses of a finished order — no further state changes are expected. */ + val terminalStatuses: Set = setOf(COMPLETED, CANCELED) + } } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderType.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderType.kt index 006a67088f..c5cf104ae8 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderType.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderType.kt @@ -1,6 +1,7 @@ package com.tangem.domain.pay.model import com.tangem.domain.pay.model.OrderType.Companion.fromString +import com.tangem.domain.pay.model.OrderType.Companion.issueCardTypes /** * Order type used for findOrders filtering and order-conflict checks. @@ -10,8 +11,9 @@ import com.tangem.domain.pay.model.OrderType.Companion.fromString */ enum class OrderType(val wireValue: String) { CARD_ISSUE_ADDITIONAL("CARD_ISSUE_ADDITIONAL"), - CARD_ISSUE_VIRTUAL_RAIN_KYC_V2("CARD_ISSUE_VIRTUAL_RAIN_KYC_V2"), + CARD_ISSUE_VIRTUAL_RAIN("CARD_ISSUE_VIRTUAL_RAIN"), CARD_ISSUE_VIRTUAL_RAIN_KYC("CARD_ISSUE_VIRTUAL_RAIN_KYC"), + CARD_ISSUE_VIRTUAL_RAIN_KYC_V2("CARD_ISSUE_VIRTUAL_RAIN_KYC_V2"), CARD_REISSUE("CARD_REISSUE"), CARD_FREEZE("CARD_FREEZE"), CARD_UNFREEZE("CARD_UNFREEZE"), @@ -19,7 +21,29 @@ enum class OrderType(val wireValue: String) { UNKNOWN(""), ; + /** `true` for any card-issuance order type — see [issueCardTypes]. */ + val isIssuing: Boolean get() = issueCardTypes.contains(this) + + /** `true` for card freeze / unfreeze orders. */ + val isFreezingUnfreezing: Boolean get() = this == CARD_FREEZE || this == CARD_UNFREEZE + + /** `true` for card reissue orders. */ + val isReissuing: Boolean get() = this == CARD_REISSUE + companion object { + + /** + * All order types that represent issuing a card: the first virtual card (and its KYC + * variants) and an additional card. Used both to filter `findOrders` and to detect + * issue-card conflicts. + */ + val issueCardTypes = setOf( + CARD_ISSUE_ADDITIONAL, + CARD_ISSUE_VIRTUAL_RAIN, + CARD_ISSUE_VIRTUAL_RAIN_KYC, + CARD_ISSUE_VIRTUAL_RAIN_KYC_V2, + ) + fun fromString(value: String?): OrderType { if (value.isNullOrBlank()) return UNKNOWN return entries.firstOrNull { it.wireValue == value || it.name == value } ?: UNKNOWN diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/IssueAdditionalCardUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/IssueAdditionalCardUseCase.kt index e33223eab4..23b67e2b72 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/IssueAdditionalCardUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/IssueAdditionalCardUseCase.kt @@ -6,6 +6,7 @@ import arrow.core.raise.catch import arrow.core.raise.either import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.pay.model.Offer +import com.tangem.domain.pay.model.OrderStatus import com.tangem.domain.pay.model.OrderType import com.tangem.domain.pay.model.TangemPayOrderInfo import com.tangem.domain.pay.repository.CustomerOffersRepository @@ -56,11 +57,8 @@ class IssueAdditionalCardUseCase( customerOrderRepository .findOrders( userWalletId = userWalletId, - types = setOf( - offer.data.orderType, - OrderType.CARD_ISSUE_VIRTUAL_RAIN_KYC, - OrderType.CARD_ISSUE_VIRTUAL_RAIN_KYC_V2, - ), + types = setOf(offer.data.orderType) + OrderType.issueCardTypes, + statuses = OrderStatus.activeStatuses, ) .bind() }, diff --git a/domain/visa/src/test/kotlin/com/tangem/domain/pay/model/OrderConflictRulesTest.kt b/domain/visa/src/test/kotlin/com/tangem/domain/pay/model/OrderConflictRulesTest.kt index a288112d52..d0a4c4895a 100644 --- a/domain/visa/src/test/kotlin/com/tangem/domain/pay/model/OrderConflictRulesTest.kt +++ b/domain/visa/src/test/kotlin/com/tangem/domain/pay/model/OrderConflictRulesTest.kt @@ -26,6 +26,15 @@ internal class OrderConflictRulesTest { assertThat(resolution).isInstanceOf(ConflictResolution.Blocked::class.java) } + @Test + fun `IssueCard is blocked by an active plain virtual-card issue order`() { + val active = listOf(order(type = OrderType.CARD_ISSUE_VIRTUAL_RAIN, status = OrderStatus.PROCESSING)) + + val resolution = OrderConflictRules.resolve(OrderIntent.IssueCard, active) + + assertThat(resolution).isInstanceOf(ConflictResolution.Blocked::class.java) + } + @Test fun `IssueCard is allowed when only withdraw is active`() { val active = listOf(order(type = OrderType.WITHDRAW, status = OrderStatus.PROCESSING)) diff --git a/domain/visa/src/test/kotlin/com/tangem/domain/pay/usecase/IssueAdditionalCardUseCaseTest.kt b/domain/visa/src/test/kotlin/com/tangem/domain/pay/usecase/IssueAdditionalCardUseCaseTest.kt index 8e6b1926a4..5200c2b329 100644 --- a/domain/visa/src/test/kotlin/com/tangem/domain/pay/usecase/IssueAdditionalCardUseCaseTest.kt +++ b/domain/visa/src/test/kotlin/com/tangem/domain/pay/usecase/IssueAdditionalCardUseCaseTest.kt @@ -67,10 +67,11 @@ internal class IssueAdditionalCardUseCaseTest { userWalletId, types = setOf( OrderType.CARD_ISSUE_ADDITIONAL, + OrderType.CARD_ISSUE_VIRTUAL_RAIN, OrderType.CARD_ISSUE_VIRTUAL_RAIN_KYC, OrderType.CARD_ISSUE_VIRTUAL_RAIN_KYC_V2, ), - statuses = emptySet(), + statuses = setOf(OrderStatus.NEW, OrderStatus.PROCESSING), ) } returns listOf(existing).right() @@ -89,10 +90,11 @@ internal class IssueAdditionalCardUseCaseTest { userWalletId, types = setOf( OrderType.CARD_ISSUE_ADDITIONAL, + OrderType.CARD_ISSUE_VIRTUAL_RAIN, OrderType.CARD_ISSUE_VIRTUAL_RAIN_KYC, OrderType.CARD_ISSUE_VIRTUAL_RAIN_KYC_V2, ), - statuses = emptySet(), + statuses = setOf(OrderStatus.NEW, OrderStatus.PROCESSING), ) } returns emptyList().right() coEvery { @@ -117,10 +119,11 @@ internal class IssueAdditionalCardUseCaseTest { userWalletId, types = setOf( OrderType.CARD_ISSUE_ADDITIONAL, + OrderType.CARD_ISSUE_VIRTUAL_RAIN, OrderType.CARD_ISSUE_VIRTUAL_RAIN_KYC, OrderType.CARD_ISSUE_VIRTUAL_RAIN_KYC_V2, ), - statuses = emptySet(), + statuses = setOf(OrderStatus.NEW, OrderStatus.PROCESSING), ) } returns emptyList().right() val newOrder = order(