Updated on 2026-08-14
This commit is contained in:
parent
50a384f13d
commit
457e8870a4
7 changed files with 165 additions and 10 deletions
|
|
@ -5,6 +5,8 @@ import com.tangem.domain.pay.flow.PaymentAccountStatusFetcher
|
|||
import com.tangem.domain.pay.model.OrderStatus
|
||||
import com.tangem.domain.pay.model.TangemPayOrderInfo
|
||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.coroutines.delay
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
|
|
@ -13,6 +15,8 @@ class StartTangemPayOrderPollingUseCase(
|
|||
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
|
||||
) {
|
||||
|
||||
private val logger = TangemLogger.withTag("StartTangemPayOrderPollingUseCase")
|
||||
|
||||
/**
|
||||
* Order keys (`walletId:orderId`) currently being polled. Keeps polling idempotent so callers that
|
||||
* may fire repeatedly for the same order (e.g. order restore on every wallet (re)load) never spawn a
|
||||
|
|
@ -36,7 +40,7 @@ class StartTangemPayOrderPollingUseCase(
|
|||
val newOrder = if (order.orderStatus.isTerminal) {
|
||||
order
|
||||
} else {
|
||||
cardDetailsRepository.getOrderInfo(userWalletId, order.orderId).getOrNull()
|
||||
getOrderInfo(userWalletId = userWalletId, orderId = order.orderId)
|
||||
}
|
||||
|
||||
if (newOrder != null && newOrder != currentOrder) {
|
||||
|
|
@ -56,7 +60,22 @@ class StartTangemPayOrderPollingUseCase(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun getOrderInfo(userWalletId: UserWalletId, orderId: String): TangemPayOrderInfo? {
|
||||
return cardDetailsRepository.getOrderInfo(userWalletId, orderId).fold(
|
||||
ifLeft = { error ->
|
||||
if (error == VisaApiError.OrderNotFound) {
|
||||
logger.i("$orderId: does not exist on the backend, resolving as CANCELED")
|
||||
TangemPayOrderInfo(orderId = orderId, orderStatus = OrderStatus.CANCELED)
|
||||
} else {
|
||||
logger.e("$orderId: poll failed, will retry — $error")
|
||||
null
|
||||
}
|
||||
},
|
||||
ifRight = { it },
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val POLLING_DELAY = 3000L
|
||||
private const val POLLING_DELAY = 5_000L
|
||||
}
|
||||
}
|
||||
|
|
@ -230,6 +230,65 @@ internal class StartTangemPayOrderPollingUseCaseTest {
|
|||
firstPoller.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN processing order WHEN backend reports order not found THEN resolves as CANCELED and stops`() = runTest {
|
||||
// GIVEN
|
||||
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.PROCESSING)
|
||||
val changes = mutableListOf<TangemPayOrderInfo>()
|
||||
coEvery {
|
||||
cardDetailsRepository.getOrderInfo(USER_WALLET_ID, ORDER_ID)
|
||||
} returns VisaApiError.OrderNotFound.left()
|
||||
coEvery { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) } returns Unit.right()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(order, USER_WALLET_ID, onOrderStateChange = { changes.add(it) })
|
||||
|
||||
// THEN
|
||||
assertThat(result).isFalse()
|
||||
assertThat(changes).containsExactly(TangemPayOrderInfo(ORDER_ID, OrderStatus.CANCELED))
|
||||
coVerify(exactly = 1) { cardDetailsRepository.getOrderInfo(USER_WALLET_ID, ORDER_ID) }
|
||||
coVerify(exactly = 1) { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN long outage WHEN backend recovers THEN order is still polled to terminal`() = runTest {
|
||||
// GIVEN
|
||||
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.PROCESSING)
|
||||
val outage = List(size = 100) { VisaApiError.ServerUnavailable.left() }
|
||||
coEvery {
|
||||
cardDetailsRepository.getOrderInfo(USER_WALLET_ID, ORDER_ID)
|
||||
} returnsMany outage + TangemPayOrderInfo(ORDER_ID, OrderStatus.COMPLETED).right()
|
||||
coEvery { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) } returns Unit.right()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(order, USER_WALLET_ID)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isTrue()
|
||||
coVerify(exactly = 101) { cardDetailsRepository.getOrderInfo(USER_WALLET_ID, ORDER_ID) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN non-terminal order WHEN polling THEN polls at a flat 5s cadence`() = runTest {
|
||||
// GIVEN
|
||||
val order = TangemPayOrderInfo(ORDER_ID, OrderStatus.PROCESSING)
|
||||
coEvery {
|
||||
cardDetailsRepository.getOrderInfo(USER_WALLET_ID, ORDER_ID)
|
||||
} returnsMany listOf(
|
||||
TangemPayOrderInfo(ORDER_ID, OrderStatus.NEW).right(),
|
||||
VisaApiError.ServerUnavailable.left(),
|
||||
TangemPayOrderInfo(ORDER_ID, OrderStatus.COMPLETED).right(),
|
||||
)
|
||||
coEvery { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) } returns Unit.right()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(order, USER_WALLET_ID)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isTrue()
|
||||
assertThat(testScheduler.currentTime).isEqualTo(10_000L)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val USER_WALLET_ID = UserWalletId("aabbcc112233")
|
||||
const val ORDER_ID = "order-test-1"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue