Updated on 2026-08-14
This commit is contained in:
parent
c3d8722f57
commit
4589332282
10 changed files with 233 additions and 13 deletions
1
domain/card/.gitignore
vendored
Normal file
1
domain/card/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/build
|
||||||
18
domain/card/build.gradle.kts
Normal file
18
domain/card/build.gradle.kts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
plugins {
|
||||||
|
alias(deps.plugins.kotlin.jvm)
|
||||||
|
alias(deps.plugins.kotlin.kapt)
|
||||||
|
id("configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":domain:core"))
|
||||||
|
// TODO: Remove after new card scan result was implemented
|
||||||
|
implementation(project(":domain:models"))
|
||||||
|
|
||||||
|
implementation(deps.tangem.card.core)
|
||||||
|
|
||||||
|
implementation(deps.hilt.core)
|
||||||
|
kapt(deps.hilt.kapt)
|
||||||
|
|
||||||
|
implementation(deps.moshi.kotlin)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.tangem.domain.card
|
||||||
|
|
||||||
|
import com.tangem.common.core.TangemSdkError
|
||||||
|
|
||||||
|
// TODO: May be add new error types
|
||||||
|
sealed class ScanCardException : Exception() {
|
||||||
|
object WrongCardId : ScanCardException()
|
||||||
|
|
||||||
|
object UserCancelled : ScanCardException()
|
||||||
|
|
||||||
|
open class ChainException : ScanCardException()
|
||||||
|
|
||||||
|
class SdkException(override val cause: TangemSdkError) : ScanCardException()
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package com.tangem.domain.card
|
||||||
|
|
||||||
|
import arrow.core.EitherNel
|
||||||
|
import arrow.core.flatMap
|
||||||
|
import arrow.core.toEitherNel
|
||||||
|
import com.tangem.TangemSdk
|
||||||
|
import com.tangem.common.core.CardIdDisplayFormat
|
||||||
|
import com.tangem.domain.card.model.ScanCardParams
|
||||||
|
import com.tangem.domain.card.repository.ScanCardRepository
|
||||||
|
import com.tangem.domain.core.chain.ChainProcessor
|
||||||
|
import com.tangem.domain.models.scan.ProductType
|
||||||
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use case responsible for scanning a card and returning a [ScanResponse] object.
|
||||||
|
* @property scanCardRepository A repository object implementing [ScanCardRepository] interface.
|
||||||
|
* @property tangemSdk An instance of [TangemSdk] to configure the display format of the card ID.
|
||||||
|
* @constructor Create a new instance of [ScanCardUseCase] with the given dependencies.
|
||||||
|
*/
|
||||||
|
class ScanCardUseCase @Inject internal constructor(
|
||||||
|
private val scanCardRepository: ScanCardRepository,
|
||||||
|
private val tangemSdk: TangemSdk,
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [ChainProcessor] object to launch the after-scan chains.
|
||||||
|
*/
|
||||||
|
private val scanChainProcessor by lazy {
|
||||||
|
ChainProcessor<ScanCardException.ChainException, ScanResponse>()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan a card and return a [ScanResponse] object.
|
||||||
|
* @param params An instance of [ScanCardParams] to configure the scan process.
|
||||||
|
* @return A [EitherNel] object with either a non-empty list of [ScanCardException] or a [ScanResponse].
|
||||||
|
*/
|
||||||
|
suspend operator fun invoke(params: ScanCardParams = ScanCardParams()): EitherNel<ScanCardException, ScanResponse> {
|
||||||
|
resetCardIdDisplayFormat()
|
||||||
|
scanChainProcessor.addChains(*params.afterScanChains)
|
||||||
|
|
||||||
|
return scanCardRepository.scanCard(
|
||||||
|
cardId = params.cardId,
|
||||||
|
allowRequestAccessCodeFromRepository = params.allowRequestAccessCodeFromRepository,
|
||||||
|
)
|
||||||
|
.onRight { scanResponse ->
|
||||||
|
updateCardIdDisplayFormat(scanResponse.productType)
|
||||||
|
}
|
||||||
|
.toEitherNel()
|
||||||
|
.flatMap { scanResponse ->
|
||||||
|
scanChainProcessor.launchChains(initial = scanResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the card ID display format to [CardIdDisplayFormat.Full].
|
||||||
|
*/
|
||||||
|
private fun resetCardIdDisplayFormat() {
|
||||||
|
tangemSdk.config.cardIdDisplayFormat = CardIdDisplayFormat.Full
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the card ID display format according to the [ProductType] of the scanned card.
|
||||||
|
* @param productType The [ProductType] of the scanned card.
|
||||||
|
*/
|
||||||
|
private fun updateCardIdDisplayFormat(productType: ProductType) {
|
||||||
|
tangemSdk.config.cardIdDisplayFormat = when (productType) {
|
||||||
|
ProductType.Twins -> CardIdDisplayFormat.LastLuhn(numbers = 4)
|
||||||
|
ProductType.SaltPay -> CardIdDisplayFormat.None
|
||||||
|
ProductType.Note,
|
||||||
|
ProductType.Wallet,
|
||||||
|
ProductType.Start2Coin,
|
||||||
|
-> CardIdDisplayFormat.Full
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.tangem.domain.card.di
|
||||||
|
|
||||||
|
import com.tangem.TangemSdk
|
||||||
|
import com.tangem.domain.card.ScanCardUseCase
|
||||||
|
import com.tangem.domain.card.repository.ScanCardRepository
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
class CardDomainModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideScanCardUseCase(scanCardRepository: ScanCardRepository, tangemSdk: TangemSdk) =
|
||||||
|
ScanCardUseCase(scanCardRepository, tangemSdk)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.domain.card.model
|
||||||
|
|
||||||
|
import com.tangem.domain.card.ScanCardException
|
||||||
|
import com.tangem.domain.core.chain.Chain
|
||||||
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the parameters to be used during a card scanning operation.
|
||||||
|
*
|
||||||
|
* @property cardId an optional card ID to scan. If null, can scan any card present.
|
||||||
|
* Defaults to null.
|
||||||
|
* @property allowRequestAccessCodeFromRepository whether to prompt the user for an access code if needed.
|
||||||
|
* Defaults to false.
|
||||||
|
* @property afterScanChains An array of chains that should be executed after a successful card scan operation.
|
||||||
|
* Defaults to an empty array.
|
||||||
|
*/
|
||||||
|
class ScanCardParams(
|
||||||
|
val cardId: String? = null,
|
||||||
|
val allowRequestAccessCodeFromRepository: Boolean = false,
|
||||||
|
val afterScanChains: Array<out Chain<ScanCardException.ChainException, ScanResponse>> = emptyArray(),
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.tangem.domain.card.repository
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.tangem.domain.card.ScanCardException
|
||||||
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A repository for scanning a card.
|
||||||
|
*/
|
||||||
|
interface ScanCardRepository {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the card with the given [cardId] and returns a [ScanResponse]. If [allowRequestAccessCodeFromRepository]
|
||||||
|
* is true, the repository may prompt the user for an access code.
|
||||||
|
*
|
||||||
|
* @param cardId an optional card ID to scan. If null, the repository should scan any card present.
|
||||||
|
* @param allowRequestAccessCodeFromRepository whether to prompt the user for an access code if needed.
|
||||||
|
* @return an [Either] that contains a [ScanCardException] in case of an error or a [ScanResponse].
|
||||||
|
*/
|
||||||
|
suspend fun scanCard(
|
||||||
|
cardId: String?,
|
||||||
|
allowRequestAccessCodeFromRepository: Boolean,
|
||||||
|
): Either<ScanCardException, ScanResponse>
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,19 @@
|
||||||
package com.tangem.domain.core.chain
|
package com.tangem.domain.core.chain
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
|
import arrow.core.EitherNel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A chain in the [ChainProcessor] class for processing a chain of operations with the ability to handle errors.
|
||||||
|
* @param E the type of error
|
||||||
|
* @param R the type of result
|
||||||
|
*/
|
||||||
interface Chain<E, R> {
|
interface Chain<E, R> {
|
||||||
suspend operator fun invoke(previousChainResult: Either<E, R>): Either<E, R>
|
|
||||||
|
/**
|
||||||
|
* Invokes the chain with the previous chain result as input and returns an [Either] result.
|
||||||
|
* @param previousChainResult the previous chain result as an [EitherNel]
|
||||||
|
* @return the result of the chain processing as an [Either]
|
||||||
|
*/
|
||||||
|
suspend operator fun invoke(previousChainResult: EitherNel<E, R>): Either<E, R>
|
||||||
}
|
}
|
||||||
|
|
@ -1,28 +1,63 @@
|
||||||
package com.tangem.domain.core.chain
|
package com.tangem.domain.core.chain
|
||||||
|
|
||||||
import arrow.core.Either
|
import arrow.core.Either
|
||||||
|
import arrow.core.EitherNel
|
||||||
|
import arrow.core.left
|
||||||
|
import arrow.core.nel
|
||||||
|
import arrow.core.right
|
||||||
|
import arrow.core.toEitherNel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class for processing a chain of operations with the ability to handle errors.
|
||||||
|
* @param E the type of error
|
||||||
|
* @param R the type of result
|
||||||
|
*/
|
||||||
class ChainProcessor<E, R> {
|
class ChainProcessor<E, R> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of chains to be executed in order.
|
||||||
|
*/
|
||||||
private val chains: MutableList<Chain<E, R>> = mutableListOf()
|
private val chains: MutableList<Chain<E, R>> = mutableListOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds chains to the existing list of chains to be executed.
|
||||||
|
* @param chains the chains to be added to the list
|
||||||
|
*/
|
||||||
fun addChains(vararg chains: Chain<E, R>) {
|
fun addChains(vararg chains: Chain<E, R>) {
|
||||||
this.chains.addAll(chains)
|
this.chains.addAll(chains)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun launchChains(initial: R, returnOnFirstError: Boolean = true): Either<E, R> {
|
/**
|
||||||
return launchChains(initial = Either.Right(initial), returnOnFirstError)
|
* Launches the chains with the given initial value as input.
|
||||||
}
|
* @param initial the initial value to start the chain processing.
|
||||||
|
* @param accumulateExceptions determines whether to accumulate exceptions or break on first exception.
|
||||||
private suspend fun launchChains(initial: Either<E, R>, returnOnFirstError: Boolean = true): Either<E, R> {
|
* Defaults to true.
|
||||||
var result = initial
|
* @return the result of the chain processing as an EitherNel, which is a disjunction that may contain multiple errors.
|
||||||
|
*/
|
||||||
chains.forEach { chain ->
|
private suspend fun launchChains(initial: Either<E, R>, accumulateExceptions: Boolean = true): EitherNel<E, R> {
|
||||||
result = chain(result)
|
return chains.fold(initial.toEitherNel()) { previousChainResult, chain ->
|
||||||
.onRight {
|
chain.invoke(previousChainResult)
|
||||||
if (returnOnFirstError) return result
|
.mapLeft { e ->
|
||||||
|
previousChainResult.leftOrNull()?.plus(e)
|
||||||
|
?: e.nel()
|
||||||
|
}
|
||||||
|
.onLeft { e ->
|
||||||
|
if (!accumulateExceptions) {
|
||||||
|
return e.left()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
/**
|
||||||
|
* Launches the chains with the given initial value as input.
|
||||||
|
* This function is a convenience function that assumes the initial value does not contain any errors.
|
||||||
|
* @param initial the initial value to start the chain processing.
|
||||||
|
* @param accumulateExceptions determines whether to accumulate exceptions or break on first exception.
|
||||||
|
* Defaults to true.
|
||||||
|
* @return the result of the chain processing as an EitherNel, which is a disjunction that may contain multiple errors.
|
||||||
|
*/
|
||||||
|
suspend fun launchChains(initial: R, accumulateExceptions: Boolean = true): EitherNel<E, R> {
|
||||||
|
return launchChains(initial.right(), accumulateExceptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -64,4 +64,5 @@ include(":domain:models")
|
||||||
include(":domain:legacy")
|
include(":domain:legacy")
|
||||||
|
|
||||||
include(":domain:core")
|
include(":domain:core")
|
||||||
|
include(":domain:card")
|
||||||
// endregion Domain modules
|
// endregion Domain modules
|
||||||
Loading…
Add table
Add a link
Reference in a new issue