Updated on 2026-08-14
This commit is contained in:
parent
ff55704d32
commit
9e2eb5710b
82 changed files with 1150 additions and 401 deletions
|
|
@ -27,6 +27,7 @@ dependencies {
|
|||
implementation(deps.spongecastle.core)
|
||||
|
||||
/** Libs - Other */
|
||||
implementation(deps.timber)
|
||||
implementation(deps.jodatime)
|
||||
implementation(deps.androidx.paging.runtime)
|
||||
implementation(deps.moshi)
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ sealed class VisaApiError(
|
|||
fun isUnknown() = this is UnknownWithoutCode || this is Unknown
|
||||
|
||||
data object RefreshTokenExpired : VisaApiError(104004001)
|
||||
data object NotPaeraCustomer : VisaApiError(104004002)
|
||||
|
||||
companion object {
|
||||
fun fromBackendError(backendErrorCode: Int): VisaApiError {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.visa.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TangemPayAuthTokens(
|
||||
@Json(name = "access_token") val accessToken: String,
|
||||
@Json(name = "expires_at") val expiresAt: Long,
|
||||
@Json(name = "refresh_token") val refreshToken: String,
|
||||
@Json(name = "refresh_expires_at") val refreshExpiresAt: Long,
|
||||
)
|
||||
|
||||
fun TangemPayAuthTokens.getAuthHeader(): String {
|
||||
return "Bearer $accessToken"
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
package com.tangem.domain.visa.model
|
||||
|
||||
data class TangemPayInitialCredentials(val customerWalletAddress: String, val authTokens: VisaAuthTokens)
|
||||
data class TangemPayInitialCredentials(val customerWalletAddress: String, val authTokens: TangemPayAuthTokens)
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.domain.pay.datasource
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.visa.model.TangemPayAuthTokens
|
||||
import com.tangem.domain.visa.model.TangemPayInitialCredentials
|
||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||
|
||||
interface TangemPayAuthDataSource {
|
||||
|
||||
suspend fun produceInitialCredentials(cardId: String): Either<Throwable, TangemPayInitialCredentials>
|
||||
|
||||
suspend fun refreshAuthTokens(refreshToken: String): Either<Throwable, VisaAuthTokens>
|
||||
suspend fun refreshAuthTokens(refreshToken: String): Either<Throwable, TangemPayAuthTokens>
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.domain.pay.model
|
||||
|
||||
import com.tangem.domain.visa.model.TangemPayCardFrozenState
|
||||
import java.math.BigDecimal
|
||||
|
||||
data class MainScreenCustomerInfo(
|
||||
|
|
@ -16,13 +17,8 @@ data class CustomerInfo(
|
|||
data class ProductInstance(
|
||||
val id: String,
|
||||
val cardId: String,
|
||||
val status: Status,
|
||||
) {
|
||||
enum class Status {
|
||||
ACTIVE,
|
||||
INACTIVE,
|
||||
}
|
||||
}
|
||||
val cardFrozenState: TangemPayCardFrozenState,
|
||||
)
|
||||
|
||||
data class CardInfo(
|
||||
val lastFourDigits: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.domain.pay.model
|
||||
|
||||
sealed interface TangemPayCustomerInfoError {
|
||||
|
||||
data object UnavailableError : TangemPayCustomerInfoError
|
||||
data object RefreshNeededError : TangemPayCustomerInfoError
|
||||
data object UnknownError : TangemPayCustomerInfoError
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.pay.repository
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.KycStartInfo
|
||||
|
||||
interface KycRepository {
|
||||
|
|
@ -9,5 +10,5 @@ interface KycRepository {
|
|||
/**
|
||||
* Returns KYC data to start or continue the survey
|
||||
*/
|
||||
suspend fun getKycStartInfo(): Either<UniversalError, KycStartInfo>
|
||||
suspend fun getKycStartInfo(userWalletId: UserWalletId): Either<UniversalError, KycStartInfo>
|
||||
}
|
||||
|
|
@ -2,23 +2,27 @@ package com.tangem.domain.pay.repository
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
import com.tangem.domain.pay.model.MainScreenCustomerInfo
|
||||
import com.tangem.domain.pay.model.OrderStatus
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
interface OnboardingRepository {
|
||||
|
||||
suspend fun validateDeeplink(link: String): Either<UniversalError, Boolean>
|
||||
|
||||
suspend fun isTangemPayInitialDataProduced(): Boolean
|
||||
suspend fun isTangemPayInitialDataProduced(userWalletId: UserWalletId): Boolean
|
||||
|
||||
suspend fun produceInitialData()
|
||||
suspend fun produceInitialData(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun getCustomerInfo(): Either<UniversalError, CustomerInfo>
|
||||
suspend fun getCustomerInfo(userWalletId: UserWalletId): Either<VisaApiError, CustomerInfo>
|
||||
|
||||
/**
|
||||
* Returns only if the user already authorised at least once
|
||||
*/
|
||||
suspend fun getMainScreenCustomerInfo(): Either<UniversalError, MainScreenCustomerInfo>
|
||||
suspend fun createOrder(userWalletId: UserWalletId): Job
|
||||
|
||||
fun getSavedCustomerInfo(): CustomerInfo?
|
||||
suspend fun clearOrderId(userWalletId: UserWalletId)
|
||||
suspend fun getOrderId(userWalletId: UserWalletId): String?
|
||||
suspend fun getOrderStatus(userWalletId: UserWalletId, orderId: String): Either<VisaApiError, OrderStatus>
|
||||
suspend fun checkCustomerWallet(userWalletId: UserWalletId): Either<VisaApiError, Boolean>
|
||||
fun getSavedCustomerInfo(userWalletId: UserWalletId): CustomerInfo?
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.pay.repository
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.SetPinResult
|
||||
import com.tangem.domain.pay.model.TangemPayCardBalance
|
||||
import com.tangem.domain.pay.model.TangemPayCardDetails
|
||||
|
|
@ -10,18 +11,22 @@ import kotlinx.coroutines.flow.Flow
|
|||
|
||||
interface TangemPayCardDetailsRepository {
|
||||
|
||||
suspend fun getCardBalance(): Either<UniversalError, TangemPayCardBalance>
|
||||
suspend fun getCardBalance(userWalletId: UserWalletId): Either<UniversalError, TangemPayCardBalance>
|
||||
|
||||
suspend fun revealCardDetails(): Either<UniversalError, TangemPayCardDetails>
|
||||
suspend fun revealCardDetails(userWalletId: UserWalletId): Either<UniversalError, TangemPayCardDetails>
|
||||
|
||||
suspend fun setPin(pin: String): Either<UniversalError, SetPinResult>
|
||||
suspend fun setPin(userWalletId: UserWalletId, pin: String): Either<UniversalError, SetPinResult>
|
||||
|
||||
suspend fun isAddToWalletDone(): Either<UniversalError, Boolean>
|
||||
suspend fun isAddToWalletDone(userWalletId: UserWalletId): Either<UniversalError, Boolean>
|
||||
|
||||
suspend fun setAddToWalletAsDone(): Either<UniversalError, Unit>
|
||||
suspend fun setAddToWalletAsDone(userWalletId: UserWalletId): Either<UniversalError, Unit>
|
||||
|
||||
suspend fun freezeCard(userWalletId: UserWalletId, cardId: String): Either<UniversalError, TangemPayCardFrozenState>
|
||||
suspend fun unfreezeCard(
|
||||
userWalletId: UserWalletId,
|
||||
cardId: String,
|
||||
): Either<UniversalError, TangemPayCardFrozenState>
|
||||
|
||||
suspend fun freezeCard(cardId: String): Either<UniversalError, TangemPayCardFrozenState>
|
||||
suspend fun unfreezeCard(cardId: String): Either<UniversalError, TangemPayCardFrozenState>
|
||||
fun cardFrozenState(cardId: String): Flow<TangemPayCardFrozenState>
|
||||
suspend fun cardFrozenStateSync(cardId: String): TangemPayCardFrozenState?
|
||||
}
|
||||
|
|
@ -2,20 +2,16 @@ package com.tangem.domain.pay.usecase
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.Either.Companion.catch
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
|
||||
class ProduceTangemPayInitialDataUseCase(
|
||||
private val repository: OnboardingRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): Either<Throwable, Unit> {
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<Throwable, Unit> {
|
||||
return catch {
|
||||
val isDataProduced = repository.isTangemPayInitialDataProduced()
|
||||
if (isDataProduced) {
|
||||
return@catch Unit
|
||||
} else {
|
||||
repository.produceInitialData()
|
||||
}
|
||||
repository.produceInitialData(userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,19 @@
|
|||
package com.tangem.domain.pay.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.raise.catch
|
||||
import arrow.core.right
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
import com.tangem.domain.pay.model.MainScreenCustomerInfo
|
||||
import com.tangem.domain.pay.model.OrderStatus
|
||||
import com.tangem.domain.pay.model.TangemPayCustomerInfoError
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import timber.log.Timber
|
||||
|
||||
private const val TAG = "TangemPayMainScreenCustomerInfoUseCase"
|
||||
|
||||
/**
|
||||
* Returns tangem pay customer info for the main screen banner
|
||||
|
|
@ -11,5 +23,96 @@ class TangemPayMainScreenCustomerInfoUseCase(
|
|||
private val repository: OnboardingRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(): MainScreenCustomerInfo? = repository.getMainScreenCustomerInfo().getOrNull()
|
||||
suspend operator fun invoke(
|
||||
userWalletId: UserWalletId,
|
||||
): Either<TangemPayCustomerInfoError, MainScreenCustomerInfo> = catch(
|
||||
block = {
|
||||
repository.checkCustomerWallet(userWalletId)
|
||||
.fold(
|
||||
ifLeft = { TangemPayCustomerInfoError.UnknownError.left() },
|
||||
ifRight = { hasTangemPay ->
|
||||
if (hasTangemPay) {
|
||||
proceedWithPaeraCustomerResult(userWalletId)
|
||||
} else {
|
||||
TangemPayCustomerInfoError.UnknownError.left() // ignore if there's no TangemPay
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
catch = { error ->
|
||||
Timber.tag(TAG).e(error)
|
||||
TangemPayCustomerInfoError.UnknownError.left()
|
||||
},
|
||||
)
|
||||
|
||||
private suspend fun proceedWithPaeraCustomerResult(
|
||||
userWalletId: UserWalletId,
|
||||
): Either<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
||||
val orderId = repository.getOrderId(userWalletId)
|
||||
return if (orderId != null) {
|
||||
proceedWithOrderId(userWalletId = userWalletId, orderId = orderId)
|
||||
} else {
|
||||
proceedWithoutOrder(userWalletId = userWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithoutOrder(
|
||||
userWalletId: UserWalletId,
|
||||
): Either<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
||||
return repository.getCustomerInfo(userWalletId)
|
||||
.mapLeft { error -> error.mapErrorForCustomer() }
|
||||
.map { customerInfo ->
|
||||
if (customerInfo.cardInfo == null) {
|
||||
// If order id wasn't saved -> start order creation and get customer info
|
||||
repository.createOrder(userWalletId)
|
||||
}
|
||||
MainScreenCustomerInfo(info = customerInfo, orderStatus = OrderStatus.UNKNOWN)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun proceedWithOrderId(
|
||||
userWalletId: UserWalletId,
|
||||
orderId: String,
|
||||
): Either<TangemPayCustomerInfoError, MainScreenCustomerInfo> {
|
||||
return repository.getOrderStatus(userWalletId, orderId = orderId)
|
||||
.fold(
|
||||
ifLeft = { error ->
|
||||
error.mapErrorForCustomer().left()
|
||||
},
|
||||
ifRight = { orderStatus ->
|
||||
when (orderStatus) {
|
||||
// Kyc is passed and user waits for order creation -> no need to get customer info
|
||||
OrderStatus.NEW,
|
||||
OrderStatus.PROCESSING,
|
||||
-> MainScreenCustomerInfo(
|
||||
info = CustomerInfo(productInstance = null, isKycApproved = true, cardInfo = null),
|
||||
orderStatus = orderStatus,
|
||||
).right()
|
||||
|
||||
// Order was created/cancelled -> clear order id and get customer info
|
||||
OrderStatus.COMPLETED,
|
||||
OrderStatus.CANCELED,
|
||||
OrderStatus.UNKNOWN,
|
||||
-> {
|
||||
repository.clearOrderId(userWalletId)
|
||||
// If order was cancelled -> start order creation
|
||||
if (orderStatus == OrderStatus.CANCELED) repository.createOrder(userWalletId)
|
||||
repository.getCustomerInfo(userWalletId = userWalletId)
|
||||
.mapLeft { it.mapErrorForCustomer() }
|
||||
.map { customerInfo ->
|
||||
MainScreenCustomerInfo(info = customerInfo, orderStatus = orderStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun VisaApiError.mapErrorForCustomer(): TangemPayCustomerInfoError {
|
||||
return if (this !is VisaApiError.NotPaeraCustomer) {
|
||||
TangemPayCustomerInfoError.UnavailableError
|
||||
} else {
|
||||
TangemPayCustomerInfoError.UnknownError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.domain.tangempay.repository
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchFlow
|
||||
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchingContext
|
||||
|
||||
interface TangemPayTxHistoryRepository {
|
||||
fun getTxHistoryBatchFlow(
|
||||
userWalletId: UserWalletId,
|
||||
batchSize: Int,
|
||||
context: TangemPayTxHistoryListBatchingContext,
|
||||
): TangemPayTxHistoryListBatchFlow
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.visa.datasource
|
|||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import com.tangem.domain.visa.model.TangemPayAuthTokens
|
||||
import com.tangem.domain.visa.model.VisaAuthChallenge
|
||||
import com.tangem.domain.visa.model.VisaAuthSignedChallenge
|
||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||
|
|
@ -20,17 +21,16 @@ interface VisaAuthRemoteDataSource {
|
|||
|
||||
suspend fun getCustomerWalletAuthChallenge(
|
||||
customerWalletAddress: String,
|
||||
customerWalletId: String,
|
||||
): Either<VisaApiError, VisaAuthChallenge.Wallet>
|
||||
|
||||
suspend fun getTokenWithCustomerWallet(
|
||||
sessionId: String,
|
||||
signature: String,
|
||||
nonce: String,
|
||||
): Either<VisaApiError, VisaAuthTokens>
|
||||
): Either<VisaApiError, TangemPayAuthTokens>
|
||||
|
||||
suspend fun refreshCustomerWalletAuthTokens(
|
||||
refreshToken: VisaAuthTokens.RefreshToken,
|
||||
): Either<VisaApiError, VisaAuthTokens>
|
||||
suspend fun refreshCustomerWalletAuthTokens(refreshToken: String): Either<VisaApiError, TangemPayAuthTokens>
|
||||
|
||||
suspend fun getAccessTokens(signedChallenge: VisaAuthSignedChallenge): Either<VisaApiError, VisaAuthTokens>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue