From 45893322827adb64ae091007e86fd1c67a9bb2e6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Apr 2023 19:05:58 +0300 Subject: [PATCH] Updated on 2026-08-14 --- domain/card/.gitignore | 1 + domain/card/build.gradle.kts | 18 +++++ .../tangem/domain/card/ScanCardException.kt | 14 ++++ .../com/tangem/domain/card/ScanCardUseCase.kt | 76 +++++++++++++++++++ .../tangem/domain/card/di/CardDomainModule.kt | 18 +++++ .../domain/card/model/ScanCardParams.kt | 21 +++++ .../card/repository/ScanCardRepository.kt | 24 ++++++ .../com/tangem/domain/core/chain/Chain.kt | 14 +++- .../domain/core/chain/ChainProcessor.kt | 59 +++++++++++--- settings.gradle.kts | 1 + 10 files changed, 233 insertions(+), 13 deletions(-) create mode 100644 domain/card/.gitignore create mode 100644 domain/card/build.gradle.kts create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardException.kt create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/di/CardDomainModule.kt create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/model/ScanCardParams.kt create mode 100644 domain/card/src/main/kotlin/com/tangem/domain/card/repository/ScanCardRepository.kt diff --git a/domain/card/.gitignore b/domain/card/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/domain/card/.gitignore @@ -0,0 +1 @@ +/build diff --git a/domain/card/build.gradle.kts b/domain/card/build.gradle.kts new file mode 100644 index 0000000000..0da92b17b4 --- /dev/null +++ b/domain/card/build.gradle.kts @@ -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) +} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardException.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardException.kt new file mode 100644 index 0000000000..b1000aa283 --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardException.kt @@ -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() +} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt new file mode 100644 index 0000000000..88de647d8d --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/ScanCardUseCase.kt @@ -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() + } + + /** + * 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 { + 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 + } + } +} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/di/CardDomainModule.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/di/CardDomainModule.kt new file mode 100644 index 0000000000..592df6a876 --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/di/CardDomainModule.kt @@ -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) +} \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/model/ScanCardParams.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/model/ScanCardParams.kt new file mode 100644 index 0000000000..2fa7fd798d --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/model/ScanCardParams.kt @@ -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> = emptyArray(), +) \ No newline at end of file diff --git a/domain/card/src/main/kotlin/com/tangem/domain/card/repository/ScanCardRepository.kt b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/ScanCardRepository.kt new file mode 100644 index 0000000000..0fce5103c1 --- /dev/null +++ b/domain/card/src/main/kotlin/com/tangem/domain/card/repository/ScanCardRepository.kt @@ -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 +} \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/chain/Chain.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/chain/Chain.kt index c61ed6a0bd..57ec9f59b2 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/chain/Chain.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/chain/Chain.kt @@ -1,7 +1,19 @@ package com.tangem.domain.core.chain 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 { - suspend operator fun invoke(previousChainResult: Either): Either + + /** + * 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): Either } \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/chain/ChainProcessor.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/chain/ChainProcessor.kt index be45957aff..334af39b0e 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/chain/ChainProcessor.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/chain/ChainProcessor.kt @@ -1,28 +1,63 @@ package com.tangem.domain.core.chain 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 { + + /** + * The list of chains to be executed in order. + */ private val chains: MutableList> = 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) { this.chains.addAll(chains) } - suspend fun launchChains(initial: R, returnOnFirstError: Boolean = true): Either { - return launchChains(initial = Either.Right(initial), returnOnFirstError) - } - - private suspend fun launchChains(initial: Either, returnOnFirstError: Boolean = true): Either { - var result = initial - - chains.forEach { chain -> - result = chain(result) - .onRight { - if (returnOnFirstError) return result + /** + * 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. + * Defaults to true. + * @return the result of the chain processing as an EitherNel, which is a disjunction that may contain multiple errors. + */ + private suspend fun launchChains(initial: Either, accumulateExceptions: Boolean = true): EitherNel { + return chains.fold(initial.toEitherNel()) { previousChainResult, chain -> + chain.invoke(previousChainResult) + .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 { + return launchChains(initial.right(), accumulateExceptions) } } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index be4258e20d..c8c3af9b91 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -64,4 +64,5 @@ include(":domain:models") include(":domain:legacy") include(":domain:core") +include(":domain:card") // endregion Domain modules \ No newline at end of file