Updated on 2026-08-14
This commit is contained in:
parent
b83b9d4ef6
commit
9db2ebaef4
21 changed files with 279 additions and 20 deletions
|
|
@ -38,6 +38,10 @@ data class CustomerInfo(
|
|||
/** Transitional single-card accessor — returns the first card, or null if none. */
|
||||
val cardInfo: CardInfo? get() = cards.firstOrNull()
|
||||
|
||||
/** Card-level product instances only (excludes the VA ACCOUNT instance). */
|
||||
val cardProductInstances: List<ProductInstance>
|
||||
get() = productInstances.filter { it.specificationDataType == ProductInstance.SpecificationDataType.CARD }
|
||||
|
||||
enum class State {
|
||||
NEW,
|
||||
ACTIVE,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface OnboardingRepository {
|
||||
|
||||
suspend fun validateDeeplink(link: String): Either<UniversalError, Boolean>
|
||||
|
|
@ -30,6 +31,16 @@ interface OnboardingRepository {
|
|||
|
||||
suspend fun getOrderId(userWalletId: UserWalletId): String?
|
||||
|
||||
/** Creates a Virtual Account on-ramp order (VA MVP0, TWI-1638); returns the created order id. */
|
||||
suspend fun createVirtualAccountOrder(
|
||||
userWalletId: UserWalletId,
|
||||
paymentAccountAddress: String,
|
||||
): Either<VisaApiError, String>
|
||||
|
||||
suspend fun getVirtualAccountOrderId(userWalletId: UserWalletId): String?
|
||||
|
||||
suspend fun storeVirtualAccountOrderId(userWalletId: UserWalletId, vaOrderId: String)
|
||||
|
||||
suspend fun hasTangemPayInWallet(userWalletId: UserWalletId): Either<VisaApiError, Boolean>
|
||||
|
||||
suspend fun checkCustomerEligibility(): List<TangemPayEligibilityType>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
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.model.OrderStatus
|
||||
import com.tangem.domain.pay.model.TangemPayOrderInfo
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
|
||||
/**
|
||||
* Creates the Virtual Account on-ramp order (VA MVP0, TWI-1638) and persists the returned id as `vaOrderId`.
|
||||
*
|
||||
* Idempotent: if an order id was already stored for the wallet, it is returned without hitting the network.
|
||||
* Otherwise creates the order (`ACCOUNT_ISSUE_VIRTUAL_RAIN`) and stores the returned id.
|
||||
*
|
||||
* @property onboardingRepository resolves the customer wallet address, creates the order, and persists the id.
|
||||
*/
|
||||
class CreateVirtualAccountOrderUseCase(
|
||||
private val onboardingRepository: OnboardingRepository,
|
||||
private val pollingUseCase: StartTangemPayOrderPollingUseCase,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
paymentAccountAddress: String,
|
||||
): Either<VisaApiError, Unit> = either {
|
||||
onboardingRepository.getVirtualAccountOrderId(userWalletId)
|
||||
?: run {
|
||||
val vaOrderId = onboardingRepository.createVirtualAccountOrder(
|
||||
userWalletId = userWalletId,
|
||||
paymentAccountAddress = paymentAccountAddress,
|
||||
).bind()
|
||||
onboardingRepository.storeVirtualAccountOrderId(userWalletId = userWalletId, vaOrderId = vaOrderId)
|
||||
pollingUseCase.invoke(
|
||||
order = TangemPayOrderInfo(orderId = vaOrderId, orderStatus = OrderStatus.NEW),
|
||||
userWalletId = userWalletId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
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.repository.OnboardingRepository
|
||||
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 CreateVirtualAccountOrderUseCaseTest {
|
||||
|
||||
private val onboardingRepository: OnboardingRepository = mockk(relaxUnitFun = true)
|
||||
private val pollingUseCase: StartTangemPayOrderPollingUseCase = mockk(relaxed = true)
|
||||
private val useCase = CreateVirtualAccountOrderUseCase(onboardingRepository, pollingUseCase)
|
||||
|
||||
private val userWalletId = UserWalletId("1234567890ABCDEF")
|
||||
private val paymentAccountAddress = "0xcollateral"
|
||||
|
||||
@Test
|
||||
fun `GIVEN stored va order id WHEN invoke THEN skips creation and polling`() = runTest {
|
||||
coEvery { onboardingRepository.getVirtualAccountOrderId(userWalletId) } returns "existing-id"
|
||||
|
||||
val result = useCase(userWalletId, paymentAccountAddress)
|
||||
|
||||
assertThat(result.isRight()).isTrue()
|
||||
coVerify(exactly = 0) { onboardingRepository.createVirtualAccountOrder(any(), any()) }
|
||||
coVerify(exactly = 0) { onboardingRepository.storeVirtualAccountOrderId(any(), any()) }
|
||||
coVerify(exactly = 0) { pollingUseCase.invoke(any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no stored id and create succeeds WHEN invoke THEN stores id and starts polling`() = runTest {
|
||||
coEvery { onboardingRepository.getVirtualAccountOrderId(userWalletId) } returns null
|
||||
coEvery {
|
||||
onboardingRepository.createVirtualAccountOrder(userWalletId, paymentAccountAddress)
|
||||
} returns "new-id".right()
|
||||
|
||||
val result = useCase(userWalletId, paymentAccountAddress)
|
||||
|
||||
assertThat(result.isRight()).isTrue()
|
||||
coVerify(exactly = 1) { onboardingRepository.storeVirtualAccountOrderId(userWalletId, "new-id") }
|
||||
coVerify(exactly = 1) { pollingUseCase.invoke(any(), userWalletId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN no stored id and create fails WHEN invoke THEN returns error and does not store or poll`() = runTest {
|
||||
coEvery { onboardingRepository.getVirtualAccountOrderId(userWalletId) } returns null
|
||||
coEvery {
|
||||
onboardingRepository.createVirtualAccountOrder(userWalletId, paymentAccountAddress)
|
||||
} returns VisaApiError.Unspecified.left()
|
||||
|
||||
val result = useCase(userWalletId, paymentAccountAddress)
|
||||
|
||||
assertThat(result.leftOrNull()).isEqualTo(VisaApiError.Unspecified)
|
||||
coVerify(exactly = 0) { onboardingRepository.storeVirtualAccountOrderId(any(), any()) }
|
||||
coVerify(exactly = 0) { pollingUseCase.invoke(any(), any()) }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue