Updated on 2026-08-14
This commit is contained in:
parent
4fc52e2052
commit
017eece8b7
18 changed files with 938 additions and 14 deletions
|
|
@ -95,6 +95,12 @@ interface TangemPayApi {
|
|||
@Body body: VirtualAccountOrderRequest,
|
||||
): ApiResponse<OrderResponse>
|
||||
|
||||
@POST("v1/order/{order_id}/cancel")
|
||||
suspend fun cancelOrder(
|
||||
@Header("Authorization") authHeader: String,
|
||||
@Path("order_id") orderId: String,
|
||||
): ApiResponse<Any>
|
||||
|
||||
/** Customer offers — used to gate the issue-additional-card flow. */
|
||||
@GET("v1/customer/offers")
|
||||
suspend fun getCustomerOffers(@Header("Authorization") authHeader: String): ApiResponse<CustomerOffersResponse>
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ internal interface TangemPayDataModule {
|
|||
repository: DefaultTariffPlanTransitionsRepository,
|
||||
): TangemPayTariffPlanTransitionsRepository
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
companion object {
|
||||
|
||||
@Provides
|
||||
|
|
@ -335,5 +336,34 @@ internal interface TangemPayDataModule {
|
|||
pollingUseCase = pollingUseCase,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideCancelTangemPayOrderUseCase(
|
||||
customerOrderRepository: CustomerOrderRepository,
|
||||
issueCardRepository: TangemPayIssueCardRepository,
|
||||
paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
|
||||
startTangemPayOrderPollingUseCase: StartTangemPayOrderPollingUseCase,
|
||||
): CancelTangemPayOrderUseCase {
|
||||
return CancelTangemPayOrderUseCase(
|
||||
customerOrderRepository = customerOrderRepository,
|
||||
paymentAccountStatusFetcher = paymentAccountStatusFetcher,
|
||||
startTangemPayOrderPollingUseCase = startTangemPayOrderPollingUseCase,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideCreateTariffPlanTransitionOrderUseCase(
|
||||
customerOrderRepository: CustomerOrderRepository,
|
||||
issueCardRepository: TangemPayIssueCardRepository,
|
||||
startTangemPayOrderPollingUseCase: StartTangemPayOrderPollingUseCase,
|
||||
appCoroutineScope: AppCoroutineScope,
|
||||
): CreateTariffPlanTransitionOrderUseCase {
|
||||
return CreateTariffPlanTransitionOrderUseCase(
|
||||
customerOrderRepository = customerOrderRepository,
|
||||
issueCardRepository = issueCardRepository,
|
||||
startTangemPayOrderPollingUseCase = startTangemPayOrderPollingUseCase,
|
||||
appCoroutineScope = appCoroutineScope,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -79,4 +79,10 @@ internal class DefaultCustomerOrderRepository @Inject constructor(
|
|||
OrderConverter.convert(result)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun cancelOrder(userWalletId: UserWalletId, orderId: String): Either<VisaApiError, Unit> {
|
||||
return requestHelper.performRequest(userWalletId) { authHeader ->
|
||||
tangemPayApi.cancelOrder(authHeader = authHeader, orderId = orderId)
|
||||
}.map {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
package com.tangem.data.pay.converter
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.datasource.api.pay.models.response.CustomerMeResponse
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlan
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class TangemPayTariffPlanConverterTest {
|
||||
|
||||
@Test
|
||||
fun `GIVEN null value WHEN convert THEN returns null`() {
|
||||
assertThat(TangemPayTariffPlanConverter.convert(null)).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN missing id WHEN convert THEN returns null`() {
|
||||
val value = tariffPlan(id = null)
|
||||
|
||||
assertThat(TangemPayTariffPlanConverter.convert(value)).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN missing name WHEN convert THEN returns null`() {
|
||||
val value = tariffPlan(name = null)
|
||||
|
||||
assertThat(TangemPayTariffPlanConverter.convert(value)).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN full valid plan WHEN convert THEN maps all fields`() {
|
||||
// GIVEN
|
||||
val value = tariffPlan(
|
||||
type = "PLUS",
|
||||
descriptionItems = listOf(
|
||||
CustomerMeResponse.DescriptionItem(
|
||||
type = "PLAN_RELATED",
|
||||
order = 2,
|
||||
title = "Title",
|
||||
body = "Body",
|
||||
),
|
||||
),
|
||||
images = listOf(CustomerMeResponse.Image(type = "MAIN", url = "https://img")),
|
||||
fees = listOf(
|
||||
CustomerMeResponse.Fee(
|
||||
type = "RECURRING",
|
||||
amount = BigDecimal("9.99"),
|
||||
currency = "USD",
|
||||
description = "Monthly",
|
||||
period = "MONTH",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = TangemPayTariffPlanConverter.convert(value)
|
||||
|
||||
// THEN
|
||||
val expected = TangemPayTariffPlan(
|
||||
id = PLAN_ID,
|
||||
type = TangemPayTariffPlan.Type.PLUS,
|
||||
name = PLAN_NAME,
|
||||
descriptionItems = listOf(
|
||||
TangemPayTariffPlan.DescriptionItem(
|
||||
section = TangemPayTariffPlan.Section.PLAN_RELATED,
|
||||
order = 2,
|
||||
title = "Title",
|
||||
body = "Body",
|
||||
),
|
||||
),
|
||||
images = listOf(
|
||||
TangemPayTariffPlan.Image(type = TangemPayTariffPlan.Image.Type.MAIN, url = "https://img"),
|
||||
),
|
||||
fees = listOf(
|
||||
TangemPayTariffPlan.Fee(
|
||||
type = TangemPayTariffPlan.Fee.Type.RECURRING,
|
||||
amount = BigDecimal("9.99"),
|
||||
currency = "USD",
|
||||
description = "Monthly",
|
||||
period = TangemPayTariffPlan.Fee.Period.MONTH,
|
||||
),
|
||||
),
|
||||
)
|
||||
assertThat(result).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN unknown enum strings and null optional fields WHEN convert THEN falls back to defaults`() {
|
||||
// GIVEN
|
||||
val value = tariffPlan(
|
||||
type = "SOMETHING_NEW",
|
||||
descriptionItems = listOf(
|
||||
CustomerMeResponse.DescriptionItem(type = "wat", order = null, title = "Title", body = null),
|
||||
),
|
||||
images = listOf(CustomerMeResponse.Image(type = null, url = "https://img")),
|
||||
fees = listOf(
|
||||
CustomerMeResponse.Fee(
|
||||
type = null,
|
||||
amount = BigDecimal.ONE,
|
||||
currency = null,
|
||||
description = null,
|
||||
period = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = TangemPayTariffPlanConverter.convert(value)
|
||||
|
||||
// THEN
|
||||
val expected = TangemPayTariffPlan(
|
||||
id = PLAN_ID,
|
||||
type = TangemPayTariffPlan.Type.UNKNOWN,
|
||||
name = PLAN_NAME,
|
||||
descriptionItems = listOf(
|
||||
TangemPayTariffPlan.DescriptionItem(
|
||||
section = TangemPayTariffPlan.Section.UNKNOWN,
|
||||
order = 0,
|
||||
title = "Title",
|
||||
body = "",
|
||||
),
|
||||
),
|
||||
images = listOf(
|
||||
TangemPayTariffPlan.Image(type = TangemPayTariffPlan.Image.Type.UNKNOWN, url = "https://img"),
|
||||
),
|
||||
fees = listOf(
|
||||
TangemPayTariffPlan.Fee(
|
||||
type = TangemPayTariffPlan.Fee.Type.UNKNOWN,
|
||||
amount = BigDecimal.ONE,
|
||||
currency = "",
|
||||
description = "",
|
||||
period = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
assertThat(result).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN nested items with missing required fields WHEN convert THEN filters them out`() {
|
||||
// GIVEN
|
||||
val value = tariffPlan(
|
||||
descriptionItems = listOf(
|
||||
CustomerMeResponse.DescriptionItem(type = "PLAN_RELATED", order = 1, title = null, body = "Body"),
|
||||
),
|
||||
images = listOf(CustomerMeResponse.Image(type = "MAIN", url = null)),
|
||||
fees = listOf(
|
||||
CustomerMeResponse.Fee(
|
||||
type = "FREE",
|
||||
amount = null,
|
||||
currency = "USD",
|
||||
description = "d",
|
||||
period = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = TangemPayTariffPlanConverter.convert(value)
|
||||
|
||||
// THEN
|
||||
assertThat(result?.descriptionItems).isEmpty()
|
||||
assertThat(result?.images).isEmpty()
|
||||
assertThat(result?.fees).isEmpty()
|
||||
}
|
||||
|
||||
private fun tariffPlan(
|
||||
id: String? = PLAN_ID,
|
||||
type: String? = "BASIC",
|
||||
name: String? = PLAN_NAME,
|
||||
descriptionItems: List<CustomerMeResponse.DescriptionItem>? = null,
|
||||
images: List<CustomerMeResponse.Image>? = null,
|
||||
fees: List<CustomerMeResponse.Fee>? = null,
|
||||
) = CustomerMeResponse.TariffPlan(
|
||||
id = id,
|
||||
type = type,
|
||||
name = name,
|
||||
descriptionItems = descriptionItems,
|
||||
images = images,
|
||||
fees = fees,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
const val PLAN_ID = "plan-1"
|
||||
const val PLAN_NAME = "Plus"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
package com.tangem.data.pay.repository
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.datasource.api.pay.models.response.CustomerMeResponse
|
||||
import com.tangem.datasource.api.pay.models.response.TariffPlanTransitionResponse
|
||||
import com.tangem.datasource.api.pay.models.response.TariffPlanTransitionsResponse
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlan
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlanTransition
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class DefaultTariffPlanTransitionsRepositoryTest {
|
||||
|
||||
private val tangemPayApi: TangemPayApi = mockk()
|
||||
private val requestHelper: TangemPayRequestPerformer = mockk()
|
||||
|
||||
private val repository = DefaultTariffPlanTransitionsRepository(
|
||||
tangemPayApi = tangemPayApi,
|
||||
requestHelper = requestHelper,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
coEvery {
|
||||
requestHelper.performRequest<Any>(userWalletId = any(), requestBlock = any())
|
||||
} coAnswers {
|
||||
val block = secondArg<suspend (String) -> ApiResponse<Any>>()
|
||||
when (val response = block(AUTH_HEADER)) {
|
||||
is ApiResponse.Success -> response.data.right()
|
||||
is ApiResponse.Error -> VisaApiError.Unspecified.left()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN backend error WHEN getTransitions THEN returns error`() = runTest {
|
||||
// GIVEN
|
||||
coEvery { tangemPayApi.getTariffPlanTransitions(any()) } returns
|
||||
ApiResponse.Error(ApiResponseError.NetworkException()) as ApiResponse<TariffPlanTransitionsResponse>
|
||||
|
||||
// WHEN
|
||||
val result = repository.getTransitions(USER_WALLET_ID)
|
||||
|
||||
// THEN
|
||||
assertThat(result.leftOrNull()).isEqualTo(VisaApiError.Unspecified)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN null result WHEN getTransitions THEN returns empty list`() = runTest {
|
||||
// GIVEN
|
||||
coEvery { tangemPayApi.getTariffPlanTransitions(any()) } returns
|
||||
ApiResponse.Success(TariffPlanTransitionsResponse(result = null))
|
||||
|
||||
// WHEN
|
||||
val result = repository.getTransitions(USER_WALLET_ID)
|
||||
|
||||
// THEN
|
||||
assertThat(result.getOrNull()).isEqualTo(emptyList<TangemPayTariffPlanTransition>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN valid transitions WHEN getTransitions THEN maps them to domain`() = runTest {
|
||||
// GIVEN
|
||||
coEvery { tangemPayApi.getTariffPlanTransitions(any()) } returns ApiResponse.Success(
|
||||
TariffPlanTransitionsResponse(
|
||||
result = listOf(
|
||||
TariffPlanTransitionResponse(type = "UPGRADE", tariffPlan = tariffPlan()),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = repository.getTransitions(USER_WALLET_ID)
|
||||
|
||||
// THEN
|
||||
val expected = listOf(
|
||||
TangemPayTariffPlanTransition(
|
||||
type = TangemPayTariffPlanTransition.Type.UPGRADE,
|
||||
plan = TangemPayTariffPlan(
|
||||
id = PLAN_ID,
|
||||
type = TangemPayTariffPlan.Type.PLUS,
|
||||
name = PLAN_NAME,
|
||||
descriptionItems = emptyList(),
|
||||
images = emptyList(),
|
||||
fees = emptyList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
assertThat(result.getOrNull()).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN a transition with unconvertible plan WHEN getTransitions THEN it is filtered out`() = runTest {
|
||||
// GIVEN
|
||||
coEvery { tangemPayApi.getTariffPlanTransitions(any()) } returns ApiResponse.Success(
|
||||
TariffPlanTransitionsResponse(
|
||||
result = listOf(
|
||||
TariffPlanTransitionResponse(type = "UPGRADE", tariffPlan = null),
|
||||
TariffPlanTransitionResponse(type = "DOWNGRADE", tariffPlan = tariffPlan(id = null)),
|
||||
TariffPlanTransitionResponse(type = "ACTIVATION", tariffPlan = tariffPlan()),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = repository.getTransitions(USER_WALLET_ID)
|
||||
|
||||
// THEN
|
||||
val transitions = result.getOrNull().orEmpty()
|
||||
assertThat(transitions.map { it.type })
|
||||
.containsExactly(TangemPayTariffPlanTransition.Type.ACTIVATION)
|
||||
}
|
||||
|
||||
private fun tariffPlan(id: String? = PLAN_ID) = CustomerMeResponse.TariffPlan(
|
||||
id = id,
|
||||
type = "PLUS",
|
||||
name = PLAN_NAME,
|
||||
descriptionItems = null,
|
||||
images = null,
|
||||
fees = null,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
val USER_WALLET_ID = UserWalletId("aabbcc112233")
|
||||
const val AUTH_HEADER = "auth-header"
|
||||
const val PLAN_ID = "plan-plus"
|
||||
const val PLAN_NAME = "Plus"
|
||||
}
|
||||
}
|
||||
|
|
@ -40,4 +40,6 @@ interface CustomerOrderRepository {
|
|||
targetTariffPlanId: String? = null,
|
||||
transitionType: TangemPayTariffPlanTransition.Type? = null,
|
||||
): Either<VisaApiError, Order>
|
||||
|
||||
suspend fun cancelOrder(userWalletId: UserWalletId, orderId: String): Either<VisaApiError, Unit>
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.tangem.domain.pay.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
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.CustomerOrderRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
|
||||
class CancelTangemPayOrderUseCase(
|
||||
private val customerOrderRepository: CustomerOrderRepository,
|
||||
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher,
|
||||
private val startTangemPayOrderPollingUseCase: StartTangemPayOrderPollingUseCase,
|
||||
) {
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, orderId: String): Either<VisaApiError, Unit> = either {
|
||||
customerOrderRepository.cancelOrder(userWalletId, orderId).bind()
|
||||
|
||||
paymentAccountStatusFetcher.invoke(userWalletId)
|
||||
|
||||
startTangemPayOrderPollingUseCase(
|
||||
order = TangemPayOrderInfo(orderId = orderId, orderStatus = OrderStatus.PROCESSING),
|
||||
userWalletId = userWalletId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.tangem.domain.pay.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlanTransition
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
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.CustomerOrderRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayIssueCardRepository
|
||||
import com.tangem.domain.pay.util.OrderResolver
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.utils.coroutines.AppCoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
class CreateTariffPlanTransitionOrderUseCase(
|
||||
private val customerOrderRepository: CustomerOrderRepository,
|
||||
private val issueCardRepository: TangemPayIssueCardRepository,
|
||||
private val startTangemPayOrderPollingUseCase: StartTangemPayOrderPollingUseCase,
|
||||
private val appCoroutineScope: AppCoroutineScope,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
targetTariffPlanId: String,
|
||||
transitionType: TangemPayTariffPlanTransition.Type,
|
||||
): Either<VisaApiError, Unit> = either {
|
||||
val orderType = OrderType.TARIFF_PLAN_TRANSITION
|
||||
|
||||
val activeOrders = customerOrderRepository.findOrders(
|
||||
userWalletId = userWalletId,
|
||||
types = setOf(orderType),
|
||||
statuses = OrderStatus.activeStatuses,
|
||||
).bind()
|
||||
|
||||
val planTransitionOrder = OrderResolver.selectActive(
|
||||
orders = activeOrders,
|
||||
type = orderType,
|
||||
)
|
||||
|
||||
val order = planTransitionOrder ?: customerOrderRepository.createOrder(
|
||||
userWalletId = userWalletId,
|
||||
type = orderType,
|
||||
specificationName = null,
|
||||
idempotencyKey = UUID.randomUUID().toString(),
|
||||
targetTariffPlanId = targetTariffPlanId,
|
||||
transitionType = transitionType,
|
||||
).bind()
|
||||
|
||||
issueCardRepository.storeIssueOrderId(userWalletId = userWalletId, orderId = order.id)
|
||||
|
||||
appCoroutineScope.launch {
|
||||
startTangemPayOrderPollingUseCase(
|
||||
order = TangemPayOrderInfo(orderId = order.id, orderStatus = order.status),
|
||||
userWalletId = userWalletId,
|
||||
onTerminalReached = { issueCardRepository.removeIssueOrderId(userWalletId, order.id) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package com.tangem.domain.pay.usecase
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
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.CustomerOrderRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class CancelTangemPayOrderUseCaseTest {
|
||||
|
||||
private val customerOrderRepository: CustomerOrderRepository = mockk()
|
||||
private val paymentAccountStatusFetcher: PaymentAccountStatusFetcher = mockk()
|
||||
private val startTangemPayOrderPollingUseCase: StartTangemPayOrderPollingUseCase = mockk()
|
||||
|
||||
private val useCase = CancelTangemPayOrderUseCase(
|
||||
customerOrderRepository = customerOrderRepository,
|
||||
paymentAccountStatusFetcher = paymentAccountStatusFetcher,
|
||||
startTangemPayOrderPollingUseCase = startTangemPayOrderPollingUseCase,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN cancelOrder fails WHEN invoke THEN returns Left and skips fetch and polling`() = runTest {
|
||||
// GIVEN
|
||||
coEvery { customerOrderRepository.cancelOrder(USER_WALLET_ID, ORDER_ID) } returns VisaApiError.Unspecified.left()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(USER_WALLET_ID, ORDER_ID)
|
||||
|
||||
// THEN
|
||||
assertThat(result.leftOrNull()).isEqualTo(VisaApiError.Unspecified)
|
||||
coVerify(exactly = 0) { paymentAccountStatusFetcher.invoke(any<UserWalletId>()) }
|
||||
coVerify(exactly = 0) { startTangemPayOrderPollingUseCase(any(), any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN cancelOrder succeeds WHEN invoke THEN fetches status and starts polling with PROCESSING order`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
coEvery { customerOrderRepository.cancelOrder(USER_WALLET_ID, ORDER_ID) } returns Unit.right()
|
||||
coEvery { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) } returns Unit.right()
|
||||
coEvery { startTangemPayOrderPollingUseCase(any(), any(), any()) } returns true
|
||||
|
||||
// WHEN
|
||||
val result = useCase(USER_WALLET_ID, ORDER_ID)
|
||||
|
||||
// THEN
|
||||
assertThat(result.isRight()).isTrue()
|
||||
coVerify(exactly = 1) { paymentAccountStatusFetcher.invoke(USER_WALLET_ID) }
|
||||
coVerify(exactly = 1) {
|
||||
startTangemPayOrderPollingUseCase(
|
||||
TangemPayOrderInfo(orderId = ORDER_ID, orderStatus = OrderStatus.PROCESSING),
|
||||
USER_WALLET_ID,
|
||||
any(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val USER_WALLET_ID = UserWalletId("aabbcc112233")
|
||||
const val ORDER_ID = "order-test-1"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
package com.tangem.domain.pay.usecase
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlanTransition
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.Order
|
||||
import com.tangem.domain.pay.model.OrderStatus
|
||||
import com.tangem.domain.pay.model.OrderStep
|
||||
import com.tangem.domain.pay.model.OrderType
|
||||
import com.tangem.domain.pay.repository.CustomerOrderRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayIssueCardRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.test.core.TestAppCoroutineScope
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class CreateTariffPlanTransitionOrderUseCaseTest {
|
||||
|
||||
private val customerOrderRepository: CustomerOrderRepository = mockk()
|
||||
private val issueCardRepository: TangemPayIssueCardRepository = mockk(relaxed = true)
|
||||
private val startTangemPayOrderPollingUseCase: StartTangemPayOrderPollingUseCase = mockk(relaxed = true)
|
||||
|
||||
private val useCase = CreateTariffPlanTransitionOrderUseCase(
|
||||
customerOrderRepository = customerOrderRepository,
|
||||
issueCardRepository = issueCardRepository,
|
||||
startTangemPayOrderPollingUseCase = startTangemPayOrderPollingUseCase,
|
||||
appCoroutineScope = TestAppCoroutineScope(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN findOrders fails WHEN invoke THEN returns Left and skips create and store`() = runTest {
|
||||
// GIVEN
|
||||
coEvery {
|
||||
customerOrderRepository.findOrders(USER_WALLET_ID, ACTIVE_TRANSITION_TYPES, OrderStatus.activeStatuses)
|
||||
} returns VisaApiError.Unspecified.left()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(USER_WALLET_ID, TARGET_PLAN_ID, TangemPayTariffPlanTransition.Type.UPGRADE)
|
||||
|
||||
// THEN
|
||||
assertThat(result.leftOrNull()).isEqualTo(VisaApiError.Unspecified)
|
||||
coVerify(exactly = 0) {
|
||||
customerOrderRepository.createOrder(any(), any(), any(), any(), any(), any())
|
||||
}
|
||||
coVerify(exactly = 0) { issueCardRepository.storeIssueOrderId(any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN active transition order exists WHEN invoke THEN reuses it without calling createOrder`() = runTest {
|
||||
// GIVEN
|
||||
val existing = order(id = "existing", status = OrderStatus.PROCESSING)
|
||||
coEvery {
|
||||
customerOrderRepository.findOrders(USER_WALLET_ID, ACTIVE_TRANSITION_TYPES, OrderStatus.activeStatuses)
|
||||
} returns listOf(existing).right()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(USER_WALLET_ID, TARGET_PLAN_ID, TangemPayTariffPlanTransition.Type.UPGRADE)
|
||||
|
||||
// THEN
|
||||
assertThat(result.isRight()).isTrue()
|
||||
coVerify(exactly = 0) {
|
||||
customerOrderRepository.createOrder(any(), any(), any(), any(), any(), any())
|
||||
}
|
||||
coVerify(exactly = 1) { issueCardRepository.storeIssueOrderId(USER_WALLET_ID, existing.id) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no active order AND createOrder fails WHEN invoke THEN returns Left and skips store`() = runTest {
|
||||
// GIVEN
|
||||
coEvery {
|
||||
customerOrderRepository.findOrders(USER_WALLET_ID, ACTIVE_TRANSITION_TYPES, OrderStatus.activeStatuses)
|
||||
} returns emptyList<Order>().right()
|
||||
coEvery {
|
||||
customerOrderRepository.createOrder(
|
||||
userWalletId = USER_WALLET_ID,
|
||||
type = OrderType.TARIFF_PLAN_TRANSITION,
|
||||
specificationName = null,
|
||||
targetTariffPlanId = TARGET_PLAN_ID,
|
||||
transitionType = TangemPayTariffPlanTransition.Type.UPGRADE,
|
||||
idempotencyKey = any(),
|
||||
)
|
||||
} returns VisaApiError.Unspecified.left()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(USER_WALLET_ID, TARGET_PLAN_ID, TangemPayTariffPlanTransition.Type.UPGRADE)
|
||||
|
||||
// THEN
|
||||
assertThat(result.leftOrNull()).isEqualTo(VisaApiError.Unspecified)
|
||||
coVerify(exactly = 0) { issueCardRepository.storeIssueOrderId(any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no active order AND createOrder succeeds WHEN invoke THEN stores the new order id`() = runTest {
|
||||
// GIVEN
|
||||
val newOrder = order(id = "new", status = OrderStatus.NEW)
|
||||
coEvery {
|
||||
customerOrderRepository.findOrders(USER_WALLET_ID, ACTIVE_TRANSITION_TYPES, OrderStatus.activeStatuses)
|
||||
} returns emptyList<Order>().right()
|
||||
coEvery {
|
||||
customerOrderRepository.createOrder(
|
||||
userWalletId = USER_WALLET_ID,
|
||||
type = OrderType.TARIFF_PLAN_TRANSITION,
|
||||
specificationName = null,
|
||||
targetTariffPlanId = TARGET_PLAN_ID,
|
||||
transitionType = TangemPayTariffPlanTransition.Type.UPGRADE,
|
||||
idempotencyKey = any(),
|
||||
)
|
||||
} returns newOrder.right()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(USER_WALLET_ID, TARGET_PLAN_ID, TangemPayTariffPlanTransition.Type.UPGRADE)
|
||||
|
||||
// THEN
|
||||
assertThat(result.isRight()).isTrue()
|
||||
coVerify(exactly = 1) { issueCardRepository.storeIssueOrderId(USER_WALLET_ID, newOrder.id) }
|
||||
}
|
||||
|
||||
private fun order(id: String, status: OrderStatus): Order = Order(
|
||||
id = id,
|
||||
customerId = "customer",
|
||||
type = OrderType.TARIFF_PLAN_TRANSITION,
|
||||
status = status,
|
||||
step = OrderStep.UNKNOWN,
|
||||
stepChangeCode = null,
|
||||
productInstanceId = null,
|
||||
paymentAccountId = null,
|
||||
cardId = null,
|
||||
toTariffPlanId = TARGET_PLAN_ID,
|
||||
withdrawTxHash = null,
|
||||
createdAt = null,
|
||||
updatedAt = null,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
val USER_WALLET_ID = UserWalletId("aabbcc112233")
|
||||
const val TARGET_PLAN_ID = "plan-plus"
|
||||
val ACTIVE_TRANSITION_TYPES = setOf(OrderType.TARIFF_PLAN_TRANSITION)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
package com.tangem.domain.pay.usecase
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.domain.models.account.TangemPayCustomerTariffPlan
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlan
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlanState
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlanTransition
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.Order
|
||||
import com.tangem.domain.pay.model.OrderStatus
|
||||
import com.tangem.domain.pay.model.OrderStep
|
||||
import com.tangem.domain.pay.model.OrderType
|
||||
import com.tangem.domain.pay.repository.CustomerOrderRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
internal class GetTangemPayTariffPlanStateUseCaseTest {
|
||||
|
||||
private val customerOrderRepository: CustomerOrderRepository = mockk()
|
||||
private val getTariffPlanTransitions: GetTangemPayTariffPlanTransitionsUseCase = mockk()
|
||||
|
||||
private val useCase = GetTangemPayTariffPlanStateUseCase(
|
||||
customerOrderRepository = customerOrderRepository,
|
||||
getTariffPlanTransitions = getTariffPlanTransitions,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN no active transition order WHEN invoke THEN state has no order`() = runTest {
|
||||
// GIVEN
|
||||
coEvery { customerOrderRepository.findOrders(USER_WALLET_ID, TRANSITION_TYPES, ACTIVE) } returns
|
||||
emptyList<Order>().right()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(USER_WALLET_ID, CUSTOMER_TARIFF)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(TangemPayTariffPlanState(tariff = CUSTOMER_TARIFF, order = null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN findOrders fails WHEN invoke THEN state has no order`() = runTest {
|
||||
// GIVEN
|
||||
coEvery { customerOrderRepository.findOrders(USER_WALLET_ID, TRANSITION_TYPES, ACTIVE) } returns
|
||||
VisaApiError.Unspecified.left()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(USER_WALLET_ID, CUSTOMER_TARIFF)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(TangemPayTariffPlanState(tariff = CUSTOMER_TARIFF, order = null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN active order with non-awaiting step WHEN invoke THEN order step is Unknown`() = runTest {
|
||||
// GIVEN
|
||||
val order = order(step = OrderStep.UNKNOWN)
|
||||
coEvery { customerOrderRepository.findOrders(USER_WALLET_ID, TRANSITION_TYPES, ACTIVE) } returns
|
||||
listOf(order).right()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(USER_WALLET_ID, CUSTOMER_TARIFF)
|
||||
|
||||
// THEN
|
||||
val expected = TangemPayTariffPlanState(
|
||||
tariff = CUSTOMER_TARIFF,
|
||||
order = TangemPayTariffPlanState.Order(
|
||||
orderId = ORDER_ID,
|
||||
step = TangemPayTariffPlanState.OrderStep.Unknown,
|
||||
),
|
||||
)
|
||||
assertThat(result).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN awaiting-deposit order but no matching transition WHEN invoke THEN order step is Unknown`() = runTest {
|
||||
// GIVEN
|
||||
val order = order(step = OrderStep.AWAITING_DEPOSIT)
|
||||
coEvery { customerOrderRepository.findOrders(USER_WALLET_ID, TRANSITION_TYPES, ACTIVE) } returns
|
||||
listOf(order).right()
|
||||
coEvery { getTariffPlanTransitions(USER_WALLET_ID) } returns emptyList<TangemPayTariffPlanTransition>().right()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(USER_WALLET_ID, CUSTOMER_TARIFF)
|
||||
|
||||
// THEN
|
||||
val expected = TangemPayTariffPlanState(
|
||||
tariff = CUSTOMER_TARIFF,
|
||||
order = TangemPayTariffPlanState.Order(
|
||||
orderId = ORDER_ID,
|
||||
step = TangemPayTariffPlanState.OrderStep.Unknown,
|
||||
),
|
||||
)
|
||||
assertThat(result).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN awaiting-deposit order with matching transition WHEN invoke THEN order step is AwaitingDeposit`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
val order = order(step = OrderStep.AWAITING_DEPOSIT)
|
||||
val transition = TangemPayTariffPlanTransition(
|
||||
type = TangemPayTariffPlanTransition.Type.UPGRADE,
|
||||
plan = TARGET_PLAN,
|
||||
)
|
||||
coEvery { customerOrderRepository.findOrders(USER_WALLET_ID, TRANSITION_TYPES, ACTIVE) } returns
|
||||
listOf(order).right()
|
||||
coEvery { getTariffPlanTransitions(USER_WALLET_ID) } returns listOf(transition).right()
|
||||
|
||||
// WHEN
|
||||
val result = useCase(USER_WALLET_ID, CUSTOMER_TARIFF)
|
||||
|
||||
// THEN
|
||||
val expected = TangemPayTariffPlanState(
|
||||
tariff = CUSTOMER_TARIFF,
|
||||
order = TangemPayTariffPlanState.Order(
|
||||
orderId = ORDER_ID,
|
||||
step = TangemPayTariffPlanState.OrderStep.AwaitingDeposit(
|
||||
fromPlan = CURRENT_PLAN,
|
||||
toPlan = TARGET_PLAN,
|
||||
),
|
||||
),
|
||||
)
|
||||
assertThat(result).isEqualTo(expected)
|
||||
}
|
||||
|
||||
private fun order(step: OrderStep): Order = Order(
|
||||
id = ORDER_ID,
|
||||
customerId = "customer",
|
||||
type = OrderType.TARIFF_PLAN_TRANSITION,
|
||||
status = OrderStatus.PROCESSING,
|
||||
step = step,
|
||||
stepChangeCode = null,
|
||||
productInstanceId = null,
|
||||
paymentAccountId = null,
|
||||
cardId = null,
|
||||
toTariffPlanId = TARGET_PLAN_ID,
|
||||
withdrawTxHash = null,
|
||||
createdAt = null,
|
||||
updatedAt = null,
|
||||
)
|
||||
|
||||
private companion object {
|
||||
val USER_WALLET_ID = UserWalletId("aabbcc112233")
|
||||
const val ORDER_ID = "order-test-1"
|
||||
const val TARGET_PLAN_ID = "plan-plus"
|
||||
val TRANSITION_TYPES = setOf(OrderType.TARIFF_PLAN_TRANSITION)
|
||||
val ACTIVE = OrderStatus.activeStatuses
|
||||
|
||||
val CURRENT_PLAN = TangemPayTariffPlan(
|
||||
id = "plan-basic",
|
||||
type = TangemPayTariffPlan.Type.BASIC,
|
||||
name = "Basic",
|
||||
descriptionItems = emptyList(),
|
||||
)
|
||||
val TARGET_PLAN = TangemPayTariffPlan(
|
||||
id = TARGET_PLAN_ID,
|
||||
type = TangemPayTariffPlan.Type.PLUS,
|
||||
name = "Plus",
|
||||
descriptionItems = emptyList(),
|
||||
)
|
||||
val CUSTOMER_TARIFF = TangemPayCustomerTariffPlan(
|
||||
status = TangemPayCustomerTariffPlan.Status.ACTIVE,
|
||||
plan = CURRENT_PLAN,
|
||||
nextBillingAt = null,
|
||||
pendingPlan = null,
|
||||
pendingTransitionAt = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,11 +8,16 @@ import com.tangem.core.ui.ds.image.TangemIconUM
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.themedColor
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.getJavaCurrencyByCode
|
||||
import com.tangem.core.ui.format.bigdecimal.optionalDecimals
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.generated.icons.Icons
|
||||
import com.tangem.core.ui.res.generated.icons.ic_document_20
|
||||
import com.tangem.domain.models.account.PaymentAccountStatusValue
|
||||
import com.tangem.domain.models.account.TangemPayCustomerTariffPlan
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlan
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlanState
|
||||
import com.tangem.domain.models.pay.TangemPayCard
|
||||
import com.tangem.domain.models.pay.TangemPayCardFrozenState
|
||||
import com.tangem.domain.models.pay.TangemPayCardState
|
||||
|
|
@ -75,7 +80,7 @@ internal class TangemPayDetailsStateFactory(
|
|||
onBackClick = onBack,
|
||||
onOpenMenu = onOpenMenu,
|
||||
items = getTopBarMenuItems(),
|
||||
itemsV2 = getTopBarMenuItemsV2(tariffPlan = status.tariffPlan?.tariff),
|
||||
itemsV2 = getTopBarMenuItemsV2(tariffPlan = status.tariffPlan),
|
||||
),
|
||||
pullToRefreshConfig = PullToRefreshConfig(
|
||||
isRefreshing = false,
|
||||
|
|
@ -106,13 +111,15 @@ internal class TangemPayDetailsStateFactory(
|
|||
),
|
||||
isBalanceHidden = false,
|
||||
addToWalletBlockState = null,
|
||||
errorNotificationConfig = when (status.error) {
|
||||
errorNotificationConfig = createErrorConfig(status.error) ?: createAwaitingDepositConfig(status.tariffPlan),
|
||||
accountDeactivatedNotificationConfig = null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createErrorConfig(error: PaymentAccountStatusValue.Error?): NotificationConfig? = when (error) {
|
||||
null -> null
|
||||
PaymentAccountStatusValue.Error.NotSynced -> createRenewSessionNotificationConfig(isRedesignEnabled)
|
||||
else -> createAccountUnavailableConfig(isRedesignEnabled)
|
||||
},
|
||||
accountDeactivatedNotificationConfig = null,
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<TangemPayCard>.resolveProgressBanner(): CardsProgressBannerUM? = when {
|
||||
|
|
@ -167,6 +174,30 @@ internal class TangemPayDetailsStateFactory(
|
|||
},
|
||||
)
|
||||
|
||||
// TODO v_rodionov: strings hardcoded for now - wait for localization
|
||||
private fun createAwaitingDepositConfig(tariffPlan: TangemPayTariffPlanState?): NotificationConfig? {
|
||||
val order = tariffPlan?.order ?: return null
|
||||
|
||||
val orderStep = order.step
|
||||
if (orderStep !is TangemPayTariffPlanState.OrderStep.AwaitingDeposit) return null
|
||||
|
||||
val recurringFee = orderStep.toPlan.fees.find { it.type == TangemPayTariffPlan.Fee.Type.RECURRING }
|
||||
val feeText = recurringFee?.let { fee ->
|
||||
val currency = getJavaCurrencyByCode(fee.currency)
|
||||
fee.amount.format { fiat(currency.currencyCode, currency.symbol).optionalDecimals() }
|
||||
}
|
||||
val title = if (feeText != null) "Top-up your account on $feeText" else "Top-up your account"
|
||||
return NotificationConfig(
|
||||
title = stringReference(title),
|
||||
subtitle = stringReference("To pay monthly fee for plan and start use card"),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = stringReference("Cancel ${orderStep.toPlan.name}, move to ${orderStep.fromPlan.name}"),
|
||||
onClick = { intents.onCancelPlusTransition(order.orderId) },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun createRenewSessionNotificationConfig(isRedesignEnabled: Boolean) = NotificationConfig(
|
||||
title = resourceReference(R.string.tangempay_sync_needed_title),
|
||||
subtitle = resourceReference(R.string.tangempay_sync_needed_body),
|
||||
|
|
@ -242,20 +273,18 @@ internal class TangemPayDetailsStateFactory(
|
|||
}.toImmutableList()
|
||||
}
|
||||
|
||||
private fun getTopBarMenuItemsV2(
|
||||
tariffPlan: TangemPayCustomerTariffPlan?,
|
||||
): ImmutableList<TangemPayDropDownItemUM> {
|
||||
private fun getTopBarMenuItemsV2(tariffPlan: TangemPayTariffPlanState?): ImmutableList<TangemPayDropDownItemUM> {
|
||||
return buildList {
|
||||
if (isTiersPlusPlanEnabled && tariffPlan != null) {
|
||||
add(
|
||||
TangemPayDropDownItemUM(
|
||||
title = resourceReference(R.string.tangempay_current_plan_title),
|
||||
onClick = { intents.onClickCurrentPlan(tariffPlan) },
|
||||
onClick = { intents.onClickCurrentPlan(tariffPlan.tariff) },
|
||||
icon = TangemIconUM.Icon(
|
||||
iconRes = CoreUiR.drawable.ic_information_24,
|
||||
tintReference = { TangemTheme.colors3.icon.primary },
|
||||
),
|
||||
subtitle = stringReference(tariffPlan.plan.name),
|
||||
subtitle = stringReference(tariffPlan.tariff.plan.name),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import com.tangem.domain.pay.model.TangemPayTopUpData
|
|||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository
|
||||
import com.tangem.domain.pay.repository.TangemPayWithdrawRepository
|
||||
import com.tangem.domain.pay.usecase.CancelTangemPayOrderUseCase
|
||||
import com.tangem.domain.pay.usecase.GetCustomerOffersUseCase
|
||||
import com.tangem.domain.pay.usecase.ProduceTangemPayInitialDataUseCase
|
||||
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
|
||||
|
|
@ -87,6 +88,7 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
private val produceTangemPayInitialDataUseCase: ProduceTangemPayInitialDataUseCase,
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
private val getCustomerOffers: GetCustomerOffersUseCase,
|
||||
private val cancelTangemPayOrderUseCase: CancelTangemPayOrderUseCase,
|
||||
) : Model(),
|
||||
TangemPayTxHistoryUiActions,
|
||||
TangemPayDetailIntents,
|
||||
|
|
@ -170,6 +172,15 @@ internal class TangemPayDetailsModel @Inject constructor(
|
|||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
override fun onCancelPlusTransition(orderId: String) {
|
||||
modelScope.launch {
|
||||
cancelTangemPayOrderUseCase(userWalletId = userWalletId, orderId = orderId)
|
||||
.onLeft {
|
||||
uiMessageSender.send(TangemPayMessagesFactory.createGenericError())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onStart() {
|
||||
onRefreshSwipe(refreshState = ShowRefreshState(false))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,16 @@ import com.tangem.core.decompose.di.ModelScoped
|
|||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlan
|
||||
import com.tangem.domain.models.account.TangemPayTariffPlanTransition
|
||||
import com.tangem.domain.pay.usecase.CreateTariffPlanTransitionOrderUseCase
|
||||
import com.tangem.domain.pay.usecase.GetTangemPayTariffPlanTransitionsUseCase
|
||||
import com.tangem.features.tangempay.details.impl.R
|
||||
import com.tangem.features.tangempay.navigation.TangemPayAccountDetailsInnerRoute
|
||||
import com.tangem.features.tangempay.utils.TangemPayMessagesFactory
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -27,6 +31,8 @@ internal class TangemPaySelectPlanModel @Inject constructor(
|
|||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val getTransitions: GetTangemPayTariffPlanTransitionsUseCase,
|
||||
private val createTransitionOrder: CreateTariffPlanTransitionOrderUseCase,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
) : Model() {
|
||||
|
||||
private val params = paramsContainer.require<TangemPaySelectPlanComponent.Params>()
|
||||
|
|
@ -34,6 +40,7 @@ internal class TangemPaySelectPlanModel @Inject constructor(
|
|||
private var transitions: List<TangemPayTariffPlanTransition> = emptyList()
|
||||
private var selectedIndex: Int = 0
|
||||
private var isConfirm: Boolean = false
|
||||
private var isProcessing: Boolean = false
|
||||
|
||||
val state: StateFlow<TangemPaySelectPlanUM>
|
||||
field = MutableStateFlow(buildState())
|
||||
|
|
@ -73,6 +80,7 @@ internal class TangemPaySelectPlanModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun onBackClick() {
|
||||
if (isProcessing) return
|
||||
if (isConfirm) {
|
||||
isConfirm = false
|
||||
state.update { buildState() }
|
||||
|
|
@ -81,6 +89,29 @@ internal class TangemPaySelectPlanModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun onConfirmClick() {
|
||||
val transition = transitions.getOrNull(selectedIndex) ?: return
|
||||
if (transition.type != TangemPayTariffPlanTransition.Type.UPGRADE) return
|
||||
if (isProcessing) return
|
||||
|
||||
isProcessing = true
|
||||
state.update { buildState() }
|
||||
modelScope.launch {
|
||||
createTransitionOrder(
|
||||
userWalletId = params.userWalletId,
|
||||
targetTariffPlanId = transition.plan.id,
|
||||
transitionType = transition.type,
|
||||
).fold(
|
||||
ifRight = { router.popTo(TangemPayAccountDetailsInnerRoute.AccountDetails) },
|
||||
ifLeft = {
|
||||
isProcessing = false
|
||||
state.update { buildState() }
|
||||
uiMessageSender.send(message = TangemPayMessagesFactory.createGenericError())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildState(showPlanCompare: Boolean = false): TangemPaySelectPlanUM = TangemPaySelectPlanUM(
|
||||
topBarTitle = if (isConfirm) {
|
||||
resourceReference(R.string.tangempay_select_plan_confirm_title)
|
||||
|
|
@ -138,8 +169,9 @@ internal class TangemPaySelectPlanModel @Inject constructor(
|
|||
R.string.tangempay_select_plan_btn_downgrade
|
||||
},
|
||||
),
|
||||
isProcessing = isProcessing,
|
||||
onCancelClick = ::onBackClick,
|
||||
onConfirmClick = {},
|
||||
onConfirmClick = ::onConfirmClick,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +219,6 @@ internal class TangemPaySelectPlanModel @Inject constructor(
|
|||
private val ALLOWED_TYPES = setOf(
|
||||
TangemPayTariffPlanTransition.Type.UPGRADE,
|
||||
TangemPayTariffPlanTransition.Type.DOWNGRADE,
|
||||
TangemPayTariffPlanTransition.Type.ACTIVATION, // TODO v_rodionov: Only for test, must be removed in future
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -250,6 +250,7 @@ private fun ConfirmFooter(content: TangemPaySelectPlanUM.Content.Confirm, modifi
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
variant = TangemButton.Variant.Secondary,
|
||||
size = TangemButton.Size.X12,
|
||||
isEnabled = !content.isProcessing,
|
||||
text = resourceReference(R.string.tangempay_select_plan_btn_cancel),
|
||||
onClick = content.onCancelClick,
|
||||
)
|
||||
|
|
@ -257,6 +258,7 @@ private fun ConfirmFooter(content: TangemPaySelectPlanUM.Content.Confirm, modifi
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
variant = TangemButton.Variant.Primary,
|
||||
size = TangemButton.Size.X12,
|
||||
isLoading = content.isProcessing,
|
||||
text = content.confirmButtonText,
|
||||
onClick = content.onConfirmClick,
|
||||
)
|
||||
|
|
@ -344,6 +346,7 @@ private fun previewState(isConfirm: Boolean) = TangemPaySelectPlanUM(
|
|||
),
|
||||
),
|
||||
confirmButtonText = stringReference("Upgrade plan"),
|
||||
isProcessing = false,
|
||||
onCancelClick = {},
|
||||
onConfirmClick = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ internal data class TangemPaySelectPlanUM(
|
|||
val title: TextReference,
|
||||
val points: ImmutableList<PointUM>,
|
||||
val confirmButtonText: TextReference,
|
||||
val isProcessing: Boolean,
|
||||
val onCancelClick: () -> Unit,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : Content
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ internal interface TangemPayDetailIntents {
|
|||
fun onClickWithdraw()
|
||||
fun onClickTermsAndLimits()
|
||||
fun onClickCurrentPlan(tariffPlan: TangemPayCustomerTariffPlan)
|
||||
fun onCancelPlusTransition(orderId: String)
|
||||
fun onCardClick(cardId: String)
|
||||
fun onAddCardClick()
|
||||
fun onRemoveAccount()
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ internal class TangemPayDetailsModelTest {
|
|||
produceTangemPayInitialDataUseCase = mockk(relaxed = true),
|
||||
onboardingRepository = mockk(relaxed = true),
|
||||
getCustomerOffers = mockk(relaxed = true),
|
||||
cancelTangemPayOrderUseCase = mockk(relaxed = true),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue